在網上看到這篇文章:一次谷歌面試趣事。覺得其中的演算法題以及作者的解決思路很有趣,就拿來分享一下吧。
問題假設這有乙個各種字母組成的字串,假設這還有另外乙個字串,而且這個字串裡的字母數相對少一些。從演算法是講,什麼方法能最快的查出所有小字串裡的字母在大字串裡都有?
比如,如果是下面兩個字串:
string 1: abcdefghlmnopqrs
string 2: dcgsrqpo
答案是true,所有在string2裡的字母string1也都有。如果是下面兩個字串:
string 1: abcdefghlmnopqrs
string 2: dcgsrqpz
答案是false,因為第二個字串裡的z字母不在第乙個字串裡。
解決方案
1. 輪詢
對於這種操作最簡單最幼稚的做法是輪詢第二個字串裡的每個字母,看它是否同在第乙個字串裡。從演算法上講,這需要o(n*m)次操作,其中n是string1的長度,m是string2的長度。就拿上面的例子來說,最壞的情況下將會有16*8 = 128次操作。
2. 排序
乙個稍微好一點的方案是先對這兩個字串的字母進行排序,然後同時對兩個字串依次輪詢。兩個字串的排序需要o(m log m) + o(n log n)次操作(常規情況下),之後的線性掃瞄需要o(m+n)次操作。同樣拿上面的字串做例子,將會需要16*4 + 8*3 = 88加上對兩個字串線性掃瞄的16 + 8 = 24的操作。(隨著字串長度的增長,你會發現這個演算法的效果會越來越好)
/*** 排序方案:快速排序
*/public static boolean issubsetbyquicksort(string a, string b)
if(c != ca[pos])
}return true;
}public static void quicksort(char arr, int low, int high)
arr[l] = arr[r];
while(l < r && arr[l] <= pivot)
arr[r] = arr[l];
}arr[l] = pivot;
quicksort(arr, low, l - 1);
quicksort(arr, l + 1, high);}1
2345
6789
1011
1213
1415
1617
1819
2021
2223
2425
2627
2829
3031
3233
3435
3637
3839
4041
4243
4445
46不過,常規排序比如快排可以達到o(n log n)的時間複雜度,這裡也可以選用用空間換時間的的基數排序、桶排序等線性時間複雜度的排序演算法。
// 字母編碼[a - z]:[65 - 122]
public static final int letter_region = 122 - 65 + 1;
/*** 排序方案:計數排序
*/public static boolean issubsetbycountersort(string a, string b)
if(c != ca[pos])
}return true;
}public static char countersort(char arr)
for(int i = 1; i < letter_region; i++)
char res = new char[arr.length];
for(char c : arr)
return res;}1
2345
6789
1011
1213
1415
1617
1819
2021
2223
2425
2627
2829
3031
3233
3435
3637
3839
4041
4243
4445
463. 雜湊表
雜湊表hashtable是乙個只需要o(n+m)次操作的演算法。方法就是,對第乙個字串進行輪詢,把其中的每個字母都放入乙個hashtable裡(時間成本是o(n),這裡是16次操作)。然後輪詢第二個字串,在hashtable裡查詢每個字母,看能否找到。如果找不到,說明沒有匹配成功。這將消耗掉8次操作 —— 這樣兩項操作加起來一共只有24次。不錯吧,比前面兩種方案都要好。
/*** 雜湊表hashset
*/public static boolean issubsetbyhashset(string a, string b)
for(char c : cb)
}return true;}1
2345
6789
1011
1213
1415
1617
184、bitmap位圖法
這個解決方案思想和hashtable一致,只不過使用的是位圖法來為每乙個字元保留一位。同樣只需要o(n+m)次操作。
// 字母編碼區間[a - z]:[65 - 122]
public static final int letter_region = 122 - 65 + 1;
/*** 位元位方案
*/public static boolean issubsetbybitmap(string a, string b)
for(char c : cb)
}return true;
}/**
* 寫入指定位的位元
*/public static void setbit(byte bitmap, int k)
/*** 讀取指定位的位元
*/public static int getbit(byte bitmap, int k)12
3456
78910
1112
1314
1516
1718
1920
2122
2324
2526
2728
2930
3132
3334
3536
37到此為止,o(n+m)幾乎是你能得到的最好的結果了,因為至少要對每個字母至少訪問一次才能完成這項操作,而上述這兩個方案是剛好是對每個字母只訪問一次。下面看看文章中最後的這個素數方案。
5. 素數
假設我們有乙個一定個數的字母組成字串。我給每個字母分配乙個素數,從2開始,往後類推。這樣a將會是2,b將會是3,c將會是5,等等。現在我遍歷第乙個字串,把每個字母代表的素數相乘。最終會得到乙個很大的整數,對吧?然後 —— 輪詢第二個字串,用每個字母除它。如果除的結果有餘數,這說明有不匹配的字母。如果整個過程中沒有餘數,你應該知道它是第乙個字串恰好的子集了。這樣不行嗎?
public static int primes = ;
// 字母編碼區間[a - z]:[65 - 122]
public static final int letter_region = 122 - 65 + 1;
/*** 素數方案
*/public static boolean issubsetbyprimenumber(string a, string b)
system.out.println("乘積結果p = " + p.tostring());
for(char c : cb)
}return true;}1
2345
6789
1011
1213
1415
1617
1819
2021
2223
2425
2627
2829
3031
3233
34測試**
public class charactersubset }1
2345
6789
1011
1213
1415
1617
1819
2021
2223
2425
總結就如文章中所說,素數方案在演算法上並不能說就比雜湊表好。而且在實際操作中,你很可能仍會使用雜湊表的方案,因為它更通用,無需跟麻煩的大型數字打交道。但從」巧妙水平「上講,guy提供的素數方案是一種更、更、更有趣的方案。
字串是否包含問題
假設這有兩個分別由字母組成的字串a另外字串b,字串b的字母數較字串a少一些。什麼方法能最快地查出字串b所有字母是不是都在字串a裡?也就是說判斷字串b是不是字串a的真子集 為了簡化,姑且認為兩個集合都不是空集,即字串都不為空。解法一 暴力輪詢 就是將b中的每一字元都和a中的字元做對比,思想簡單此處就不...
字串是否包含問題
題目描述 假設這有乙個各種字母組成的字串a,和另外乙個字串b,字串裡b的字母數相對少一些。什麼方法能最快的查出所有小字串b裡的字母在大字串a裡都有?比如,如果是下面兩個字串 string 1 abcdefghlmnopqrs string 2 dcgsrqpo 答案是true,所有在string2裡...
字串包含問題演算法
現在假設有2個字串r和s,其中m r.len n s.len,設計乙個演算法判斷字串s中的每個字元在r串中均存在.顯然,很容易想到的乙個演算法,最粗魯最暴力演算法,其時間複雜度o m n 也就是對s字串中的每個字元在r中進行查詢判斷 這或許是我自己想到的最快的方法了。顯而易見,這樣的演算法或許不是演...