454.四数相加II
题目描述:
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 1 。
例如:
输入:
- A = [ 1, 2]
- B = [-2,-1]
- C = [-1, 2]
- D = [ 0, 2]
输出:
2
解释:
两个元组如下:
- (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
- (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
思路:
本题乍眼一看好像和0015.三数之和 (opens new window),0018.四数之和 (opens new window)差不多,其实差很多。
本题是使用哈希法的经典题目,而0015.三数之和 (opens new window),0018.四数之和 (opens new window)并不合适使用哈希法,因为三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理。
而这道题目是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于题目18. 四数之和,题目15.三数之和,还是简单了不少!
如果本题想难度升级:就是给出一个数组(而不是四个数组),在这里找出四个元素相加等于0,答案中不可以包含重复的四元组,大家可以思考一下,后续的文章我也会讲到的。
本题解题步骤:
- 首先定义 一个map,key放a和b两数之和,value 放a和b两数之和出现的次数。
- 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
- 定义int变量res,用来统计 a+b+c+d = 0 出现的次数。
- 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
- 最后返回统计值 res就可以
Java代码如下
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
// 定义一个hashmap
Map<Integer,Integer> map1 = new HashMap<>();
// 将A+B集合的数据进行for循环,数据存入到map中,key放入相加的数据和,value放入出现的次数
// 数据长度默认为1,当出现了重复和时,value加一在进行修改key对应的值.
for(int a:nums1){
for(int b :nums2){
int result = map1.getOrDefault(a+b,0);
map1.put(a+b,++result);
}
}
int count =0;
int res =0;
// 同理遍历numse和nums4,因为返回的是结果0,所以使用0-a-b的值去判断map1有没有,有就返回map1对应的value进行累加。
for(int a:nums3){
for(int b :nums4){
count = 0-a-b;
if(map1.containsKey(count)){
res += map1.get(count);
}
}
}
return res;
}
}
383. 赎金信
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。
(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。)
注意:
你可以假设两个字符串均只含有小写字母。
canConstruct("a", "b") -> false
canConstruct("aa", "ab") -> false
canConstruct("aa", "aab") -> true
思路:
这题其实和242有效异味字符串是一样的,但是这里的区别在于,杂志字符串中的每个字符只能在赎金信字符串中使用一次,所以我们应该遍历杂志表进行加加操作,同时赎金减减操作,最后判断nums数组是否存在负数,如果存在负数,则表明杂志字符串出现的字符数据没有赎金字符串多,返回false,否则,返回true
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
if(ransomNote.length() > magazine.length()||ransomNote == null||magazine==null){
return false;
}
int[] nums = new int[26];
for(int i = 0;i<ransomNote.length();i++){
nums[ransomNote.charAt(i)-'a']--;
}
for(int i = 0;i<magazine.length();i++){
nums[magazine.charAt(i)-'a']++;
};
for(int i =0;i<nums.length;i++){
if(nums[i] <0){
return false;
}
}
return true;
}
}
15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
思路:数据+双指针
首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。
接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
Jaca代码如下:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> lists = new ArrayList<>();
Arrays.sort(nums);
for(int i =0;i<nums.length;i++){
// TODO 排序过后的数据,当第一个字符串大于0的时候,直接跳出循环。
if(nums[0] > 0){
break;
}
// 我们要对第一个已经使用过的数据进行去重,例如-2,-2,-1,-1,0,1,2 --第一个-2已经进行过一次运算了,我们不能把第二个-2也拿进来运算。
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){
// 如果满足要求,那就把当前的I和left和right存入到数组中,
lists.add(Arrays.asList(nums[i],nums[left],nums[right]));
// 要对right去重处理
while (nums[right] == nums[right-1] && right > left){
right--;
}// 对left进行去重处理
while (nums[left] == nums[left+1] && right > left ){
left++;
}
right--;
left++;
} else if (sum >0) {
right--;
}else{
left++;
}
}
}
return lists;
}
}
18. 四数之和
题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例: 给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]
此题重点在于target是一个目标值,有可能为负数。这道题和三数之和(如果不理解,可以看上面那道题,在重新梳理一下)的逻辑相差不大,但是要增加一个for循环,相当与2个for循环+双指针的解法。
在第一个for循环中,我们跳出循环的是当nums[i]>0并且nums[i]>target,我们就跳出循环,
在进行第二个for循环的同时,去重的条件就变成了nums[j] == nums[j - 1] && j - 1 > i。其他逻辑和三数之和是一样的。
判断的逻辑就是i+j+left+right 的和是否和target一样了。
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> list = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
if (nums[i] > 0 && nums[i] > target) {
return list;
}
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] == nums[j - 1] && j - 1 > i) {
continue;
}
int left = j + 1;
int right = nums.length - 1;
while (left < right) {
long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target) {
list.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
while (nums[right] == nums[right - 1] && left < right) {
right--;
}
while (nums[left] == nums[left + 1] && left < right) {
left++;
}
left++;
right--;
} else if (sum > target) {
right--;
} else if (sum < target) {
left++;
}
}
}
}
return list;
}
}