LintCode 亂序字符串

今天做了一道中等難度的字符串題目,這道題目花了我兩個小時,不過也做了不少的思考,寫篇日志記錄一下我的思考過程。

首先是題目內容:
<h4>題目</h4>
給出一個字符串數組S,找到其中所有的亂序字符串(Anagram)。如果一個字符串是亂序字符串,那么他存在一個字母集合相同,但順序不同的字符串也在S中。

<h4>樣例</h4>
對于字符串數組 ["lint","intl","inlt","code"]
返回 ["lint","inlt","intl"]
<h4>注意</h4>
所有的字符串都只包含小寫字母

我一開始的想法很簡單,先寫一個算法判斷兩個字符串是否為亂序字符串,再遍歷整個數組S,找出所有的亂序字符串。判斷亂序字符串的方法就是把一個字符串放入HashMap當中,使用HashMap<Character, int>記錄字符串,如果有重復的字符就把map的value值++,再遍歷第二個字符串和第一個生成出來的map,遇到相同的就把HashMap對應char的value減1,減為0就remove掉這個key。

具體是這樣的

    public boolean compareStrings(String A, String B) {
        CompareString cs = new CompareString();
        if(B.length() > A.length())
            return false;
        Map<Character,Integer> aMap = cs.StringToMap(A);
        //Map<Character,Integer> bMap = cs.StringToMap(B);
        for(int i = 0; i < B.length(); i++){
            char b = B.charAt(i);
            if (aMap.containsKey(b)){
                if(aMap.get(b) > 1){
                    int times = aMap.get(b)-1;
                    aMap.put(b, times);
                }else
                    aMap.remove(b);
            }else
                return false;
        }
        return true;
    }

    public Map<Character,Integer> StringToMap(String S){
        Map<Character,Integer> sMap = new HashMap<Character,Integer>();
        for(int i = 0; i < S.length();i++){
            char a = S.charAt(i);
            if(sMap.containsKey(a)){
                int times = sMap.get(a)+1;
                sMap.put(a, times);
            }else
                sMap.put(a, 1);
        }
        return sMap;
    }

不過這樣的想法雖然可行,但是算法復雜度卻有O(n2)那么高!因為要把整個數組S里面的String兩兩比較一遍,我覺得這太蠢了,一定有更加簡單易行的方法。

于是,必須轉換思路。

我想到,如果ASCII碼來判斷兩個字符串是否為亂序字符串呢?就好像MD5算法一樣,我最后只需要給每一個亂序字符串算出一個特定的數字,用這個數字作為key,把所有等于這個key的字符串放入一個list中,把這個list作為value,用這樣的<key, value>組成HashMap,這樣我只需要便利一遍字符串數組S,再遍歷一遍生成的HashMap,把Map中value的list長度大于1的都放到return list中作為該程序的返回值,問題不就引刃而解了嗎!這樣算法復雜度只有O(n),等于用空間換時間了,可行!

于是寫出了這樣的算法

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class disOrderString {
    /**
     * @param strs: A list of strings
     * @return: A list of strings
     */
    public List<String> anagrams(String[] strs) {
        List<String> disOrderList = new ArrayList<String>();
        Map<Long,List<String>> anagram = new HashMap<Long,List<String>>();
        disOrderString ds = new disOrderString();
        for(int i = 0; i< strs.length;i++){
            String str = strs[i];
            long strAscii = ds.stringToAscii(str);
            System.out.println(strAscii + ":" + str);
            if(anagram.containsKey(strAscii)){
                anagram.get(strAscii).add(str);
            }else{
                List<String> strString  = new ArrayList<String>();
                strString.add(str);
                anagram.put(strAscii, strString);
            }
        }
        Iterator iter = anagram.entrySet().iterator();
        while(iter.hasNext()){
            Map.Entry entry = (Map.Entry) iter.next();
            List<String> list = (List<String>) entry.getValue();
            if(list.size() > 1)
                disOrderList.addAll(list);
        }
        return disOrderList;
    }
   
    //it's unique
    public long stringToAscii(String target){
        long asciiSum = 0;
        int length = target.length();
        for(int i = 0;i<target.length();i++){
            asciiSum += Math.pow(target.charAt(i) - 'a' + 1,7); 
        }
        if(length == 0)
            return 0;
        return (long) (asciiSum*asciiSum/length);
    }
    
    public static void main(String args[]){
        disOrderString ds = new disOrderString();
        String[] strs = {"coroners","crooners","deed","ed","gums","mugs","per","potties","rep","sanity","satiny","smug","tiptoes"};
        List ret = ds.anagrams(strs);
        for(int i = 0; i < ret.size();i++){
            System.out.println(ret.get(i));
        }
    }
}

