代码随想录Day7
题目列表
- 454.四数相加II(LeetCode)
- 383.赎金信(LeetCode)
- 15.三数之和(LeetCode)
- 18.四数之和(LeetCode)
解题过程
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]
输出:2
解释:
两个元组如下:
- (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
- (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
示例 2:
输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1
解题思路
根据示例可以看出这些元组的值允许重复。同时也要维护四个元素的和与个数的关系,采用哈希表 HashMap。
要在四个数组中各选一个数字加起来,那么就可以先处理前两个数组,算出所有的两数之和以及它们各自出现的次数,放到哈希表中,然后处理后两个数组,算这两个数组两数之和的同时判断 0 - nums3[i] - nums4[j] 在HashMap 的 key 中是否出现过,如果是,那么累计个数加上出现的次数,直到结束。
注意事项
1.元组允许重复
也就是说不用关心是具体的数值是多少,只要它们符合条件即可,这同时也意味着我们需要记录符合条件的 sum 出现的具体次数。
代码展示
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
int res = 0;
Map<Integer,Integer> map = new HashMap<>();
for(int i : nums1){
for(int j : nums2){
int sum = i+j;
map.put(sum,map.getOrDefault(sum,0)+1);
}
}
for(int i : nums3){
for(int j : nums4){
int find = 0 - (i+j);
res += map.getOrDefault(find,0);
}
}
return res;
}
}
383.赎金信
题目描述
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
示例 1:
输入:ransomNote = "a", magazine = "b"
输出:false
示例 2:
输入:ransomNote = "aa", magazine = "ab"
输出:false
示例 3:
输入:ransomNote = "aa", magazine = "aab"
输出:true
解题思路
每个字母都只用一次,ransomNote里有的字母,有几个字母,magazine必须包含。
我想的是先使用哈希数组记录ransomNote中出现的字母以及次数,再遍历magazine进行减1,最后如果这个数组所有元素都不大于零,就说明满足要求。
代码展示
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
if(ransomNote.length() > magazine.length()){
return false;
}
int [] record = new int [26];
for(char c : ransomNote.toCharArray()){
record [c-'a'] += 1;
}
for(char c : magazine.toCharArray()){
record [c-'a'] -= 1;
}
for(int i : record){
if(i > 0){
return false;
}
}
return true;
}
}
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]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
解题思路
预先看了网站的题解,这道题的难点在于“去重”,在结果集中不允许有重复的三元组。
如果先用哈希法得出所有相加为 0 的三元组再遍历去重,就会超时,哈希法不太适用。题解提供了一个使用双指针解法的思路:
- 首先要对数组进行排序(这道题不涉及记录下标,可以排序),数组有序是“双指针无遗漏地移动”以及“更便利地去重”的前提条件。
- 然后定义一层循环,i 从 0 开始,并且定义两个指针 left 和 right, left 初始指向 i + 1 的位置,right 初始指向 n - 1 的位置。
- 接下来进行寻找三元组以及去重:
- 先看如何获取到有效的三元组,在每一次 for 循环中,先计算 nums[i] + nums[left] + nums[right] 的值,如果 == 0 则记录在结果集中,如果 > 0,则需要 right 指针向前移动,如果 < 0,则需要 left 指针向后移动,一直到 right > left 这个条件不再满足,这一次循环就算结束了。
- 在寻找有效的解的同时要进行去重操作。
首先是第一位的去重,也就是 i 指示的那个数,注意去重是不允许有重复的三元组,三元组中是可以有重复的元素的。因此去重条件应该是(nums[i]==nums[i-1]),这样就可以保证在每次大循环中无重复三元组,这得利于数组有序,因为后面的数字只大不小,所以不用担心跳过 nums[i] 后第二位第三位又出现与 nums[i] 相等的数。
再注意第二位与第三位的去重,这两个位置的去重只发生在一次循环的内部,假如说这个数组的第一个数字是-1,剩下五个0和五个1,假如不干涉,到时候就会记录五个重复的三元组【-1,0,1】,这显然是不允许的,正确做法是要对left和right指示的数字进行判断去重,相等就要跳过。
注意事项
去重应该在判断三数之和是否等于0之后
设想一个数组全是 0 ,假如先去重,那么会一直去重,直到循环结束,这样就无法得到一个有效解【0,0,0】。首位去重的条件里有 i > 0 一方面是防止越界,另一方面也保证了先判断一次,再进行去重。而第二位和第三位的去重语句要在记录语句之后,防止上述情况发生。
代码展示
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
for(int i = 0;i < nums.length; i++){
int left = i + 1;
int right = nums.length - 1;
//第一个位置去重
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
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(right > left && nums[left] == nums[left + 1]){
left++;
}
while(right > left && nums[right] == nums[right - 1]){
right--;
}
//再移一位才是不重复的数值
left++;
right--;
}else if(sum > 0){
right--;
}else{
left++;
}
}
}
return res;
}
}
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
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
提示:
1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109
解题思路
与三数之和类似,只是现在变成四个数,需要一个双层的for循环,边界条件要稍作修改。此外,三数之和与四数之和在循环中都可以剪枝提升效率,上一个题因为 target = 0 是确定的,所以直接判断当前数字是否大于0就可以了。但这道题不可以,因为target是随机的,如果简单地写为当前数字大于target,可能会遗漏情况(例如 target = -5, nums[0] = -4),nums[1] = -1)
注意事项
剪枝条件
如果当前数字是正数且已超过目标值,此时后续所有组合的四数之和必然大于目标值,因此可以提前终止循环.
代码展示
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
for(int k = 0; k < nums.length; k++){
//剪枝
if(nums[k] > target && nums[k] >= 0){
break;
}
//第一个位置去重
if (k > 0 && nums[k] == nums[k - 1]) {
continue;
}
for(int i = k + 1;i < nums.length; i++){
//剪枝
if((nums[k] + nums[i]) > target && nums[k] + nums[i] >= 0){
break;
}
//第二个位置去重
if (i > k + 1 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.length - 1;
while(left < right){
int sum = nums[k] + nums[i] + nums[left] + nums[right];
if(sum < target){
left++;
}
else if(sum > target){
right--;
}
else{
res.add(Arrays.asList(nums[k], nums[i], nums[left], nums[right]));
while(right > left && nums[left] == nums[left + 1]){
left++;
}
while(right > left && nums[right] == nums[right - 1]){
right--;
}
//再移一位才是不重复的数值
left++;
right--;
}
}
}
}
return res;
}
}