在之前刷力扣的过程中,我每刷道题都是先看题解然后再去边写边看,很多内容根本没消化,在进度上欺骗自己,感觉之前刷的都忘了,能力没有什么提升,现在还是一道都不会做。所以这次报了卡哥的算法训练营,希望能够按计划地有效刷题。本次刷题,我用的是 Java 语言解题,有余力的话也可能会加上 C++ 的题解。以下是算法训练营第七天的刷题记录和思考笔记。
一、454 四数相加 II
题目
给你四个整数数组 nums1
、nums2
、nums3
和 nums4
,数组长度都是 n
,请你计算有多少个元组 (i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
1 | 输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2] |
解题思路
这道题目是四个独立的数组,只要找到 A[i] + B[j] + C[k] + D[l] = 0
就可以,不用考虑有重复的四个元素相加等于 0 的情况。使用哈希法的 map 可以巧妙解决的问题,提高程序执行效率,降低时间复杂度,当然使用哈希法会提高空间复杂度,但一般来说我们都是舍空间换时间。
先统计前两个数组中的元素之和,同时统计出现的次数,放入 map。再统计剩余的两个元素的和,在 map 中找是否存在相加为 0 的情况,同时记录次数。
1 | class Solution { |
二、383 赎金信
题目
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。如果可以,返回 true
;否则返回 false
。magazine
中的每个字符只能在 ransomNote
中使用一次。
1 | 输入:ransomNote = "a", magazine = "b" |
解题思路
这道题目和 242 有效的字母异位词很像,有效的字母异位词相当于求字符串 a 和 b 是否可以相互组成 ,而这道题目是求字符串 a 能否组成字符串 b,而不用管字符串 b 能不能组成字符串 a。
这里需要注意两点。第一点为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思说明杂志里面的字母不可重复使用。第二点你可以假设两个字符串均只含有小写字母,说明只有小写字母,那可以采用空间换取时间的哈希策略, 用一个长度为 26 的数组还记录 magazine
里字母出现的次数。
1 | class Solution { |
在本题的情况下,使用 map 的空间消耗要比数组大一些的,因为 map 要维护红黑树或者哈希表,而且还要做哈希函数,是更费时的。所以对于数据量大的字符串,使用数组更加简单直接有效。
三、15 三数之和
题目
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。注意:答案中不可以包含重复的三元组。
1 | 输入:nums = [-1,0,1,2,-1,-4] |
解题思路
找出 a + b + c = 0
,即 a = nums[i], b = nums[j], c = -(a + b)
,可以使用哈希法来确定 0-(a+b)
是否在数组里出现过,但是题目中说的不可以包含重复的三元组,需要再去重,这样很容易超时。
这道题目使用双指针法要比哈希法高效一些。
首先将 nums
数组排序,然后有一层 for
循环,i
从下标 0 的地方开始,同时定一个下标 left
定义在 i+1
的位置上,定义下标 right
在数组结尾的位置上。数组中 a = nums[i],b = nums[left],c = nums[right]
,要使得 a + b + c = 0
。
如果 nums[i] + nums[left] + nums[right] > 0
就说明此时三数之和大了,因为数组是经过排序后的,所以 right
下标就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0
说明此时三数之和小了,left
就向右移动,才能让三数之和大一些,直到 left
与 right
相遇为止。
1 | class Solution { |
之前做过的 1 两数之和 就不能用双指针法,因为要求返回的是索引下标,而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。如果两数之和要求返回的是数值的话,就可以使用双指针法了。
四、18 四数之和
题目
给你一个由 n
个整数组成的数组 nums
,和一个目标值 target
。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按任意顺序返回答案 。
1 | 输入:nums = [1,0,-1,0,-2,2], target = 0 |
解题思路
本题思路整体和上一题 15 三数之和一样的,都是双指针法,但难在去重和剪枝。
四数之和的双指针解法是在三数之和的基础上再套一层 for 循环。nums[k] + nums[i]
为确定值,依然是循环内有 left
和 right
下标作为双指针,找出 nums[k] + nums[i] + nums[left] + nums[right] == target
的情况。
需要注意的细节:因为这个题目时自定义 target
,所以不能像三数之和那样进行剪枝操作。i
进行剪枝操作的时候要将 k
和 i
看成是一个整体。i
进行去重的时候,应该是 i > k + 1
。因为数字之和比较大,所以数据类型应该选择 long
,而且要对和进行强转,因为 Java 中字面量默认时 int
类型。
1 | class Solution { |
五、哈希表总结
- 数组作为哈希表:数组就是简单的哈希表,但是数组的大小是受限的。如果题中要求只有小写字母,那么就可以考虑用数组。
- set 作为哈希表:数组的大小是有限的,受到系统栈空间(不是数据结构的栈)的限制。如果数组空间够大,但哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。所以此时一样的做映射的话,就可以使用 set 了。
- map 作为哈希表:map 是一种
<key, value>
的结构,如果不仅要判断 X 是否存在而且还要记录 X 的下标位置,可以用key
保存数值,用value
在保存数值所在的下标,所以使用map
最为合适。虽然 map 是万能的,但使用 map 的空间消耗要比数组大一些,因为 map 要维护红黑树或者符号表,而且还要做哈希函数的运算。