哈希表总结

本文总结了多种使用哈希表解决算法问题的实例,包括查找共用字符、上升下降字符串、有效数独、缺失的第一个正数、字母异位词分组、最长连续序列、同构字符串、有效字母异位词、单词规律、找不同、和为K的子数组、宝石与石头以及公平的糖果棒交换等。通过哈希表,实现了快速查找、计数、去重和优化空间复杂度等功能。
摘要由CSDN通过智能技术生成

1002. 查找共用字符(计数法,数组优化哈希)

class Solution {
    public List<String> commonChars(String[] words) {
        /**
        分析:
        一般来说使用哈希表用来表示 字符-数量关系,但是本题的字符是26个字母,完全可以使用数组来替代哈希表
        这种类型的题目之前应该做过了,属于数组优化哈希的操作,使用int数组定义,数组值就是个数
        题意表达的是求解每个字符串中的公共交集,按此交集输出对应字符串个数。所谓公共交集,不就是对应位置的最小值嘛~问题解决
        
         */
         // 使用int数组替代哈希表 26代表字母个数
         int [] res = new int[26];
         // 初始化res,这里调用了字符串的api,toCharArray(),转换为了字符数组
         for(char c:words[0].toCharArray()){
             // 字符对应位置计数
             res[ c -  'a']++; 
         }
         // 遍历余下的字符串
         for(int i = 1; i < words.length; i++){
             // 定义一个临时数组
             int[] temp = new int[26];
             // 遍历字符串中的字符
             for(char c : words[i].toCharArray()){
                 // 储存到临时数组中
                 temp[c - 'a']++;
             }
             // 对数组26个字母位置个数,取交集
             for(int j = 0; j < 26; j++){
                res[j] = Math.min(res[j],temp[j]);
             }
         }
         List<String> ans = new ArrayList<>();
         // 筛选出个数大于0的字符出来
         for(int i = 0; i < 26; i++){
             if(res[i] > 0){
                 // 输出字符,这里要主要有可能有重复的字符需要输出
                 for(int j = 0; j < res[i]; j++){
                    ans.add((char)(i+'a')+"");
                 }
             }
         }
        return ans;
    }
}

1370. 上升下降字符串(计数法,数组优化哈希)

class Solution {
    public String sortString(String s) {
        /**
        分析:
        重构后的字符串所对应的字母个数是不变的,于原字符串中字母个数保持一致性。那么就可以使用一个数组用来保存对应位置的字符个数。
        对于重构后字符串,是满足字符先上升再下降的特性,利用此特性进行构造
         */
         // 定义一个26个字母的计数数组
         int[] counts = new int[26];
         for(char c:s.toCharArray()){
             // 对应位置 储存个数
             counts[ c - 'a']++;
         }
         // 使用StringBuild来动态拼接字符串
         StringBuilder sb = new StringBuilder();
         // 遍历原始数组
         while(sb.length() < s.length()){
             // 字符先上升(正序)
             for(int i = 0; i < 26; i++){
                 if(counts[i] > 0){
                     // 储存进sb
                     sb.append((char)(i + 'a'));
                     // 对应个数-1
                     counts[i]--;
                 }
             }
             // 字符后下降(倒序)
             for(int i = 25; i >= 0; i--){
                 if(counts[i] > 0){
                     // 储存进sb
                     sb.append((char)(i + 'a'));
                     // 对应个数-1
                     counts[i]--;
                 }
             }
         }
         // 返回结果
         return sb.toString();


    }
}

36. 有效的数独(哈希表+数学推导)

