哈希表問題

這些問題歸爲哈希表,倒不必定都是要建哈希表來作,有的題目能夠用定長的數組起到哈希表的做用。
並且不少題有其餘更好一些的方法。java

數組中的問題

兩數之和 1題(easy):

給定一個整數數組和一個目標值,找出數組中和爲目標值的兩個數。
你能夠假設每一個輸入只對應一種答案,且一樣的元素不能被重複利用。算法

示例:

給定 nums = [2, 7, 11, 15], target = 9

由於 nums[0] + nums[1] = 2 + 7 = 9
因此返回 [0, 1]

分析:

暴力方法時間複雜度要O( n^2 ),使用哈希表能夠下降到O(n);並且只需一趟掃描存值就能夠。數組

代碼:

public int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        int complement = target - nums[i];
        if (map.containsKey(complement)) {  //存值以前先在哈希表中查此元素的「配對」元素是否在其中
            return new int[] { map.get(complement), i };
        }
        map.put(nums[i], i);//由於在後續查找中要查的是數組中存的值,因此key存num[i]
    }
    throw new IllegalArgumentException("No two sum solution");//拋異常
}

存在重複元素 217題(easy):

給定一個整數數組,判斷是否存在重複元素。
若是任何值在數組中出現至少兩次,函數返回 true。若是數組中每一個元素都不相同,則返回 false。app

分析:

最簡單的題目了,不少種方法:
1.暴力法 兩個for循環 O(n^2) O(1)
2.排序法 先排序 看臨近的是否相等 O(NlogN)O(1)
3.哈希表法 一個個往哈希表裏存 O(n) O(n)函數

代碼:

public boolean containsDuplicate(int[] nums) {
    Set<Integer> set = new HashSet<>(nums.length);
    for (int x: nums) {
        if (set.contains(x)) return true;
        set.add(x);
    }
    return false;
}

存在重複元素II 219題:

給定一個整數數組和一個整數 k,判斷數組中是否存在兩個不一樣的索引 i 和 j,使得 nums [i] = nums [j],而且 i 和 j 的差的絕對值最大爲 k。ui

示例:

輸入: nums = [1,2,3,1], k = 3
輸出: true
輸入: nums = [1,2,3,1,2,3], k = 2
輸出: false

分析:

這題最開始想法是用hashmap,相似I只是加個下標多個判斷,可是不夠巧妙。高票答案是Buckets方法,哈希表中維持一個容量爲k+1個元素的「滑動窗」,至關於在限定窗長度的條件之上找重複,只用hashset就夠了。指針

代碼:

public boolean containsNearbyDuplicate(int[] nums, int k) {
        Set<Integer> set = new HashSet<Integer>();
        for(int i = 0; i < nums.length; i++){
            if(i > k) set.remove(nums[i-k-1]); //把最前面一個元素刪掉
            if(!set.add(nums[i])) return true; //add的同時就判斷重複了,重複就return
        }
        return false;
 }

有效的數獨 36題:

判斷一個 9x9 的數獨是否有效。只須要根據如下規則,驗證已經填入的數字是否有效便可。
數字 1-9 在每一行只能出現一次。
數字 1-9 在每一列只能出現一次。
數字 1-9 在每個以粗實線分隔的 3x3 宮內只能出現一次。
code

數獨部分空格內已填入了數字,空白格用 '.' 表示。對象

示例:

