【Leetcode】哈希练习

1. 两数之和

暴力枚举

  • 时间复杂度为O(n^2)的解法为遍历数组,每遍历到一个元素x,再进行二次循环,找到元素为target-x的index。
public static int[] twoSum1(int[] nums, int target) {
    
    
    int[] res = new int[2];

    for (int i = 0; i < nums.length-1; i++) {
    
    
        for (int j = i+1; j < nums.length; j++) {
    
    
            if (nums[i] + nums[j] == target) {
    
    
                res[0] = i;
                res[1] = j;
            }
        }
    }
    return res;
}
  • 然而,这是最初的解法,时间复杂度:O(n^2),空间复杂度:O(1)
  • 当在寻找target-x元素时,时间复杂度过高,而哈希表可以将时间复杂度从O(n)降到O(1),因此可以使用HashMap来降低复杂度。

哈希表

  • 使用HashMap的key存储index,使用value存储值。当遍历到一个元素x,我们从哈希表中查找是否可以找到元素为target-x的元素,将元素x存入哈希表,即可保证不会和自己x匹配。
  • 时间复杂度:O(n),空间复杂度:O(n)
public static int[] twoSum(int[] nums, int target) {
    
    
    int[] res = new int[2];
    // map存储(num,i)
    HashMap<Integer,Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
    
    
        int temp = target - nums[i];
        if (map.containsKey(temp)) {
    
    
            res[0] = i;
            res[1] = map.get(temp);
        }
        map.put(nums[i],i);
    }
    return res;
}

49. 字母异位词分组

  • 异位词:将一个单词的各个字符重排列得到的词

解题思路1: 排序

  • 解题关键在于:对于异位词来说,应该都有个关键的标志。在这里,我们将字符数组排序后的字符串作为区分异位词的标志。
  • 例如:eat,ate,tea,这三个词排序后都应该为aet,即他们本质是相同的。
  • 因此,我们可以使用哈希表,key为排序后的string,value为满足该标志的异位词eat,ate,tea
public static List<List<String>> groupAnagrams1(String[] strs) {
    
    
    HashMap<String,List<String>> map = new HashMap<>();
    for (String str : strs) {
    
    
        // 转为数组
        char[] arr = str.toCharArray();
        // 排序
        Arrays.sort(arr);
        // 转为字符串:key
        String key = String.valueOf(arr);
        if (!map.containsKey(key)) {
    
    
            map.put(key, new ArrayList<>());
        }
        map.get(key).add(str);
    }
    return new ArrayList<>(map.values());
}

解题思路2:计数

  • 由于使用排序时间复杂度较大,因此需要进行改进。
  • 第一种方法我们使用排序后的字符串(aet)作为key,第2种我们使用字符+字符频次(a1e1t1)这种的作为标志。
public static List<List<String>> groupAnagrams2(String[] strs) {
    
    
    // 使用a3b1c1这种的作为标志key
    HashMap<String,List<String>> map = new HashMap<>();
    for (String str : strs) {
    
    
        int[] counts = new int[26];
        // 计算每一个字符串的各个字符的频次
        for (int i = 0; i < str.length(); i++) {
    
    
            counts[str.charAt(i)-'a']++;
        }
        //将字符和频次进行连接作为key
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 26; i++) {
    
    
            // b + 2 => b2
            if (counts[i] > 0) {
    
    
                sb.append((char) (i + 'a')).append(counts[i]);
            }
        }
        // 进行map <key,list>
        if (!map.containsKey(sb.toString())) {
    
    
            map.put(sb.toString(),new ArrayList<>());
        }
        map.get(sb.toString()).add(str);
    }
    return new ArrayList<>(map.values());
}

128.最长连续序列

哈希表

  • 使用哈希表来存储元素与该元素的最长连续序列长度
  • 当有新元素时,计算该元素的left和right的最长连续序列长度len,则leftLen+rightLen+1为该元素的最长连续序列长度maxLen
  • 最后,我们每计算一次,都要更新该元素x的value为maxLen,更新左边界(x-leftLen)的value为maxLen,更新右边界(x-rightLen)的value为maxLen
public static int longestConsecutive(int[] nums) {
    
    
    HashMap<Integer,Integer> map = new HashMap();
    int maxLen = 0;
    // 1. 若为新元素,则计算左边和右边的长度
    for (int i = 0; i < nums.length; i++) {
    
    
        if (!map.containsKey(nums[i])) {
    
    
            // 得到左边和右边的最长子序列长度
            int left = map.getOrDefault(nums[i]-1,0);
            int right = map.getOrDefault(nums[i]+1,0);
            // 得到最长长度
            int len = 1 + left + right;
            if (len > maxLen)
                maxLen = len;
            // 更新该元素、左边界、右边界的value值
            map.put(nums[i],len);
            if (left > 0)
                map.put(nums[i]-left, len);
            if (right > 0)
                map.put(nums[i]+right, len);
        }
    }
    return maxLen;
}

猜你喜欢

转载自blog.csdn.net/weixin_40433003/article/details/132059790