一、散列函數構造方法
- 除留取余法
對于散列表長度為 m 的散列函數公式為:
f(key)= key mod p (p <= m)
mod 就是取余的方法。一般 p 為小于或等于表長(最好接近 m)的最小質數或者不包含小于 20 質因子的合數。
二、處理散列沖突的方法
- 1. 開放地址法
一旦發生了沖突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,并將記錄存入。
線性探測法:
fi(key)= (f(key)+ di)MOD m (di = 1,2,3,......,m-1)
- 2. 鏈地址法
其實這個就是 HashMap 的實現原理,將所有關鍵字為同義詞的記錄存儲在一個單鏈表中,散列表只需要存儲所有同義詞子表的頭指針就行了。
三、查找的一些面試題
- 劍指 Offer 面試題 3(Java 版):二維數組中的查找
題目:在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
思路: 首先選取數組中右上角的數字。如果該數字等于要查找的數字,查找過程結束;如果該數字大于要查找的數字,剔除這個數字所在的列;如果該數字小于要查找的數字,剔除這個數字所在的行。
也就是說如果要查找的數字不在數組的右上角,則每一次都在數組的查找范圍中剔除)行或者一列,這樣每一步都可以縮小查找的范圍,直到找到要查找的數字,或者查找范圍為空。
show my code
/**
* 二維數組的查找
* @author innovator
*
*/
public class BinaryArraySearch {
/**
* 在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。
* 請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
* <p/>
* 規律:首先選取數組中右上角的數字。如果該數字等于要查找的數字,查找過程結束:
* 如果該數字大于要查找的數字,剔除這個數字所在的列:如果該數字小于要查找的數字,剔除這個數字所在的行。
* 也就是說如果要查找的數字不在數組的右上角,則每-次都在數組的查找范圍中剔除)行或者一列,這樣每一步都可以縮小
* 查找的范圍,直到找到要查找的數字,或者查找范圍為空。
*
* @param data 待查找的數組
* @param number 要查找的數
* @return 查找結果,true 找到,false 沒有找到
*/
public static boolean find(int[][] data,int number){
boolean found = false;
//輸入條件判斷
if(data == null || data.length <1 || data[0].length <1){
return false;
}
//選取右上角的數字
int row = 0;
int colum = data[0].length-1;
//行的數量
int rows = data.length;
//列的數量
int colums = data[0].length;
//行數正向增加,列數減少,同時保證在數組內
while(row >=0 && row < rows && colum >=0 && colum < colums){
//右上角的數字和指定的數字相等
if(data[row][colum] == number){
found = true;
break;
}else if(data[row][colum] > number){
//右上角的數字大于指定的數字,可以去掉最后一列
colum --;
}else{
//右上角的數字小于指定的數字,可以去掉所在的這一行
row ++;
}
}
return found;
}
public static void main(String[] args){
int[][] matrix = {
{1, 2, 8, 9},
{2, 4, 9, 12},
{4, 7, 10, 13},
{6, 8, 11, 15}
};
System.out.println(find(matrix, 7)); // 要查找的數在數組中
System.out.println(find(matrix, 5)); // 要查找的數不在數組中
System.out.println(find(matrix, 1)); // 要查找的數是數組中最小的數字
System.out.println(find(matrix, 15)); // 要查找的數是數組中最大的數字
System.out.println(find(matrix, 0)); // 要查找的數比數組中最小的數字還小
System.out.println(find(matrix, 16)); // 要查找的數比數組中最大的數字還大
System.out.println(find(null, 16)); // 健壯性測試,輸入空指針
}
}
- 劍指 Offer 面試題 35(Java 版):第一個只出現一次的字符
題目:在字符串中找出第一個只出現一次的字符。如輸入 "abaccdef",則輸出 'b'。
思路: 我們可以通過將掃描到的 char 當成哈希表的 key,它出現的次數作為 value。當我們掃描完一次后,所有字符出現的次數都已經檢查完畢了,接下來只需要順序輸出哈希表的 Entry,如果出現的次數是 1,那么直接返回。整個過程的時間復雜度是 O(n) 和 O(1)。
show my code
/**
* 尋找第一個不重復出現的字符
* @author innovator
*
*/
public class FirstNotRepeatChar {
/**
* 尋找第一個不重復出現的字符
* @param s
* @return
*/
public static char findFirstNotRepeatChar(String s){
if(s == null || s.length() <1){
throw new IllegalArgumentException("String should not be null or empty");
}
//用 LinkedHashMap 存放每個字符出現的次數,同時保證掃描順序
Map<Character,Integer> times = new LinkedHashMap();
//第一次掃描字符串
for(int i=0;i<s.length();i++){
char c = s.charAt(i);
//不包含
if(!times.containsKey(c)){
times.put(c, 1);
}else{
int value = times.get(c);
times.put(c, value+1);
}
}
Character result = '\0';
//獲取所有掃描的數據
Set<Map.Entry<Character, Integer>> entries = times.entrySet();
for(Map.Entry<Character, Integer> entry:entries){
//只出現過一次
if(entry.getValue() == 1){
result = entry.getKey();
return result;
}
}
return result;
}
public static void main(String[] args) {
System.out.println(findFirstNotRepeatChar("google")); // l
System.out.println(findFirstNotRepeatChar("aabccdbd")); // '\0'
System.out.println(findFirstNotRepeatChar("abcdefg")); // a
System.out.println(findFirstNotRepeatChar("gfedcba")); // g
System.out.println(findFirstNotRepeatChar("zzgfedcba")); // g
}
}
- 劍指 Offer 面試題 40(Java 版):數組中只出現一次的數字
題目:一個整型數組里除了兩個數字之外,其他的數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。
思路: 這個題目在強調一個(或兩個)數字只出現一次,其他的出現兩次。這有什么意義呢?我們想到異或運算的一個性質:任何一個數字異或它自己都等于0。了解了異或去重的原理,而且知道如果只有一個只出現一次的數字的求法,我們便要想辦法把它分為兩個子數組,每個子數組中包含一個只出現一次的數字,其他的數字都出現了兩次。
劍指 offer 上的思路很巧妙,依然從頭到尾異或所有的數字,這樣得到的結果實際上就是兩個只出現了一次的數字異或的結果,我們在異或后的結果中找出其二進制中最右邊為 1 的位,該位既然為 1,說明異或的兩個數 字對應的該位肯定不同,必定一個為 1,一個為 0。因此我們可以考慮根據此位是否為 1 來劃分這兩個子數組,這樣兩個只出現一次的數字就分開了。
但我們還要保證出現兩次的數字都分到同一個子數組中,肯定不能兩個重復的數字分在兩個不同的子數組中,這樣得到的結果是不對的。很明顯,相同的數字相同的位上的值是相同的,要么都為 1,要么都為 0,因此我們同樣可以通過判斷該位是否為 1 來將這些出現兩次的數字劃分到同一個子數組中,該位如果為 1,就分到一個子數組中,如果為 0,就分到另一個子數組中。這樣就能保證每個子數組中只有一個出現一次的數字,其他的數字都出現兩次,分別全部異或即可得到這兩個只出現一次的數字。時間復雜度為 O(n)。
另外,所有元素異或后,在找出最右邊為 1 的時,我用的比劍指 offer 上更簡潔的代碼,主要用到了下面的結論:
對于一個數字 X,X&(-X) 之后得到的數字,是把 X 中最右邊的 1 保留下來,其他位全部為 0。注意,這里的 -X 是 X 的相反數,-X=~X+1,這里的 ~X 意思是對 X 所有位取反,不要將二者弄混了。
show my code
public class FindNumsAppearOnceTest {
/**
* 尋找只出現一次的數字
* @param arr
* @throws Exception
*/
public static void findNumsAppearOnce(int[] arr) throws Exception{
if(arr == null || arr.length<2){
throw new Exception("輸入的數據不合法");
}
int i;
int AllXOR = 0;
//全部異或
for(i=0;i<arr.length;i++){
AllXOR ^= arr[i];
}
//0x0001,找到最右邊第一位為 1 的數
int res = findFirstBit1(AllXOR);
int num1 = 0;
int num2 = 0;
for(int k=0;k<arr.length;k++){
//同一種類型
if(isBit1(arr[k],res)){
num1 ^= arr[k];
}else {
num2 ^= arr[k];
}
}
System.out.println("不重復的數字:"+num1);
System.out.println("不重復的數字:"+num2);
}
/**
* 返回 num 的最低位的 1,其他各位都為 0
*
* 對于一個數字X,X&(-X)之后得到的數字,是把X中最右邊的1保留下來,其他位全部為0。注意,
* 這里的-X是X的相反數,-X=~X+1,這里的~X意思是對X所有位取反,不要將二者弄混了。
* @param num
* @return
*/
public static int findFirstBit1(int num){
//二者與后得到的數,將 num 最右邊的 1 保留下來,其他位的全部置為了 0
return num & (-num);
}
/**
* 判斷 num 中特定的位是否為 1,這里的要判斷的特定的位由 res 確定,res 中只有一位為 1,
* 其他位均為 0,由 findFirstBit1 函數返回,而 num 中要判斷的位便是 res 中這唯一的 1 所在的位
*
* 根據這個來將數組分成兩個數組,保證每個數組只有一個數字只出現一次
* @param num
* @param res
* @return
*/
public static boolean isBit1(int num,int res){
return ((num & res)==0) ? false:true;
}
public static void main(String[] args) throws Exception {
int[] data = {
2,4,3,6,3,2,5,5
};
findNumsAppearOnce(data);
}
}