day06 第18题. 四数之和&&第15题. 三数之和&&383. 赎金信&&第454题.四数相加II
1.四数之和
算法思路:采用定二移二法,先对数组进行排序,然后固定前两个数,通过双指针在剩余部分查找满足条件的两个数,使四数之和等于目标值。
优化措施:
在固定第一个数时,若当前数与后三个最小数之和大于目标值,则直接退出循环,因为后续的组合只会更大;若当前数与后三个最大数之和小于目标值,则跳过当前数,继续下一个循环。
在固定第二个数时,也有类似的优化,若当前组合的最小和大于目标值,则退出内层循环;若最大和小于目标值,则跳过当前数。
使用 long 类型存储四数之和,避免整数溢出问题。
跳过重复数字,防止结果中出现重复的四元组。
时间复杂度:O(n^3),其中 n 是数组长度。排序的时间复杂度为 O(nlogn),两层循环的时间复杂度为 O(n^2),内层的双指针移动时间复杂度为 O(n)。
空间复杂度:O(1),除了存储结果的列表外,只使用了常数级别的额外空间。
//第18题. 四数之和
public List<List
//定二移二法
Arrays.sort(nums);
List<List
int len=nums.length;
for(int i=0;i<len-3;i++){
long num=nums[i];// 使用 long 避免溢出
if(num+nums[i+1]+nums[i+3]+nums[i+2]>target)break; // 优化一
if (num+nums[len-1]+nums[len-3]+nums[len-2]<target)continue;// 优化二
if(i>0&&numnums[i-1])continue;// 跳过重复数字
for(int j=i+1;j<len-2;j++){
if(num+nums[j]+nums[j+1]+nums[i+2]>target)break;// 优化一
if (num+nums[j]+nums[len-1]+nums[len-2]<target)continue;// 优化二
if(j>i+1&&nums[j]nums[j-1])continue;// 跳过重复数字
int left=j+1;
int right=nums.length-1;
while(left<right){
long sum=num+nums[j]+nums[left]+nums[right];
if(sumtarget){
res.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--;
}else if(sum<target){
left++;
}else {
right--;
}
}
}
}
return res;
}
2.三数之和
算法思路:定一移二法。先对数组进行排序,然后固定第一个数,通过双指针在剩余部分查找满足条件的两个数,使三数之和为零。
优化措施:
若固定的第一个数大于零,则直接退出循环,因为后续的组合不可能为零。
跳过重复数字,防止结果中出现重复的三元组。
时间复杂度:O(n^2),排序的时间复杂度为 O(nlogn),一层循环的时间复杂度为 O(n),内层的双指针移动时间复杂度为 O(n)。
空间复杂度:O(1),除了存储结果的列表外,只使用了常数级别的额外空间。
//第15题. 三数之和
public List<List
//暴力法又又又超出时间限制,O(n^3)
/Set<List
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
for (int j = i + 1; j < nums.length - 1; j++) {
for (int k = j + 1; k < nums.length; k++) {
if (nums[i] + nums[j] + nums[k] == 0) {
if(nums[i]+nums[k]+nums[j]==0) {
set.add(Arrays.asList(nums[i],nums[k],nums[j]));
}
}
}
}
}
List<List
return res;
//没超出时间限制,但效率还是很低,定一移二法,能不用set,map去重就不用
/Set<List
Arrays.sort(nums);
int left,right;
for (int i = 0; i < nums.length - 2; i++) {
if(nums[i]>0)break;
left = i+1;
right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
set.add(Arrays.asList(nums[i], nums[left], nums[right]));
left++;
right--;
} else if (sum<0) {
left++;
}else {
right--;
}
}
}
return new ArrayList<>(set);
List<List
Arrays.sort(nums);
for (int i = 0; i < nums.length-2; i++) {
if(nums[i]>0) break;
if (i > 0 && nums[i] == nums[i-1]) continue;
int left=i+1;
int right=nums.length-1;
while (left < right) {
int sum=nums[i]+nums[left]+nums[right];
if (sum == 0) {
res.add(Arrays.asList(nums[i],nums[left],nums[right]));
while (left < right && nums[left] == nums[left+1]) left++;
while (left < right && nums[right] == nums[right-1]) right--;
left++;
right--;
}else if (sum < 0) {
left++;
}else {
right--;
}
}
}
return res;
}
3.赎金信
算法思路:利用数组统计字符出现次数。先遍历杂志字符串,统计每个字符出现的次数;再遍历赎金信字符串,对于每个字符,将其在数组中的对应次数减一,若减一后小于零,则说明杂志中没有足够的该字符来构造赎金信,返回 false,否则遍历结束后返回 true。
时间复杂度:O(m+n),其中 m 和 n 分别是赎金信和杂志字符串的长度。
空间复杂度:O(1),使用了长度为 26 的数组来统计字符出现次数。
//383. 赎金信
public boolean canConstruct(String ransomNote, String magazine) {
int[] ransom = new int[26];
for (char c : magazine.toCharArray()) {
ransom[c - 'a']++;
}
for (char c : ransomNote.toCharArray()) {
if(--ransom[c - 'a']<0) return false;
}
return true;
}
4.四数相加 II
算法思路:两两配对处理。先计算 nums1 和 nums2 中所有可能的和,并存储在哈希表中,键为和,值为该和出现的次数;再计算 nums3 和 nums4 中所有可能的和,对于每个和,检查其负值是否在哈希表中,若在,则将对应的次数累加到结果中。
时间复杂度:O(n^2),其中 n 是数组长度。计算两个数组中所有可能的和的时间复杂度为 O(n^2)。
空间复杂度:O(n^2),最坏情况下,哈希表中需要存储 n^2 个不同的和。
//第454题.四数相加II
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
//效率超低,不能通过,O(n^4)
/int count = 0;
List<List
for (int i = 0; i < nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
for (int k = 0; k < nums3.length; k++) {
for (int l = 0; l < nums4.length; l++) {
List
list1.add(i);
list1.add(j);
list1.add(k);
list1.add(l);
list.add(list1);
}
}
}
}
for (List
if(nums1[integers.get(0)]+nums2[integers.get(1)]+nums3[integers.get(2)]+nums4[integers.get(3)]==0){
count++;
}
}
return count;
//两两配对处理,有效降低时间复杂度
Map<Integer, Integer> map = new HashMap<>();
int count = 0;
// 计算 nums1 和 nums2 中所有可能的和,并存储在哈希表中
for (int i : nums1) {
for (int j : nums2) {
int sum = i + j;
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
}
// 计算 nums3 和 nums4 中所有可能的和,并检查它们的负值是否在哈希表中
for (int k : nums3) {
for (int l : nums4) {
int sum = k + l;
count += map.getOrDefault(-sum, 0);
}
}
return count;
}

浙公网安备 33010602011771号