其中,stringToAscii(String target) 里的加權算法我調整了很多次,最后決定使用7次方,因為7是一個出現頻率相對低的質數,而最后返回加權后的ascii碼的平方與長度的比值,將字符串長度也考慮到其中,減少誤差。最終通過了lintCode 的測試,不過我覺得這個算法依然有改進的空間,當數組足夠復雜的情況下,可能依然有誤算的情況出現。

7次方的計算是一個非常耗費時間的計算,而樓下評論中有人給出了更簡化的算法,思路非常簡單,但我當時一門心思鉆進了計算hash數值的死胡同里……

鏈接在此: Find Anagrams

他的思路也是認為每一組亂序字符串都有唯一的相同的“ Hash 值 ”,但是這個值不局限于數值,而是數字和字母的結合,比如 "and" 和 "dan",他們的“ Hash 值 ”就是“a1d1n1","array" 和 "yarar" 就是 a2r2y1,這樣就確保了唯一性,算法效率也很高。
代碼如下

public ArrayList<String> anagrams(String[] strs) {  
  HashMap<String, ArrayList<String>> hash = new HashMap<String, ArrayList<String>>();  
  for (String str : strs) {  
    // create unique label for each string  
    String key = generalLabel(str);  
    // map the label to a list of anagrams  
    ArrayList<String> res = hash.get(key);  
    if (res==null) {  
      res = new ArrayList<String>();  
      hash.put(key, res);  
    }  
    res.add(str);  
  }  
  ArrayList<String> resSet = new ArrayList<String>();  
  for (ArrayList<String> anagram : hash.values()) {  
    // ignore strings without anagrams  
    if (anagram.size()>1) resSet.addAll(anagram);  
  }  
  return resSet;  
}  

/*  
 * create a unique label for a string  
 * "cat", "atc" => a1c1t1  
 */  
public String generalLabel(String str) {  
  int[] hash = new int[26];  
  for (int i=0; i<str.length(); ++i) {  
    int index = (int)(str.charAt(i) - 'a');  
    hash[index]++;  
  }  
  StringBuilder ss = new StringBuilder();  
  for (int i=0; i<26; ++i) {  
    if (hash[i]==0) continue;  
    char c = (char)('a' + i);  
    ss.append(c);  
    ss.append(hash[i]);  
  }  
  return ss.toString();  
}  
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 題目 給出一個字符串數組S,找到其中所有的亂序字符串(Anagram)。如果一個字符串是亂序字符串,那么他存在一個...
    六尺帳篷閱讀 564評論 0 1
  • 問題描述如下:給出一個字符串數組S,找到其中所有的亂序字符串(Anagram)。如果一個字符串是亂序字符串,那么他...
    愛秋刀魚的貓閱讀 445評論 1 1
  • 版權聲明:本文為博主原創文章,未經博主允許不得轉載。 難度:中等 要求: 給出一個字符串數組S,找到其中所有的亂序...
    柒黍閱讀 387評論 0 0
  • 背景 一年多以前我在知乎上答了有關LeetCode的問題, 分享了一些自己做題目的經驗。 張土汪:刷leetcod...
    土汪閱讀 12,766評論 0 33
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,738評論 18 399