基础算法(9)——哈希表
1. 两数之和
题目描述:
算法思路:
解法一:暴力解法
// 解法一:暴力解法
// 方式一:固定一个数,依次与该数后面的数相加
class Solution {public int[] twoSum(int[] nums, int target) {int[] ret = new int[2];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) {ret[0] = i;ret[1] = j;return ret;}}}return ret;}
}
// 方式二:固定一个数,依次与该数前面的数相加
class Solution {public int[] twoSum(int[] nums, int target) {int[] ret = new int[2];int n = nums.length;for (int i = 0; i < n; i++) {for (int j = i - 1; j >= 0; j--) {if (nums[i] + nums[j] == target) {ret[0] = i;ret[1] = j;return ret;}}}return ret;}
}
解法二: 使用哈希表来做优化
如果基于上面暴力解法中的方式一,事先需要将所有的数字放到哈希表中,然后 for 循环 nums,判断是否有两数相加等于 target
现假设 target 为 8,在 for 循环中,遇到了 4,需要去哈希表中找有没有 4,若 nums 中就只有一个 4,那 for 循环遇到的 4 和 哈希表中的 4 就是同一个,这样是不满足条件的,所以需要加特判,不能找到自己本身相加
// 解法二:使用哈希表来做优化
// 基于暴力解法的方式一优化
class Solution {public int[] twoSum(int[] nums, int target) {// 第一步:创建哈希表,并将数组元素存入哈希表Map<Integer, Integer> numMap = new HashMap<>();for (int i = 0; i < nums.length; i++) {numMap.put(nums[i], i);}// 第二步:遍历数组,查找满足条件的两个数for (int i = 0; i < nums.length; i++) {int complement = target - nums[i];// 判断哈希表中是否存在目标数,且不是当前元素自身if (numMap.containsKey(complement) && numMap.get(complement) != i) {return new int[]{i, numMap.get(complement)};}}return new int[]{};}
}
而基于方式二进行优化,则没有这个问题,哈希表不需要提前初始化,当 for 循环遇到数字后,就去哈希表中匹配,若没有匹配到,则将当前数字添加到哈希表中(Hash<nums[i], i>),这样向后循环,不会碰到自己匹配自己的情况
// 基于暴力解法的方式二优化
class Solution {public int[] twoSum(int[] nums, int target) {// 键为数组元素值,值为元素在数组中的索引Map<Integer, Integer> numMap = new HashMap<>();for (int i = 0; i < nums.length; i++) {// 对于每个元素 nums[i],计算其与目标值 target 的差值 complementint complement = target - nums[i];// 检查 numMap 中是否包含指定的键 complementif (numMap.containsKey(complement)) {return new int[]{numMap.get(complement), i};}// 每次循环将当前元素 nums[i] 及其索引 i 存入 numMap,为后续查找做准备numMap.put(nums[i], i);}return new int[]{};}
}
2. 判定是否互为字符重排
题目描述:
算法思路
前提:先判断两字符串长度是否相等,若不等直接返回 false
不推荐的解法:
1. 找出 s1 的全排列,依次去和 s2 比较,时间复杂度是指数级别的
2. 使用 hash <char, int>,char 表示字符,int 表示出现次数,这样将 s1 和 s2 分别存到 hash1 和 hash2 中后,需要再去循环 s1,找到其中字符在 hash1 和 hash2 中出现的次数,判断是否相等
推荐解法:使用数组模拟哈希表实现
1. 使用两个哈希表,然后判断两个哈希表是否相等即可
因为题目中说明,仅有小写字母,所以仅需开辟 26 个元素大小的空间,将 s1 和 s2 分别存放到 hash1 和 hash2 中,循环比较 26 次,即可得到结果
2. 优化:使用一个哈希表 hash1 存 s1
然后遍历 s2,遇到一个字符,则在 hash1 中当前字符出现次数 -1
若减完等于负数,则不等
代码实现
class Solution {public boolean CheckPermutation(String s1, String s2) {int n1 = s1.length();int n2 = s2.length();if (n1 != n2)return false;int[] hash = new int[26];// 先把第一个字符串的信息统计到哈希表中for (int i = 0; i < n1; i++) {hash[s1.charAt(i) - 'a']++;}// 遍历第二个字符串,判断是否可以重排for (int j = 0; j < n2; j++) {hash[s2.charAt(j) - 'a']--;if (hash[s2.charAt(j) - 'a'] < 0)return false;}return true;}
}
3. 存在重复元素
题目描述:
题目解析
分析题目,出现【至少两次】的意思就是数组中存在着重复的元素,因此我们可以无需统计元素出现的数据,仅需在遍历数组的过程中,检查当前元素【是否在之前已经出现过】即可
因此我们可以利用哈希表,仅需存储【数组内的元素】,在遍历数组的时候,一边检查哈希表中是否已经出现过当前元素,一边将元素加入到哈希表中
代码实现
class Solution {public boolean containsDuplicate(int[] nums) {Set<Integer> set = new HashSet<>();for (int x : nums) {if (set.contains(x)) return true;set.add(x);}return false;}
}
4. 存在重复元素II
题目描述
题目解析
解决该问题需要我们快速定位到两个信息:
- 两个相同的元素;
- 这两个相同元素的下标。
因此,我们可以使用「哈希表」,令数组内的元素做 key 值,该元素所对应的下标做 val 值,将「数组元素」和「下标」绑定在一起,存入到「哈希表」中。
思考题: 如果数组内存在大量的「重复元素」,而我们判断下标所对应的元素是否符合条件的时候,需要将不同下标的元素作比较,怎么处理这个情况呢?
答:这里运用了一个「小贪心」。
我们按照下标「从小到大」的顺序遍历数组,当遇到两个元素相同,并且比较它们的下标时,这两个下标一定是距离最近的,因为:
- 如果当前判断符合条件直接返回 true,无需继续往后查找。
- 如果不符合条件,那么前一个下标一定不可能与后续相同元素的下标匹配(因为下标在逐渐变大),那么我们可以大胆舍去前一个存储的下标,转而将其换成新的下标,继续匹配。
代码实现
class Solution {public boolean containsNearbyDuplicate(int[] nums, int k) {Map<Integer, Integer> hash = new HashMap<>();for (int i = 0; i < nums.length; i++) {if (hash.containsKey(nums[i])) {if (i - hash.get(nums[i]) <= k) return true;}hash.put(nums[i], i);}return false;}
}
5. 字母异位词分组
题目描述
算法思路:
解法(哈希表 + 排序):
互为字母异位词的单词有一个特点:将它们「排序」之后,两个单词应该是「完全相同」的。 所以,我们可以利用这个特性,将单词按照字典序排序,如果排序后的单词相同的话,就划分到同一组中。
这时我们就要处理两个问题:
- 排序后的单词与原单词需要能互相映射;
- 将排序后相同的单词,「划分到同一组」;
利用语言提供的「容器」的强大的功能就能实现这两点:
- 将排序后的字符串(string)当做哈希表的 key 值;
- 将字母异位词数组(string[])当成 val 值。 定义一个「哈希表」即可解决问题。
代码实现:
class Solution {public List<List<String>> groupAnagrams(String[] strs) {Map<String, List<String>> hash = new HashMap<>();// 1. 异位词分组并排序for (String s : strs) {char[] tmp = s.toCharArray();Arrays.sort(tmp);String key = new String(tmp);if (!hash.containsKey(key)) {hash.put(key, new ArrayList());}hash.get(key).add(s);}// 2. 获取值return new ArrayList(hash.values());}
}