輸入:
[
  ["8","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
輸出: false
解釋: 除了第一行的第一個數字從 5 改成 8 之外,空格內其餘數字均與 示例1 相同。
     但因爲位於左上角的 3x3 宮內有兩個 8 存在, 所以這個數獨是無效的。

說明:blog

一個有效的數獨(部分已被填充)不必定是可解的。
只須要根據以上規則,驗證已經填入的數字是否有效便可。
給定數獨序列只包含數字 1-9 和字符 '.' 。
給定數獨永遠是 9x9 形式的。

分析:

這題是基於一個矩陣的問題,簡單的思路就是將每行,每列,每塊分別建一個hashset來判斷重複元素(一共建27個)。關鍵在於怎樣巧妙地實如今一個雙重for循環當中,怎麼樣根據二維數組的的兩個座標,將值存到對應的27個哈希表中。
二維數組中的每一個值都要遍歷到,並且每一個值都要存到對應的行列塊哈希表中。

下面的代碼使用%和/兩個運算符來幫助解決矩陣遍歷問題;
對於塊遍歷,它使用以下方式:

0,0,0,1,0,2,<--- 3個水平step,而後是1個垂直step到下一個級別。
1,0,1,1,1,2,<--- 3個水平step,而後是1個垂直step到下一個級別。
2,0,2,1,2,2,<--- 3個水平step。

依次類推....
可是,j的迭代來自於0-9
但咱們須要在3個水平step後中止,並垂直向下1個step
使用%來進行水平step,由於j%3 對於每個j增長1,結果也增長1:0%3 = 0 , 1%3 = 1, 2%3 = 2
增長3次之後重置。因此這包括每一個塊的水平遍歷三次
使用/來進行垂直step 由於/ ,每三個j增長1:0/3 = 0; 1/3 = 0; 2/3 =0; 3/3 = 1
因此到目前爲止,對於一個給定的塊,能夠用j來遍歷整個的塊。

可是由於j只是0-9,因此只會停留在第一個塊,若是要增長塊,就要用到i了。

水平step到下一個塊,用%,注意這裏要乘3了,即ColIndex = 3 * i%3,使得下一個塊在3列以後,即0,0第一個塊的開始,第二個塊是0,3(不是0,1)。一樣,要垂直移動到下一個塊,要使用/並乘以3。

代碼:

public boolean isValidSudoku(char[][] board) {
    for(int i = 0; i<9; i++){
        HashSet<Character> rows = new HashSet<Character>();
        HashSet<Character> columns = new HashSet<Character>();
        HashSet<Character> cube = new HashSet<Character>();
        for (int j = 0; j < 9;j++){
            if(board[i][j]!='.' && !rows.add(board[i][j])) //存行i的set
                return false;
            if(board[j][i]!='.' && !columns.add(board[j][i]))//存列i的set
                return false;
            int RowIndex = 3*(i/3);
            int ColIndex = 3*(i%3);
            if(board[RowIndex + j/3][ColIndex + j%3]!='.' && !cube.add(board[RowIndex + j/3][ColIndex + j%3]))  //存塊i的set
                return false;
        }
    }
    return true;
  }

字符串中的問題

無重複字符的最長子串 3題:

給定一個字符串,找出不含有重複字符的最長子串的長度。

示例:
給定 "abcabcbb" ,沒有重複字符的最長子串是 "abc" ,那麼長度就是3。
給定 "bbbbb" ,最長的子串就是 "b" ,長度是1。
給定 "pwwkew" ,最長子串是 "wke" ,長度是3。請注意答案必須是一個子串,"pwke" 是序列而不是子串。

分析:

無重複問題使用哈希表是一個方法,要注意選用hashset仍是hashmap;這道題技巧在於使用hashmap同時存值和下標;在檢測重複的可以定位到重複的值的那個下標。使用雙指針標記子串起止,試圖擴展子串。

代碼:

public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        Map<Character, Integer> map = new HashMap<>(); // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {
            if (map.containsKey(s.charAt(j))) {
                i = Math.max(map.get(s.charAt(j))+1, i);//i沒必要每次只增長1,而是一步到位,加到與當前元素重複的舊索引處+1。
                                                     //i不能回退,更新時取最大,例如:「caac「
            }                                      
            map.put(s.charAt(j), j);   
            ans = Math.max(ans, j - i+1 ); //更新最大長度ans
        }
        return ans;
    }

