數據結構算法(十一) 之 散列表查找(哈希表)

一、散列函數構造方法

  • 除留取余法

對于散列表長度為 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);
    }
    
}
結果
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 基本概念 基于線性表、樹表結構的查找方法,這類查找方法都是以關鍵字的比較為基礎的。在查找過程中只考慮各元素關鍵字之...
    官先生Y閱讀 515評論 0 2
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執行單位為行(line),也就是一...
    悟名先生閱讀 4,193評論 0 13
  • 1、強隊震蕩盤口假意造冷誘惑上盤。 5月15日,恒大VS上港.主近況不佳,上輪輸球;客隊5球大勝申花加分。初盤一球...
    可樂說閱讀 1,008評論 0 1
  • 連著鍛煉了幾天,覺得整個過程特別充實。 也許是我太久沒有認真做過一件事了,在寶寶的鼓勵和開導下,我可能終于想通...
    上天的XLG閱讀 219評論 0 1
  • 研一的時候還站在開題門外 卻被老師們的氣勢給嚇壞 他瞪著大大的眼睛那么難猜 刁鉆的問題往外甩 每一項實驗設計都會有...
    財迷財謎閱讀 316評論 0 0