class Solution {
   public static boolean isValidSudoku(char[][] board) {
       /**
       分析:
       本题看似是一个中等题,实际上是由三个简单题构成。重复问题的解法,首先要想到哈希表
       1.遍历行,看是否有重复
       2.遍历列,看是否有重复
       3.遍历九宫格,看是否有重复
       以上上三个操作都可以在一次遍历中完成,其中1和2只需要对调i,j即可。3呢需要进行推导,将i,j转换为第x个九宫格的第y个位置!
       最后完成!!!重要的是思想(哈希表)和推导思路(i,j转换为x,y)。
        */
        for (int i = 0; i < 9; i++) {
            Set<Character> rowSet = new HashSet<>();
            Set<Character> colSet = new HashSet<>();
            Set<Character> squareSet = new HashSet<>();
            for (int j = 0; j < 9; j++) {
                if (board[i][j] != '.') {
                    if (rowSet.contains(board[i][j])) {
                        return false;
                    } else {
                        rowSet.add(board[i][j]);
                    }
                }
                if (board[j][i] != '.') {
                    if (colSet.contains(board[j][i])) {
                        return false;
                    } else {
                        colSet.add(board[j][i]);
                    }
                }
                // ⌊i/3⌋∗3+⌊j/3⌋
                // 如何将ij转换为九宫格的下标
                // x 代表的9*9中第几个九宫格(总共9个九宫格,下标编码0-8)
                // y 代表的是在该九宫格下的下标(下标编码0-8),所以这里要用取余操作
                // 上面两个的推导比较复杂,可以看下题解:https://leetcode-cn.com/problems/valid-sudoku/solution/36-jiu-an-zhao-cong-zuo-wang-you-cong-shang-wang-x/
                int x = i / 3 * 3 + j / 3;
                int y = i % 3 * 3 + j % 3;
                if (board[x][y] != '.') {
                    if (squareSet.contains(board[x][y])) {
                        return false;
                    } else {
                        squareSet.add(board[x][y]);
                    }
                }
            }
        }
        return true;
    }
}

41. 缺失的第一个正数(数组的原地哈希)

class Solution {
    public int firstMissingPositive(int[] nums) {
        /***
        分析:
        题目限制了条件,时间复杂度为O(n),空间复杂度为O(1)。
        若是空间复杂度没有限制,则可以使用哈希查找.
        
         */
        // 使用哈希查找
        Set<Integer> set = new HashSet<>();
        for(int i:nums){
            set.add(i);
        }
        for(int i = 1; i <= nums.length; i++){
            if(!set.contains(i)){
                return i;
            }
        }
        return nums.length+1;
    }
}

注意:其实哈希表本质就是数组+链表,jdk8中还引入了红黑树。那么我们要将空间复杂度降低为O(1),其实就是将数组哈希化(就是编写自己的哈希函数),这里的哈希函数也很简单,就是将数字为i的数隐射到索引为i-1上,每次都循环隐射(循环前,先判断两个位置是否已经相等了,隐射的方法采用交换位置法)

class Solution {
    public int firstMissingPositive(int[] nums) {
        /***
        分析:
        题目限制了条件,时间复杂度为O(n),空间复杂度为O(1)。
        若是空间复杂度没有限制,则可以使用哈希查找.
        
         */

        // 使用原地哈希算法
        // 本质是交换座位,将数字为i的数隐射到索引为i-1的位置上(采用交换位置法)
        int len = nums.length;
        for(int i = 0; i < len; i++){
            // 循环隐射,隐射前先判断两个下标位置的值是否相等
            // nums[i] >= 1 && nums[i] <= len 是保证了数字在[1,len],在这个区间的数才应该放到正确位置上,其余的数,不用管!!!
            while(nums[i] >= 1 && nums[i] <= len && nums[nums[i] - 1] != nums[i]){
                // 交换下标
                // 举个例子,索引为0的数为3,索引为2的数为-1,那么这个数3就要交换到索引为2的位置上
                swap(nums,nums[i] - 1, i);
            }
        }
        // 遍历
        for(int i = 0; i < len; i++){
            if( nums[i] != i + 1){
                // 发现第一个隐射不一致的,直接返回
                return i+1;
            }
        }
        // 否则返回len + 1
        return len + 1;
    }
    public void swap(int[] nums,int index1,int index2){
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

49. 字母异位词分组(哈希表)

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        /**
        分析:
        解决本题其实并不难,关键就是读懂题意。
        举个例子:
        输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
        输出:
        [
            ["bat"],              --> abt
            ["eat", "tea", ate"], --> aet
            ["tan", "nat"]        --> ant
        ]
        这里的第二个列表的数据有 eat tea ate ,这三个数都是有字母 a e t 构成的,所以分为一组。
        那么我就可以进行模式识别了,将一个字符串转换为char数组,然后按照字典排序,若数据经过字典排序后是一致的,储存到哈希表中。最后获取哈希表中所有的value值。

        
         */
        // 定义哈希表,键为string类型,值为列表类型
        Map<String,ArrayList<String>> map = new HashMap<>();
        // 遍历strs
        for(String str : strs){
            // 将str转换为char数组
            char[] ch = str.toCharArray();
            // 按照字典排序
            Arrays.sort(ch);
            // 再将字典排序的ch转换为string
            String s = String.valueOf(ch);
            // 判断是否已经在map中
            if(!map.containsKey(s)){
                // 没有,就新建一个
                map.put(s,new ArrayList<String>());
            }
            // 将str添加到map中键为s的值中
            map.get(s).add(str);
        }
        // 最后返回map中的value值,记得外层再包裹一个list
        return new ArrayList(map.values());
    }
}