有效的字母異位詞 242題(easy):

給定兩個字符串 s 和 t ,編寫一個函數來判斷 t 是不是 s 的一個字母異位詞。
說明:
你能夠假設字符串只包含小寫字母。
進階:
若是輸入字符串包含 unicode 字符怎麼辦?你可否調整你的解法來應對這種狀況?

示例:

輸入: s = "anagram", t = "nagaram"
輸出: true
輸入: s = "rat", t = "car"
輸出: false

分析:

若是隻考慮只包含小寫字母,就是建一個長度爲26的數組,數組中的每一個位置至關於對應字母的計數器,最後檢查數組並返回。
也還有其餘方法:
1.排序法 String轉爲CharArray再排序
2.哈希表法 用hashmap存,value存出現的次數
3.素數法(很差,溢出) 每一個字母用不一樣的素數表示,乘起來,結果同樣說明是字母異位

代碼:

public boolean isAnagram(String s, String t) {
        if (s.length()!=t.length()) return false;
       char[] a=new char[26];
       for (int i=0;i<s.length();i++){
           a[s.charAt(i)-'a']++;
           a[t.charAt(i)-'a']--;
       }
       for (int j=0;j<a.length;j++){
           if (a[j]!=0) return false;
       }
       return true;
    }

字母異位詞分組 49題:

給定一個字符串數組,將字母異位詞組合在一塊兒。字母異位詞指字母相同,但排列不一樣的字符串。

示例:

輸入: ["eat", "tea", "tan", "ate", "nat", "bat"],
輸出:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]

說明:
全部輸入均爲小寫字母。
不考慮答案輸出的順序。

分析:

這題的常規想法確定就是使用一個長度爲26的數組來存一個字符串中相應位置上的字母是有仍是無,若是兩個字符串是異位詞,那麼紀錄後的兩個數組必定是同樣的。可是這麼作仍是很差,複雜度最高O(N^2)。

這題的技巧在於sort。一個字符串轉爲字符數組後能夠進行sort操做。這樣,若是兩個字符串是異位詞,sort後是同樣的。使用hashmap,key是sort後的字符串,value是List 。一次遍歷,將字符串依次存到對應key的List 中。將複雜度降到了O(N)。

代碼:

public List<List<String>> groupAnagrams(String[] strs) {
        if (strs == null || strs.length == 0) return new ArrayList<List<String>>();
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        for (String s : strs) {
            char[] ca = s.toCharArray();
            Arrays.sort(ca);
            String keyStr = String.valueOf(ca);
            if (!map.containsKey(keyStr)) map.put(keyStr, new ArrayList<String>());
            map.get(keyStr).add(s);
        }
        return new ArrayList<List<String>>(map.values());//這一步ArrayList的構造方法,注意map.values()返回的是map中全部值的一個視圖。
    }

重複的DNA序列 187題:

全部 DNA 由一系列縮寫爲 A,C,G 和 T 的核苷酸組成,例如:「ACGAATTCCG」。在研究 DNA 時,識別 DNA 中的重複序列有時會對研究很是有幫助。
編寫一個函數來查找 DNA 分子中全部出現超多一次的10個字母長的序列(子串)。

示例:

輸入: s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"

輸出: ["AAAAACCCCC", "CCCCCAAAAA"]

分析:

這題須要遍歷全部長度爲10的子串是沒法避免的(一共有n-9個長度爲10的子串)。在遍歷的同時要判斷重複出現要用到哈希表;可是是要找出重複出現的子串,這時就還要用另外一個哈希表(因爲最後的結果中確定不能有重複,因此仍是選用哈希表)存放重複出現的子串,而第一個哈希表只起到判斷重複的做用。

這題還有一種位運算的解法能夠節省空間(哈希表中不存String存Integer)

代碼:

public List<String> findRepeatedDnaSequences(String s) {
    Set seen = new HashSet(), repeated = new HashSet();
    for (int i = 0; i + 9 < s.length(); i++) {
        String ten = s.substring(i, i + 10);
        if (!seen.add(ten))
            repeated.add(ten); //遇到重複子串添加到結果,repeated是哈希表是爲了防止結果重複
    }
    return new ArrayList(repeated);   //用hashset初始化arraylist
}

同構字符串 205題:

給定兩個字符串 s 和 t,判斷它們是不是同構的。
若是 s 中的字符能夠被替換獲得 t ,那麼這兩個字符串是同構的。
全部出現的字符都必須用另外一個字符替換,同時保留字符的順序。兩個字符不能映射到同一個字符上,但字符能夠映射本身自己。

示例:

輸入: s = "egg", t = "add"
輸出: true
輸入: s = "foo", t = "bar"
輸出: false
輸入: s = "paper", t = "title"
輸出: true

分析:

這道題能夠用建兩個Hashmap來作,一個s的,一個t的;key存字符,value存下標;每次put時,比較各自舊的value值(即以前的下標)是否相等,不等直接返回false,遍歷一遍返回true。

注意到其實這題中的字符其實是有限個(ASCII字符一共256個),並且字符的值能夠用做索引,因此能夠用數組(也是經常使用方法);建一個512長度的數組(前256個位置用於s字符串,後256個位置用於t字符串),數組中存相應字符上一次出現的下標+1。

代碼:

public boolean isIsomorphic(String s1, String s2) {
        Map<Character, Integer> m1 = new HashMap<>();
        Map<Character, Integer> m2 = new HashMap<>();
    
        for(Integer i = 0; i < s1.length(); i++) {//注意這裏只能用Integer 

            if(m1.put(s1.charAt(i), i) != m2.put(s2.charAt(i), i)) {
            //這裏put函數返回值:與 key 關聯的舊value;若是 key 沒有任何映射關係,則返回 null。
                return false; 
                
            }
        }
        return true;
    }
/*對於只能用Integer i=0,不能寫成int i=0;屬於java中Integer包裝類中的一個知識,
因爲Integer i 是一個對象,用!=比較時,比較的是否是同一個引用,不是比較的i的值等不等,int i每次自動生成對應的包裝類時都新生成了不一樣的對象(即使i的值相等)。
那爲何有時倒是能夠的?由於當int i 的值爲-128到127時,不會生成新的不一樣的對象,而是使用池中對應的對象,這時候int的值相等時,包裝後的Integer都是同一個對象,因此就能用!=判斷不等。
*/

數組的解法:

public class Solution {
    public boolean isIsomorphic(String s1, String s2) {
        int[] m = new int[512];
        for (int i = 0; i < s1.length(); i++) {
            if (m[s1.charAt(i)] != m[s2.charAt(i)+256]) return false;
            m[s1.charAt(i)] = m[s2.charAt(i)+256] = i+1;
        }
        return true;
    }
}
/*  m[s1.charAt(i)] = m[s2.charAt(i)+256] = i+1;
    這一步爲何是i+1?由於數組中默認都是0,標記下標若是從0開始,不區分默認和後置的標記下標會出錯;
    例如:「aa」和「ab」
*/

單詞模式 290題:

給定一種 pattern(模式) 和一個字符串 str ,判斷 str 是否遵循相同的模式。
這裏的遵循指徹底匹配,例如, pattern 裏的每一個字母和字符串 str 中的每一個非空單詞之間存在着雙向鏈接的對應模式。

示例:

輸入: pattern = "abba", str = "dog cat cat dog"
輸出: true
輸入:pattern = "abba", str = "dog cat cat fish"
輸出: false

分析:

這題跟同構字符串那題實際是同樣的,不過parrern和str要進行匹配的類型不一樣了,一個是字符,一個是子字符串,因此不能用數組的方法了,用哈希表的方法作。

代碼:

