题目链接:454. 四数相加 II
这个题注意它只需要给出次数,而不是元组。所以我们可以分治。将前两个数组的加和情况使用map存储起来,再将后两个数组的加和情况使用map存储起来,key存和,value存出现的次数。得到两个map之后,我们遍历其中一个map, 看另一个map中是否有和为0的情况,有就相加value,最后接可以得出答案。
在这种思路的基础上,我们可以继续优化代码,例如我们在统计后两个数组的同时,就已经可以将需要的和在前两个数组的map中找出来,然后把次数进行相加。
代码如下:
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
Map<Integer,Integer> records = new HashMap<>();
for(int i = 0;i < nums1.length;i++) {
for(int j = 0;j < nums2.length;j++) {
records.put(nums1[i] + nums2[j],records.getOrDefault(nums1[i] + nums2[j],0) + 1);
}
}
int result = 0;
for(int i = 0;i < nums3.length;i++) {
for(int j = 0;j < nums4.length;j++) {
Integer count = records.get(0 - nums3[i] - nums4[j]);
if(count != null) result += count;
}
}
return result;
}
}
题目链接:383. 赎金信
二十六个字母,计数,第一反应就要想到数组。解题思路如下:
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
int[] records = new int[26];
for (int i = 0; i < ransomNote.length(); i++) records[ransomNote.charAt(i) - 'a']++;
for (int i = 0; i < magazine.length(); i++) records[magazine.charAt(i) - 'a']--;
for (int i = 0; i < records.length; i++) if(records[i] > 0 ) return false;
return true;
}
}
题目链接:15. 三数之和
解题思路:
看到这一题我们肯定会不自觉地拿它和两数之和进行比较,我们是否能借助两数之和的思想来完成这一题?首先我们回顾一下两数之和的思想。在两数之和中,我们是遍历数组,每遍历一个元素,就看target - 该元素 是否已经出现过(也就是是否在hash表中),如果在直接返回,如果不在就把这个元素添加到hash表中,代表该元素出现过,为后面的元素服务。
在三数相加中,我们尝试沿用这种思路(先不直接到位,后面还会添加新逻辑):
代码如下:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
int need = -nums[i] - nums[j];
if (set.contains(need)) {
result.add(Arrays.asList(nums[i], nums[j], need));
} else {
set.add(nums[j]);
}
}
set.clear();
}
return result;
}
}
这个代码完成了基本的功能但是还差本题的一个重点那就是去重。
就比如题目的这个用例:[-1,0,1,2,-1,-4]
如下两种情况就会重复:
我们可以尝试排序解决这个问题:
排序之后还是这个用例就变为:[-4,-1,-1,0,1,2]
外层循环在固定到两个-1的时候肯定会发生重复,所以我们可以添加一个条件,外层循环固定的数字和上一次相同时直接跳过:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for (int j = i + 1; j < nums.length; j++) {
int need = -nums[i] - nums[j];
if (set.contains(need)) {
result.add(Arrays.asList(nums[i], nums[j], need));
} else {
set.add(nums[j]);
}
}
set.clear();
}
return result;
}
}
原因就是当内层的两数相加满足之后,内层的下一次循环还是相同的数,那么相当于把这一组答案又加了一遍,那么我们针对这个情况进行改进:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
Integer flag = null;
for (int j = i + 1; j < nums.length; j++) {
if(Integer.valueOf(nums[j]).equals(flag)) continue;
flag = null;
int need = -nums[i] - nums[j];
if (set.contains(need)) {
result.add(Arrays.asList(nums[i], nums[j], need));
flag = nums[j];
} else {
set.add(nums[j]);
}
}
set.clear();
}
return result;
}
}
我们最后总结一下这道题:
这道题在沿用了我们前面两数之和的思想之后,会存在一个去重问题:
- 外层重复:也就是当外层循环固定的数字和上一次相同时此次循环直接跳过
- 内层重复:也就是当内层的两数相加满足之后,内层的下一次循环还是相同的数。这个时候我们可以在每次三数之和满足条件之后,将内层此次的值记录一下,相邻的下一次循环与此次的值一样就跳过此次内循环。
当然此题也可以使用双指针法来做,逻辑上更为简单,代码在此处不多做赘述。
题目链接:18. 四数之和
这题使用双指针法进行解题:
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
for(int i = 0;i < nums.length;i++) {
for(int j = i + 1;j < nums.length;j++) {
int left = j + 1;
int right = nums.length - 1;
while(left < right && left < nums.length) {
int sum = 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--]));
}
}
}
}
return result;
}
}
接下来进行去重:
发生这种情况就是因为外层的双层循环固定的两个数字重复,我们添加去重的代码:
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
for(int i = 0;i < nums.length;i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
for(int j = i + 1;j < nums.length;j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
int left = j + 1;
int right = nums.length - 1;
while(left < right && left < nums.length) {
int sum = 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--]));
}
}
}
}
return result;
}
}
这个去重的逻辑就是:
if (i > 0 && nums[i] == nums[i - 1]) continue;
因为数组是按大小排序的,如果第一个固定的数不变,那么其他三个数不管怎么样,都只会与target相等(这个情况已经存在需要去重)或者比target大,所以这个循环可以直接跳过if (j > i + 1 && nums[j] == nums[j - 1]) continue;
逻辑类似执行之后有一个测试样例还是没过:
造成这个情况的原因是因为,左右指针同时内缩的时候如果元素不变也会发生重复,我们继续往里面添加去重逻辑:
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
for(int i = 0;i < nums.length;i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
for(int j = i + 1;j < nums.length;j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
int left = j + 1;
int right = nums.length - 1;
while(left < right && left < nums.length) {
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 (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
left++;
right--;
}
}
}
}
return result;
}
}