128. 最长连续序列(哈希表)

class Solution {
    public int longestConsecutive(int[] nums) {
        /**
        分析:
        按道理来说未排序,第一想法就是先排序,但是有时间复杂度的要求,很自然的想到使用空间换取时间。那就是使用哈希表。
        先定义一个哈希表,然后将数字加入到哈希表去重,然后循环判断该数x是不是起始数(判断哈希表中是否存在x-1即可),是起始数,那么就要累加长度,最后不断更新最大长度。
        
         */
         // 定义哈希表
         Set<Integer> set = new HashSet<>();
         // 去重
         for(int num:nums){
             set.add(num);
         }
        int res = 0;
         // 遍历查找
         for(int x : nums){
             // 判断是否是起始数
             if(!set.contains(x - 1)){
                 // 是起始数
                 // 那么定义一个变量记录当前数
                
                 int y = x;
                 // 遍历判断是否有 y + 1
                 while(set.contains(y + 1)){
                     // 数据累加
                     y++;
                 }
                 // 更新最新的res
                res = Math.max(res,y - x + 1);
             }
            
         }
         return res;

    }
}

205. 同构字符串(单哈希表+双哈希表)

使用单哈希表,其实就是将题目条件转换成代码的边界条件

class Solution {
    public boolean isIsomorphic(String s, String t) {
        /**
        分析:
        构建一个哈希表,将s中的字符与t中的字符进行映射。
        遍历哈希表,若取出的值与t相等,则就是同构字符串。
        这里要注意边界条件:
        不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。
         */
         Map<Character,Character> map = new HashMap<>();
         for(int i = 0; i < s.length(); i++){
             // 判断map的键是否包含字符 -- 没有包含s的字符,考虑添加
             if(!map.containsKey(s.charAt(i))){
                 // map中的值包含了t的字符,直接return false
                 // 这句话实际上说明了:不同的字符隐射到了同一个字符上
                 if(map.containsValue(t.charAt(i))){
                     return false;
                 }
                 // 添加
                 map.put(s.charAt(i),t.charAt(i));
             }else{
                 // 这句话说明了:同一个字符隐射得到的结果是不一样的字符
                 if(map.get(s.charAt(i)) != t.charAt(i)){
                     return false;
                 }
             }
         }
         return true;
    }
}

采用双射哈希表,思维跨度大,代码难度小

class Solution {
    public boolean isIsomorphic(String s, String t) {
        // 使用双射哈希表
        // 题意中可以解读出,一个字符a隐射到另一个字符b,另一个字符b隐射到字符a上
        // 以示例 2 为例,t 中的字符 a 和 r 虽然有唯一的映射 o,但对于 s 中的字符 o 来说其存在两个映射 {a,r},故不满足条件
        Map<Character,Character> map1 = new HashMap<>();
        Map<Character,Character> map2 = new HashMap<>();
        for(int i = 0; i < s.length(); i++){
            Character ch1 = s.charAt(i);
            Character ch2 = t.charAt(i);
            if(map1.containsKey(ch1) && map1.get(ch1) != ch2
            || map2.containsKey(ch2) && map2.get(ch2) != ch1){
                return false;
            }
            map1.put(ch1,ch2);
            map2.put(ch2,ch1);
        }
        return true;
        
    }
}

242. 有效的字母异位词(哈希表+数组)

使用排序法

class Solution {
    public boolean isAnagram(String s, String t) {
        /**
        分析:
        解法一:先排序,然后比较两个排序数组是否相等
        解法二:使用一个哈希表记录键和个数,然后遍历两次,看值是否存在小于0
        解法三:使用26位的数组来代替哈希表
         */
        // 解法一:排序法
        char[] s1 = s.toCharArray();
        char[] t1 = t.toCharArray();

        // 排序
        Arrays.sort(s1);
        Arrays.sort(t1);

        // 比较
        // 学到了一个新的api ,数组比较相等  Arrays.equals()
        return Arrays.equals(s1,t1);

    }
}

使用哈希表