public boolean wordPattern(String pattern, String str) {
    String[] words = str.split(" ");   //將str以空格分割開存入string數組
    if (words.length != pattern.length())    //長度不等直接退出
        return false;      
    Map index = new HashMap();    
//哈希表中存兩種鍵值對 (Charact,Integer)和(String,Integer)之間互不影響
    for (Integer i=0; i<words.length; ++i)   
           if (index.put(pattern.charAt(i), i) != index.put(words[i], i))   
           //put函數返回值爲之前與key關聯的value,映射之前包含一個該鍵的映射關係,則用指定值替換舊值
            return false;
    return true;
}
/*
遍歷 在這裏i被設置爲Integer而不是int是由於若是設爲int則要自動裝箱,
而同一int值自動裝箱後的生成的Integer在用==比較時不必定相等。
(只有在-128到127纔等 The JVM is caching Integer values. == only works for numbers between -128 and 127)
*/

猜數字遊戲 299題:

你正在和你的朋友玩 猜數字(Bulls and Cows)遊戲:你寫下一個數字讓你的朋友猜。每次他猜想後,你給他一個提示,告訴他有多少位數字和確切位置都猜對了(稱爲「Bulls」, 公牛),有多少位數字猜對了可是位置不對(稱爲「Cows」, 奶牛)。你的朋友將會根據提示繼續猜,直到猜出祕密數字。
請寫出一個根據祕密數字和朋友的猜想數返回提示的函數,用 A 表示公牛,用 B 表示奶牛。
請注意祕密數字和朋友的猜想數均可能含有重複數字

示例:

輸入: secret = "1807", guess = "7810"
輸出: "1A3B"
解釋: 1 公牛和 3 奶牛。公牛是 8,奶牛是 0, 1 和 7。

分析:

這裏Bulls的統計比較容易,一次遍歷比較就行;對於cows,除了Bulls外,只要secret中有的數字guess中也有,那麼對應的這個數字的cows就是這個數字公共出現的個數,例如,除去Bulls不看,secret中有2個‘1’,guess中有1個‘1’,那麼‘1’這個字符的cows就是1,再繼續看其餘數字把cows加起來。

這裏須要統計每次數字出現的個數,要定義一些計數器,不妨建兩個長度爲10的數組來計數。

代碼:

public String getHint(String secret, String guess) {
    int temp = 0;
    int bulls = 0;
    int[] nums1 = new int[10];     //存secret中的除了能對應上的其餘數字的個數  如nums[0]爲secret中除了對應上的
                                //其他0的個數
    int[] nums2 = new int[10];      //同上 存guess中的
      
    for(int i = 0; i < secret.length(); i++){
        char s = secret.charAt(i);
        char g = guess.charAt(i);
        if(s == g){
            bulls++;           //對應相等 bulls直接就加
        }
        else{
            nums1[s - '0']++;       //不然倆個數組相應位置上+1
            nums2[g - '0']++;
        }
    }
    int cows = 0;
    for(int i = 0; i < 10; i++){
        cows += Math.min(nums1[i], nums2[i]);       //遍歷一遍倆數組  cows每次加上兩數組中相應數字公共出現的次數
    }
    String res = bulls + "A" + cows + "B";
    return res;
}

數字中的問題

分數到小數 166題:

給定兩個整數,分別表示分數的分子 numerator 和分母 denominator,以字符串形式返回小數。
若是小數部分爲循環小數,則將循環的部分括在括號內。

示例:

輸入: numerator = 1, denominator = 2
輸出: "0.5"
輸入: numerator = 2, denominator = 1
輸出: "2"
輸入: numerator = 2, denominator = 3
輸出: "0.(6)"

分析:

示例中的三種就是要考慮的全部狀況。即可以整除,只有整數位,循環小數。這三種中只有整數位最好處理,有循環最最複雜;如何判斷循環?有循環就意味着重複,因此,哈希表。
注意這裏可能會有溢出的問題,因此將int轉化爲long

