代码随想录算法训练营day07 | 454.四数相加II , 383. 赎金信, 15. 三数之和 , 18. 四数之和
454.四数相加II
教程视频:https://www.bilibili.com/video/BV1Md4y1Q7Yh/?vd_source=ddffd51aa532d23e6feac69924e20891
解法一:HashMap
类比有效字母的异位词,可以判断本题应该使用哈希结构记录遍历过的数值和次数.(本题数值范围大且不去重所以使用HashMap).
四重for循环的时间复杂度为O(n4),为了降低复杂度,可以将四个数组平均拆分成两两数组之和,此时时间复杂度为O(n2)
因此确定思路:
- 首先定义 一个HashMap,key放a和b两数之和,value 放a+b出现的次数。
- 遍历A和B数组,统计两个数组元素a,b之和,和出现的次数,放到map中。
- 定义int变量result,用来统计 a+b+c+d = 0 出现的次数。
- 再遍历C和D数组,找到如果 0-(c+d) 在map中出现过的话,就用result把map中key对应的value也就是出现次数统计出来。
- 最后返回统计值 result
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
HashMap<Integer, Integer> record = new HashMap<>();
int result = 0;
int temp;
for(int num1:nums1){
for(int num2:nums2){
temp = num1+num2;
if(record.containsKey(temp)){
record.put(temp,record.get(temp)+1);
}else{
record.put(temp,1);
}
}
}
for(int num3:nums3){
for(int num4:nums4){
temp = 0-num3-num4;
if(record.containsKey(temp)){
result+=record.get(temp);
}
}
}
return result;
}
383. 赎金信
教程:https://programmercarl.com/0383.%E8%B5%8E%E9%87%91%E4%BF%A1.html#%E6%80%9D%E8%B7%AF
解法一:数组记录
因为题目所只有小写字母,那可以采用空间换取时间的哈希策略, 用一个长度为26的数组还记录magazine里字母出现的次数。
public boolean canConstruct(String ransomNote, String magazine) {
//如果ransomNote长度更大,肯定不行
if(ransomNote.length()>magazine.length()){
return false;
}
//建立长度为26的int[]记录每个字符出现的次数(因为全都由小写英文字母组成)
int[] record = new int[26];
char[] mArr = magazine.toCharArray();
for(char m : mArr){
record[m-'a']++;
}
//遍历ransomNote判断是否可以由int[]中字符组成
char[] rArr = ransomNote.toCharArray();
for(char r : rArr){
if(--record[r-'a']<0){
return false;
}
}
return true;
}
解法二:双重循环暴力破解
复杂度高,暂不实现
15. 三数之和
解法一:排序+双指针(优)
时间复杂度:排序O(nLog(n))+搜索解O(n2)
要使得 a+b+c=0,即要在数组中找三个位置,这三个位置上的数值和为0。因此想到使用指针指定位置。
解题思路是:
- 先将数组排序(Arrays.sort(nums););
- 用指针表示选位置(i从下标0的地方开始for循环,下标 left 定义在 i+1 的位置,下标 right 在数组结尾的位置);
- 判断三数之和是否为0,若不为0则移动 left 或者 right ,直到left与right相遇为止;为0则同时移动 left 和 right,继续判断直到left与right重合;
- 由于题中给出abc三个数自身不重复,要考虑去重问题:
本题重点在于去重细节的设计
- a 在for循环开始时去重,若与【前一位置】相同则跳过本次循环。(必须与前一位置相比,否则记录不到当前a下的结果)
- b c 在【记录结果后】去重,否则会漏掉本次找到的三元组。
public List<List<Integer>> threeSum(int[] nums) {
//先进行排序
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();//返回值
for(int i = 0; i<nums.length;i++){
//如果排序后第一个数大于0,则不可能找到三元组
if(nums[i]>0){
return result;
}
//对A去重复
if(i>0 && nums[i]==nums[i-1]){
continue;//i++
}
int left = i+1;
int right = nums.length-1;
while(left < right){
if(nums[i]+nums[left]+nums[right]>0){
right--;
}else if(nums[i]+nums[left]+nums[right]<0){
left++;
}else{ // nums[i]+nums[left]+nums[right]==0
Integer[] list = new Integer[3];
list[0] = nums[i];
list[1] = nums[left];
list[2] = nums[right];
result.add(Arrays.asList(list);//这里注意,先保存结果再进行去重
//对 b c 去重复
while(left < right && nums[left]==nums[left+1]){
left++;
}
while(left < right && nums[right]==nums[right-1]){
right--;
}
left++;
right--;
}
}
}
return result;
}
解法二:哈希法(易错不使用)
18. 四数之和
教程视频:https://www.bilibili.com/video/BV1DS4y147US/?spm_id_from=333.788&vd_source=ddffd51aa532d23e6feac69924e20891
示例2说明,题中的“a、b、c 和 d 互不相同”指的是元素不同,值可以相等。
解法一
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
//结果不重复,排序解决
Arrays.sort(nums);
for(int k=0;k<nums.length;k++){
//剪枝1(注意这里增加了nums[k]>0,因为需要排除负数相加值减小的情况)
if(nums[k]>0 && nums[k]>target){
break;
}
//k去重复
if(k>0 && nums[k]==nums[k-1]){
continue;
}
for(int i = k+1;i<nums.length;i++){
//剪枝2(将nums[k]+nums[i]看作整体,思路同剪枝1)
int preSum = nums[k]+nums[i];
if(preSum>0 && preSum>target){
break;
}
//i去重
if(i>k+1 && nums[i]==nums[i-1]){
continue;
}
int left = i+1;
int right = nums.length - 1;
while(left<right){
// 题中说明了-10^9 <= nums[i] <= 10^9
// nums[k] + nums[i] + nums[left] + nums[right] > target int可能会溢出
// long sum = (long) nums[k]+nums[i]+nums[left]+nums[right];
long sum = (long) preSum+nums[left]+nums[right];
if(sum>target){
// System.out.print("right--");
right--;
}else if(sum<target){
// System.out.print("left++");
left++;
}else{
Integer[] list = new Integer[4];
list[0] = nums[k];
list[1] = nums[i];
list[2] = nums[left];
list[3] = nums[right];
result.add(Arrays.asList(list));
//left和right去重
while(left<right && nums[left]==nums[left+1]){left++;}
while(left<right && nums[right]==nums[right-1]){right--;}
left++;
right--;
}
}
}
}
return result;
}
总结
- 拆分数据时,平均分配有助于减少时间复杂度。
- 要求结果不重复往往要先进行排序,然后使用指针遍历。去重细节较多,需注意。
- 【三数之和】和【四数之和】细节较多,需要多练几遍。