454.四数相加II
题目链接:https://leetcode.cn/problems/4sum-ii/
解答:https://programmercarl.com/0454.%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0II.html
本题是使用哈希法的经典题目,而0015.三数之和,0018.四数之和并不合适使用哈希法,因为三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理;而这道题目是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况
本题解题步骤:
1. 首先定义 一个unordered_map,key放a和b两数之和,value 放a和b两数之和出现的次数。
2. 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
3. 定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
4. 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
5. 最后返回统计值 count 就可以了
map.getOrDefault(sum,0):如果在map中存在key=sum,则返回key所对应的的value。 如果在map中不存在key,则返回默认值(这里是0)
383. 赎金信
题目链接:https://leetcode.cn/problems/ransom-note/
解答:https://programmercarl.com/0383.%E8%B5%8E%E9%87%91%E4%BF%A1.html#%E6%80%9D%E8%B7%AF
本题判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成,但是这里需要注意两点:
1. 杂志里面的字母不可重复使用
2. 只有小写字母,这一点很重要
因为题目所只有小写字母,那可以采用空间换取时间的哈希策略, 用一个长度为26的数组还记录magazine里字母出现的次数,然后再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母
如果不包含,直接return false;如果包含,那么将出现次数减一,再继续往后查找,遍历完ransomNote的所有字母后,如果都包含,那么return true
时间复杂度: O(n)
空间复杂度: O(1)
15. 三数之和
题目链接:https://leetcode.cn/problems/3sum/
解答:https://programmercarl.com/0015.%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.html
题目重点:不可包含重复的三元组!!
把符合条件的三元组放进vector中,然后再去重,这样是非常费时的,很容易超时
所以这道题使用哈希法并不十分合适,使用哈希法 在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2),也是可以在leetcode上通过,但是程序的执行时间依然比较长
哈希法:时间复杂度: O(n^2);空间复杂度: O(n),额外的 set 开销
双指针法:
1. 首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上
2. 依然还是在数组中找到 abc 使得a + b +c =0,这里相当于 a = nums[i],b = nums[left],c = nums[right]
3. 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动
4. 如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止
nums[i]的去重:
问题1:是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同?
如果判断 nums[i] 与 nums[i + 1]是否相同,就把三元组中出现重复元素的情况直接pass掉了(即pass掉了nums[i]和nums[left]相同这种情况,但题目是允许的,我们需要排除的只是nums[i]两次都相同的情况),因此应该判断 nums[i] 与 nums[i - 1]是否相同
我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!
关于nums[left]和nums[right]的去重:
当找到了nums[i] + nums[left] + nums[right]=0的时候,这时才需要对left和right进行去重操作:
方法1:
while(right>left&&nums[right]==nums[right-1])right--;
while(right>left&&nums[left]==nums[left+1])left++;
right--;
left++;
方法2:
right--;
left++;
while(right>left&&nums[right]==nums[right+1]) right--;
while(right>left&&nums[left]==nums[left-1]) left++;
小细节:
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了:
if(nums[i]>0) {returnresult;}
错误点:
在nums[i]的去重时,答案给的是:
if (i>0&&nums[i]==nums[i-1])
{// 去重 continue;}
我希望用i++的形式而不是continue的形式完成,则应该用while而不是if,否则就会出错
while(i>0 && nums[i]==nums[i-1]){
if((i+1)<nums.length){i++;}
else{ break;}
}
18. 四数之和
题目链接:https://leetcode.cn/problems/4sum/
解答:https://programmercarl.com/0018.%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.html
细节注意点:
不要判断nums[k] > target 就返回了,但是我们依旧可以去做剪枝,逻辑变成nums[i] > target && (nums[i] >=0 || target >= 0)就可以了。但是我们依旧可以去做剪枝,逻辑变成nums[i] > target && (nums[i] >=0 || target >= 0)就可以了
四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3)
双指针法可以将时间复杂度降一个数量级