代碼:

public String fractionToDecimal(int numerator, int denominator) {
        if (numerator==0) return "0";
        StringBuilder res=new StringBuilder();
        res.append((numerator>0)^(denominator>0)?"-":"");
        long num=Math.abs((long) numerator);    //轉成long防止溢出
        long den=Math.abs((long)denominator);
        res.append(num/den);
        num=num%den;
        if (num==0){
            return res.toString();      //能整除沒小數部分就返回就行
        }

        res.append(".");                    //不然有小數部分 先加個小數點
        Map<Long,Integer> map=new HashMap<>();      //建哈希表存餘數是關鍵
        map.put(num,res.length());  //key是餘數 value存的是下標,以便循環時插入()
        while (num!=0){                                 
        //判斷餘數是否爲0 爲零說明就除淨了,在while中num也不老是存餘數,也作被除數用,
        //但必定是判斷餘數是否爲零,while中的語句結構順序很重要
        
           num*=10;         //相似列豎式作除法 餘數乘10再除以除數
           res.append(num/den);
           num%=den;
           if (map.containsKey(num)){
               int index=map.get(num);
               res.insert(index,"(");
               res.append(")");
               break;
           }else {
               map.put(num,res.length());
           }

        }
        return res.toString();
    }

快樂數 202題(easy):

編寫一個算法來判斷一個數是否是「快樂數」。
一個「快樂數」定義爲:對於一個正整數,每一次將該數替換爲它每一個位置上的數字的平方和,而後重複這個過程直到這個數變爲 1,也多是無限循環但始終變不到 1。若是能夠變爲 1,那麼這個數就是快樂數。

示例:

輸入: 19
輸出: true
解釋: 
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1

分析:

這題和分數到小數有點相似,都是用哈希表判斷重複數字進而判斷循環。能夠寫一個數字求和的輔助函數,方法就是% /這種。

代碼:

public boolean isHappy(int n) {
        Set<Integer> set=new HashSet<>();
        int a;
        while (set.add(n)){
            a=qiuhe(n);
            if (a==1){
                return true;
            }
            else n=a;

        }
        return false;
    }
    public int qiuhe(int m){
        int x,b=0;

        while (m>0){
            x=m%10;
            b=b+x*x;
            m=m/10;
        }return b;
    }

計數質數 204(easy):

統計全部小於非負整數 n 的質數的數量。

示例:

輸入: 10
輸出: 4
解釋: 小於 10 的質數一共有 4 個, 它們是 2, 3, 5, 7 。

分析:

這題有一個經典算法,叫 埃拉托色尼篩選法(Sieve of Eratosthenes算法)
步驟以下:
(1)先把1刪除(1既不是質數也不是合數)
(2)讀取隊列中當前最小的數2,而後把2的倍數刪去
(3)讀取隊列中當前最小的數3,而後把3的倍數刪去
(4)讀取隊列中當前最小的數5,而後把5的倍數刪去
.......
(n)讀取隊列中當前最小的狀態爲true的數n,而後把n的倍數刪去

這題呢,能用哈希表作,思路是將全部數存哈希表中,再把非質數remove掉,但會超時。
實際只須要建一個n長的boolean數組就好了。不知道這題爲何有哈希表的標籤....

代碼:

public int countPrimes(int n) {
        boolean[] notPrime = new boolean[n];
        int count = 0;
        for (int i = 2; i < n; i++) {
            if (notPrime[i] == false) {
                count++;
                for (int j = 2; i*j < n; j++) {
                    notPrime[i*j] = true;
                }
            }
        }
        
        return count;
    }

遺留問題:

18.四數之和 (雙指針) 94.二叉樹中序遍歷 (樹) 136.只出現一次的數字(位運算) 274.H指數(sort 計數排序)

相關文章
相關標籤/搜索