class Solution {
    public boolean isAnagram(String s, String t) {
        // 解法二:哈希表
        // 两个长度不相等,直接结束
        if(s.length() != t.length()){
            return false;
        }
        Map<Character,Integer> map = new HashMap<>();
        for(char c : s.toCharArray()){
            // 储存值
            map.put(c,map.getOrDefault(c,0) + 1);
        }
        // 做减法
        for(char c:t.toCharArray()){
            if(!map.containsKey(c)){
                return false;
            }
            // 要改变其值,只能用put方法
            map.put(c,map.getOrDefault(c,0) - 1);
            if(map.get(c) < 0){
                return false;
            }
        }
        return true;
    }
}

使用数组

class Solution {
    public boolean isAnagram(String s, String t) {

         // 解法三:使用数组
         if(s.length() != t.length()){
             return false;
         }
        int[] res = new int[26];
        for(char c : s.toCharArray()){
            res[ c - 'a']++;
        }
        for(char c:t.toCharArray()){
            res[ c - 'a']--;
            if(res[c - 'a'] < 0){
                return false;
            }
        }
        return true;

    }
}

290. 单词规律(单哈希表+双哈希表)

单哈希表


class Solution {
    public boolean wordPattern(String pattern, String s) {
        Map<Character,String> map = new HashMap<>();
        String[] strs = s.split(" ");
        int len = strs.length;
        if(pattern.length() != len){
            return false;
        }
        for (int i = 0; i < len; i++) {
            char c = pattern.charAt(i);
            if (!map.containsKey(c)) {
                if(!map.containsValue(strs[i])){
                    map.put(c,strs[i]);
                }else{
                    return false;
                }
            }else if(!map.get(c).equals(strs[i])){
                    return false;
            }
        }
        return true;
    }
}

双哈希表

class Solution {
    public boolean wordPattern(String pattern, String s) {
        /**
        分析:
        第一想法是使用哈希表构建隐射关系.
        这里还是使用双向隐射,才可以保证是1v1
         */
         Map<Character,String> map1 = new HashMap<>();
         Map<String,Character> map2 = new HashMap<>();
         // 分割字符串
         String[] words = s.split(" ");
         // 判断长度
         if(pattern.length() != words.length){
             return false;
         }
         for(int i = 0; i < pattern.length(); i++){
             char c = pattern.charAt(i);
             String word = words[i];
             // 判断隐射关系
             if(map1.containsKey(c) && !map1.get(c).equals(word)
             ||map2.containsKey(word) && !map2.get(word).equals(c)){
                 return false;
             }
             map1.put(c,word);
             map2.put(word,c);
         }
         return true;
    }
}

389. 找不同(哈希表+数组)

使用哈希表

class Solution {
    public char findTheDifference(String s, String t) {
        /**
        分析:
        涉及重复问题,第一想法就是哈希表。t中做加法,s中做减法
         */
         Map<Character,Integer> map = new HashMap<>();
         for(int i = 0; i < t.length(); i++){
             char ch = t.charAt(i);
             map.put(ch,map.getOrDefault(ch,0) + 1);
         }
         for(int i = 0; i < s.length(); i++){
             char ch = s.charAt(i);
             map.put(ch,map.getOrDefault(ch,0) - 1);
         }
         char res = 'a';
         for(Character c:map.keySet()){
             if(map.get(c) > 0){
                res  = c;
             }
         }
         return res;

    }
}

使用数组

class Solution {
    public char findTheDifference(String s, String t) {

        // 明确是小写字母,那么就可以使用数组优化了
        int[] count = new int[26];
        for(char c : s.toCharArray()){
            count[ c - 'a']++;
        }
        for(char c : t.toCharArray()){
            count[ c - 'a']--;
            if(count[ c - 'a'] < 0){
                return c;
            }
        }
        return ' ';

    }
}

560. 和为 K 的子数组(前缀和+哈希表)

暴力法,从后往前推

class Solution {
    public int subarraySum(int[] nums, int k) {
        /**
        分析:
        题目要求是连续子数组,第一想法就是滑动窗口。但是这里不是有序数组...
         */
         // 使用暴力法,即先固定左边界,然后枚举右边界
         // 从后往前推
         int res = 0;
         for(int i = 0; i < nums.length ; i++){
             int count = 0;
             // 第二层循环相当于控制连续数组
             for(int j = i; j >= 0; j--){
                 // 连续数组累加
                 count += nums[j];
                 // 发现相等,累加。后面有可能继续相等
                 if(count == k ){
                     res++;
                 }
                 
             }
         }
         return res;
    }
}

前缀和+哈希表

