LeetCode 热题 100——哈希

本文讨论了如何使用哈希表解决LeetCode中的两数之和、字母异位词分组和最长连续序列问题,展示了高效的时间复杂度策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

哈希

什么时候可以用hash?当需要重复找数据时,用空间换时间。

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案

**进阶:**你可以想出一个时间复杂度小于 O(n2) 的算法吗?

方法一:暴力枚举
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length;
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                if (nums[i] + nums[j] == target) {
                    return new int[]{i, j};
                }
            }
        }
        return new int[0];
    }
}
方法二:哈希表
//哈希表
class Solution {
    public int[] twoSum(int[] nums, int target) {
        //key:数组元素,value:该元素对应的下标
        Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
        //遍历数组 nums,i 为当前下标,每个值都判断map中是否存在 target-nums[i] 的 key 值
        for (int i = 0; i < nums.length; ++i) {
            //如果存在则找到了两个值
            if (hashtable.containsKey(target - nums[i])) {
                return new int[]{hashtable.get(target - nums[i]), i};
            }
            //如果不存在则将当前的 (nums[i],i) 存入 map 中,继续遍历直到找到为止
            hashtable.put(nums[i], i);
        }
        return new int[0];
    }
}

49. 字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例 2:

输入: strs = [""]
输出: [[""]]

示例 3:

输入: strs = ["a"]
输出: [["a"]]

提示:

  • 1 <= strs.length <= 104
  • 0 <= strs[i].length <= 100
  • strs[i] 仅包含小写字母
方法一:排序

由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定相同,故可以将排序之后的字符串作为哈希表的

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        //key:排序之后的字符串,value:互为字母异位词的所有字符串
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        //遍历字符串数组strs
        for (String str : strs) {
            char[] array = str.toCharArray();//将字符串转换为char数组
            Arrays.sort(array);//对char数字进行排序
            String key = new String(array);//将排序后的char数组转回为字符串key
            //将key作为键,如果map中还不存在该key,则new一个ArrayList作为key的value
            List<String> list = map.getOrDefault(key, new ArrayList<String>());
            //并将原始的str字符串加入该ArrayList
            list.add(str);
            //将(排序后的str--即key,[原始str])put进map中
            map.put(key, list);
        }
        //将map的所有value(即不同的字母异位词list)组合成一个ArrayList
        return new ArrayList<List<String>>(map.values());
    }
}
方法二:计数

由于互为字母异位词的两个字符串包含的字母相同,因此两个字符串中的相同字母出现的次数一定是相同的,故可以将每个字母出现的次数使用字符串表示,作为哈希表的键。

由于字符串只包含小写字母,因此对于每个字符串,可以使用长度为 26的数组记录每个字母出现的次数。需要注意的是,在使用数组作为哈希表的键时,不同语言的支持程度不同,因此不同语言的实现方式也不同。

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        //遍历字符串数组strs
        for (String str : strs) {
            int[] counts = new int[26];//准备记录字符串str的每个字母出现的次数的数组counts
            int length = str.length();//字符串str的长度
            //遍历字符串str的每个字母
            for (int i = 0; i < length; i++) {
                counts[str.charAt(i) - 'a']++;
            }
            //将每个出现次数大于 0 的字母和出现次数按顺序拼接成字符串,作为哈希表的键
            //比如将 [b,a,a,a,b,c] 编码成 a3b2c1
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < 26; i++) {
                if (counts[i] != 0) {
                    sb.append((char) ('a' + i));
                    sb.append(counts[i]);
                }
            }
            String key = sb.toString();
            //如:将""a3b2c1""作为键key,得到该键对应的value(是一个list数组)
            List<String> list = map.getOrDefault(key, new ArrayList<String>());
            list.add(str);//将原始字符串add进list
            map.put(key, list);//("a3b2c1",[str1,str6,str8...])
        }
        return new ArrayList<List<String>>(map.values());
    }
}

128. 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
方法一:哈希表

核心思想:找到可能序列的第一个数currentNum,初始化currentStreak = 1,然后从currentNum逐次+1,判断currentNum+1是否在set中,如果在,则currentStreak+1,结束序列长度就则为currentStreak,与上一次的longestStreak进行比较,取max为最新的longestStreak。

class Solution {
    public int longestConsecutive(int[] nums) {
        Set<Integer> num_set = new HashSet<Integer>();//用set对nums去重
        for (int num : nums) {
            num_set.add(num);
        }
        int longestStreak = 0;//初始化最长序列的长度为0
        for (int num : num_set) {//遍历每个元素
            //只有序列的第一个数(即:最小的数)才能进入该if
            if (!num_set.contains(num - 1)) {//如果set中不存在比num小1的数,则num就是序列的第一个数
                int currentNum = num;
                int currentStreak = 1;//记录当前的循环中的序列长度
                while (num_set.contains(currentNum + 1)) {//从最小的数+1开始往后遍历,看set中是否包含
                    currentNum += 1;
                    currentStreak += 1;
                }
                //更新当前最长的序列长度
                longestStreak = Math.max(longestStreak, currentStreak);
            }
        }
        return longestStreak;
        		//如果序列是:52 76 5 7 321 6 4 77
                //第1轮:52,不存在51,52进,但是不存在53,出,longestStreak=1
                //第2轮:76,不存在75,76进,存在77,进,longestStreak=2
                //第3轮:5,存在4,不进
                //第4轮:7,存在6,不进
                //第5轮:321,不存在320,进,但是不存在322,出,longestStreak=2
                //第6轮:6,存在5,不进
                //第7轮:4,不存在3,进,存在4、5、6、7,循环之后longestStreak=4
                //第8轮:77,不存在76,进,不存在77,出,longestStreak=4
    }
}			
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值