三、哈希表
-
有效的字母异位词
力扣242
-
这题是典型的哈希映射,只要将t存到哈希表中,key为t拆解的值,value为t中有过个key这样的值,然后在使用哈希表O(1)的时间复杂度判断
class Solution { public boolean isAnagram(String s, String t) { int[] record = new int[26]; for (char i: s.toCharArray()) { record[i - 'a']++; } for (char i: t.toCharArray()) { record[i - 'a']--; } for (int i: record) { if (i != 0) { return false; } } return true; } }
-
两个数组的交集
力扣349
-
同样,典型的哈希映射,只要把nums1插接到哈希表中,与上一题相似。然后foreach循环nums2,判断哈希表中是否含有对应值,有的话,就加入返回结果的数组中,注意需要Integer转int
class Solution { public int[] intersection(int[] nums1, int[] nums2) { HashMap<Integer, Integer> record = new HashMap<>(1000); List<Integer> result = new ArrayList<>(); for (int i: nums1) { record.put(i, record.getOrDefault(i, 0) + 1); } for (int i : nums2) { if (record.containsKey(i) && record.get(i) > 0 && !result.contains(i)) { result.add(i); } } // 转换 List<Integer> 为 int[] int[] intersection = new int[result.size()]; for (int i = 0; i < result.size(); i++) { intersection[i] = result.get(i); } return intersection; } }
-
快乐数
力扣202
-
看到简单题,我就上了啊,可这,是简单吗,苦命的我啊。这题咋一看很复杂,其实就算很复杂。不开玩笑了,其实题目描述的确实很绕,但其实已经给了结论,给你一个整数,把他每位数拆分并平方算出来的值循环下去,如果中间出现了一开始给的整数,那就不是快乐数,反之就算快乐数。其实是可以证明的,下图所示,再大的数也会不断变小,这里引用力扣官网的说法
-
最终会得到 1。
-
最终会进入循环。
-
值会越来越大,最后接近无穷大。
对于 3 位数的数字,它不可能大于 243。这意味着它要么被困在 243 以下的循环内,要么跌到 1。4 位或 4 位以上的数字在每一步都会丢失一位,直到降到 3 位为止。所以我们知道,最坏的情况下,算法可能会在 243 以下的所有数字上循环,然后回到它已经到过的一个循环或者回到 1。但它不会无限期地进行下去,所以我们排除第三种选择。
所以这就是一个不断取个位数并计算然后放入哈希表,看有没有value值会大于1;
class Solution { public boolean isHappy(int n) { Set<Integer> result = new HashSet<>(); while (n != 1 && !result.contains(n)) { result.add(n); int res = 0; while (n > 0) { int i = n % 10; res += i * i; n /= 10; } n = res; } return n == 1; } }
-
两数之和
力扣1
太经典了,碰到这种重复的,第一时间就可以想到哈希表,这题需要构建一个哈希表,然后并查找是否含有需要的值。
class Solution { public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>(); for (int i = 0; i < nums.length; ++i) { if (hashtable.containsKey(target - nums[i])) { return new int[] {hashtable.get(target - nums[i]), i}; } hashtable.put(nums[i], i); } return new int[0]; } }
是这样想的如果直接构建一个哈希表,然后再去计算target-遍历的值,这样是很麻烦的,需要key为nums值,value为统计数,且复杂度会上升。这个时候想到边构建哈希表,边判断是否存在哈希表内,如果元素在哈希表内则返回结果,否则进行构建哈希表,这种双利的方法在后续的题目中也经常用到
-
四数相加II
力扣454
由于是四个数组,不用考虑重复,很简单,两两数组结合构建成哈希表组成所有可能,然后就计算值判断哈希表内存不存在
class Solution { public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) { Map<Integer, Integer> res1 = new HashMap<Integer, Integer>(); int res = 0; int count = 0; for (int i = 0; i < nums1.length; ++i) { for (int j = 0; j < nums2.length; ++j) { res = nums1[i] + nums2[j]; if (res1.containsKey(res)) res1.put(res, res1.get(res) + 1); else res1.put(res, 1); } } for (int i : nums3) { for (int j : nums4) { if (res1.containsKey(-i-j)) count += res1.get(-i-j); } } return count; } }
可以构建两个哈希表,再找值,但这不上繁琐了吗,用到上一题思想,边构建边查,哎,可以再优化,不用构建,直接查,这就形参了上面的写法,写法没有什么难点。
-
救赎金
力扣383
这题其实”有效的字母异位词“很像,都需要构建哈希表,而且结构一样,key为拆开的字符,value为字符出现在字符串的次数。只要先把magazine构建为这样的哈希表,然后ransomNote遍历的时候查询哈希表中有没有这样的值,如果找到了则记得把value减去1,找不到就算是false,注意如果判断哈希表含有ransomNote所需的字符的时候要注意,要判断value,因为value是一个数字,减到0的时候key不会消失是可以查到key的,所以不能单单把key当作判断条件
class Solution { public boolean canConstruct(String ransomNote, String magazine) { int[] a = new int[26]; for (char i :magazine.toCharArray()) { a[i - 'a']++; } for (char i : ransomNote.toCharArray()) { a[i - 'a']--; if(a[i - 'a'] < 0) { return false; } } return true; } }
-
三数之和
力扣15
这题需要考虑重复,可以想到哈希表,但判断重复的方法比较难想,我先用双指针讲,后面会讲哈希表
-
双指针
class Solution { public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> result = new ArrayList<>(); Arrays.sort(nums); for (int i = 0; i < nums.length; i++) { // 如果第一个元素大于零,不可能凑成三元组 if (nums[i] > 0) { return result; } // 三元组元素a去重 if (i > 0 && nums[i] == nums[i - 1]) { continue; } HashSet<Integer> set = new HashSet<>(); for (int j = i + 1; j < nums.length; j++) { // 三元组元素b去重 if (j > i + 2 && nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2]) { continue; } int c = -nums[i] - nums[j]; if (set.contains(c)) { result.add(Arrays.asList(nums[i], nums[j], c)); set.remove(c); // 三元组元素c去重 } else { set.add(nums[j]); } } } return result; } }
首选排序,(为什么排序呢,如果不排序重复的元素不靠在一起就不好处理,下面同理)选取a作为最外层循环,b作为left指针,c作为right指针,a开始遍历整个数组,b从a迭代开始的元素开始,c始终为最后一个元素,在a的每一层循环,判断时,只要保持left<right,就可以继续判断。从上面的描述就是代码的主流程,不同于暴力三层循环,我才用a为外层循环,bc指针作为左右指针,不断逼近,可以把每一种条件都算在内,而且始终不和a接触,这样可以始终保持不重复,因为a是不断往前的,往前的过程中b和c只能在a的更前面,这就保证了不会重复。但是到这里有问题。如果每个都不一样,则已经完成,但可能存在元素相同,a是主循环,每一此值不能一样,要不然得出的结果必定重复,a的去重完成。如果b确定,c就确定,因为a已经确定了,所以当b重复的时候,要直接删除,c也同理。而且b必须和前一个元素比,c必须和后一个比,要不然[-1, -1, 0, 1, ,1],b刚到-1就结束了
-
哈希
class Solution { public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> result = new ArrayList<>(); Arrays.sort(nums); for (int i = 0; i < nums.length; i++) { // 如果第一个元素大于零,不可能凑成三元组 if (nums[i] > 0) { return result; } // 三元组元素a去重 if (i > 0 && nums[i] == nums[i - 1]) { continue; } HashSet<Integer> set = new HashSet<>(); for (int j = i + 1; j < nums.length; j++) { // 三元组元素b去重 if (j > i + 2 && nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2]) { continue; } int c = -nums[i] - nums[j]; if (set.contains(c)) { result.add(Arrays.asList(nums[i], nums[j], c)); set.remove(c); // 三元组元素c去重 } else { set.add(nums[j]); } } } return result; } }
这里的哈希不太好理解,这里也用到了和两数之和我说的那种哈希表构建方法,“边判断边构建”,涉及到这种需要大量哈希表计算的时候,这种思想很剩时间以及空间。难度就算在去重,其实也是选取a为外层循环,通过计算然后构建哈希表,判断是否有存在对应的key。同理双指针法,先排序,a不能重复,重复得到的值也是一样的,所以去重a不能重复,然后开始遍历b就算a的索引+1,这个时候用到两数之和的思想,先抛开,三数之和,这里就是两数之和,我先得到需要的target,就是-a,然后知道b了,target为-a-b,这个时候构建哈希表,没有就存,有就返回。然后就是去重了,a以及完毕了,我们知道,如果知道abc之中任意两个,其他一个就确定了,所以当c找到哈希表有对应的值后,需要把哈希表的那个键值对直接删除,以避免重复。去重b,为什么去重b,肯定是有对应场景,正常全不相等的场景不需要去重,只要重复的地方才需要,[-1, -1, -1, 0, 0] [-1, -1, 0, 1]这里只有前面的情况需要去重,因为是三数,允许两个相同的元素存在,但如果用不到则不能重复添加,所以这使用的hash集合,可直接去重复。所以重复数字达到三次才需要在条件判断的时候就删除,要这么写
j > i + 2 && nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2]
-
-
四数之和
力扣18
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
和三数之和区别不大,只是外层循环变为两层,可以适当剪枝,就算加if判断提前结束
class Solution { public List<List<Integer>> fourSum(int[] nums, int target) { Arrays.sort(nums); List<List<Integer>> result = new ArrayList<>(); if (nums.length < 4) { return result; } if (nums[0] > Integer.MAX_VALUE) { return result; } for (int i = 0; i < nums.length - 3; ++i) { if (i > 0 && nums[i] == nums[i - 1]) { continue; // 去重 } for (int j = i + 1; j < nums.length - 2; ++j) { if (j > i + 1 && nums[j] == nums[j - 1]) { continue; // 去重 } int left = j + 1; int right = nums.length - 1; while (left < right) { long sum = (long)nums[i] + nums[j] + nums[left] + nums[right]; if (sum > target) { right--; } else if (sum < target) { left++; } else { result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right])); while (left < right && nums[left] == nums[left + 1]) left++; // 去重 while (left < right && nums[right] == nums[right - 1]) right--; // 修正 left++; right--; } } } } return result; } }