class Solution {
    public int subarraySum(int[] nums, int k) {
 
        // 使用前缀和+哈希表
        Map<Integer,Integer> map = new HashMap<>();
        // put(0,1)存在的唯一意义就在于nums[i]本身就等于k的情况
        // 建立map表用于存储每个连续子数组sum求和出现的次数,初始化为(0,1),表示和为0的连续子数组出现1次。
        map.put(0,1);
        int prefixSum = 0;
        int res = 0;
        for(int i = 0; i < nums.length; i++){
            prefixSum += nums[i];
            if(map.containsKey(prefixSum - k)){
                res += map.get(prefixSum - k);
            }
            map.put(prefixSum,map.getOrDefault(prefixSum,0) + 1);
        }
        return res;

    }
}

771. 宝石与石头(哈希表+数组)

class Solution {
    public int numJewelsInStones(String jewels, String stones) {
        /**
        分析:
        解法一:J中字母储存到哈希表中,S遍历。
        解法二:题目限定了J是小写字母和大写字母,且不重复,那么就可以使用52个数组储存
         */
         int count = 0;
         Set<Character> set = new HashSet<>();
         for(Character c:jewels.toCharArray()){
             set.add(c);
         }
         for(int i = 0; i < stones.length(); i++){
             if(set.contains(stones.charAt(i))){
                 count++;
             }
         }
         return count;

        // 解法二。 A 65  z 122  122 - 65 + 1 = 58 
        int [] count = new int[58];
        for(Character c :jewels.toCharArray()){
            count[c - 'A'] = 1;
        }
        int res = 0;
        for(Character c:stones.toCharArray()){
            if(count[c - 'A'] == 1){
                res++;
            }
        }
        return res;

    }
}

888. 公平的糖果棒交换(哈希表+双指针+数学)

class Solution {
    public int[] fairCandySwap(int[] aliceSizes, int[] bobSizes) {
        /**
        分析:
        这是一个数学问题,交换后,A和B的总和是相等的。
        解法一:使用双指针法。
            问题可以理解成 sumA - xA + yB = sumB - yB + xA
            那么 xA = (sumA - sumB) / 2 + yB
            将数组排好序,定义好双指针,若 xA - yB > (sumA - sumB) / 2,则yB指针移动
            直到找到答案。
            由于排序了,时间复杂度是O(nlogn)
        解法二:
            xA = (sumA - sumB) / 2 + yB
            那么就将xA储存哈希表中,然后去判断 yB + (sumA - sumB) / 2是否存在哈希表,有的话就直接返回呗

         */
         int[] res = new int[2];
         int sumA = 0, sumB = 0; 
         for(int num:aliceSizes){
             sumA += num;
         }
         for(int num:bobSizes){
             sumB += num;
         }
         int p1 = 0, p2 = 0;
         int delta = (sumA - sumB) / 2;
         Arrays.sort(aliceSizes);
         Arrays.sort(bobSizes);
         while(p1 < aliceSizes.length && p2 < bobSizes.length){
             if(aliceSizes[p1] - bobSizes[p2] > delta ){
                 p2++;
             }else if(aliceSizes[p1] - bobSizes[p2] < delta){
                 p1++;
             }else{
                 res[0] = aliceSizes[p1];
                 res[1] = bobSizes[p2];
                 return res;
             }
         }
         return null;

    }
}

哈希表

class Solution {
    public int[] fairCandySwap(int[] aliceSizes, int[] bobSizes) {
        /**
        分析:
        这是一个数学问题,交换后,A和B的总和是相等的。
        解法一:使用双指针法。
            问题可以理解成 sumA - xA + yB = sumB - yB + xA
            那么 xA = (sumA - sumB) / 2 + yB
            将数组排好序,定义好双指针,若 xA - yB > (sumA - sumB) / 2,则yB指针移动
            直到找到答案。
            由于排序了,时间复杂度是O(nlogn)
        解法二:
            xA = (sumA - sumB) / 2 + yB
            那么就将xA储存哈希表中,然后去判断 yB + (sumA - sumB) / 2是否存在哈希表,有的话就直接返回呗

         */
        int sumA = 0, sumB = 0;
        Set<Integer> set = new HashSet<>();
        for(int num:aliceSizes){
            sumA += num;
            set.add(num);
        }
        for(int num:bobSizes){
            sumB += num;
        }
        int[] res = new int[2];
        int delta = (sumA - sumB) / 2;
        for(int y:bobSizes){
            int x = y + delta;
            if(set.contains(x)){
                res[0] = x;
                res[1] = y;
                break;
            }
        }
        return res;

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值