1、题目1: 454-四数相加II
类型:中等、数组、哈希
给你四个整数数组
nums1
、nums2
、nums3
和nums4
,数组长度都是n
,请你计算有多少个元组(i, j, k, l)
能满足:
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例 1 :
输入: nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出: 2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (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
思路:
-
假设有A、B、C、D 是不是可以只遍历两个数组AB,获取到各个位置的和集合,然后再遍历数组C D,通过 0-AB集合的值在 数组CD中存在,那就找到了符合规则的四元组。
-
需要统计某个值出现的次数,所以用map合适,value存储出现的次数
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
int count = 0;
HashMap<Integer, Integer> map = new HashMap<>();
// 统计两个数组中的元素之和,同时统计出现的次数,放入 map
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) {
if(map.containsKey(0-i-j)){
count +=map.get(0-i-j);
}
}
}
return count;
}
2、题目2: 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
代码:
public boolean canConstruct(String ransomNote, String magazine) {
HashMap<Character,Integer> map = new HashMap<>();
for (char c : magazine.toCharArray()) {
map.put(c,map.getOrDefault(c,0)+1);
}
for (char c : ransomNote.toCharArray()) {
if(map.containsKey(c)){
Integer i = map.get(c);
if(i>1){
map.put(c,--i);
}else{
map.remove(c);
}
}else{
return false;
}
}
return true;
}
3、题目3: 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 。
思路:
- 两层 for 循环就可以确定 a 和 b 的值,那么就可以使用 0-a-b 来判断是否在哈希中出现过,但题目中说不可以包含重复的三元组。这个就是本题的难点
本题使用哈希法不合适,程序的执行时间依然比较长。
而双指针法要比哈希法更高效些。双指针法一定要排序,不在意下标的时候可以用。
-
1、拿 nums 数组来举例,首先将数组排序(一定),因此left++,right-- 才能不断的逼近目标值
-
2、一层 for 循环,i 从下标 0 的地方开始,left 定义在 i+1 的位置,right 在数组结尾的位置
-
3、如何移动 right、left,如果 nums[i] + nums[left] + nums[right] >0 就说明此时三数之和大了,right 左移,如果小了,left 右移动,直到 left 和 right 相遇。
-
4、nums[i] >0,直接 break 跳出,因为此时3个元素都大于0,不可能找到结果
-
5、当 i >0 且 nums[i] == nums[i-1] 时,跳过此元素 nums[i] ;因为已经将 nums[i-1] 的所有组合加入到结果中了,本次双指针搜索只会得到重复组合。 (注意不能是判断 nums[i] == nums[i+1], 因为可能会把结果集丢掉,例如[-1,-1,2] 的时候,就丢了这组结果集)
-
6、left、right 分别在两端,当 left < right 循环计算 sum 值= nums[left] + nums[right] + nums[i]
-
当 s<0 时,left+=1 并跳过所有重复的 nums[left];
-
当 s>0 时,right--;并跳过所有重复的 nums[right];
-
当 s=0时,记录组合[i,left,right] 放入 结果集
-
代码:
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
if(nums[i]>0){
return result;
}
// 去重
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){
right--;
}else if(sum<0){
left++;
}else{
result.add(Arrays.asList(nums[i],nums[left],nums[right]));
// 去重逻辑,对 bc 去重
while (right>left && nums[right] == nums[right-1])
right--;
while (left<right && nums[left] == nums[left+1])
left++;
// 找到答案时,双指针同时收缩,查找其他的可能值
right--;
left++;
}
}
}
return result;
}
4、题目4: 18-四数之和
类型:中等
给你一个由
n
个整数组成的数组nums
,和一个目标值target
。请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
示例 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]]
类似4数之和,双指针解决.
- 三数之和:双指针解法是一层 for 循环 nums[i] 作为确定值,然后根据 left 和 right 下标作为指针查找
- 四数之和:两层 for 循环 nums[i] +nums[j] 作为确定值,依然是双指针。时间复杂度是On 的3次方
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// 剪枝处理
if(nums[i]> target && nums[i] >= 0){
break;
}
// 去重
if(i>0 && nums[i] == nums[i-1] ){
continue;
}
for (int j = i+1; j < nums.length; j++) {
if(nums[i] + nums[j] > target && nums[i]+nums[j] >=0){
break;
}
// 去重
if(j>i+1 && nums[j] == nums[j-1] ){
continue;
}
int left = j+1;
int right = nums.length -1;
while (left<right){
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]));
while (left<right && nums[left] == nums[left+1])
left++;
while (left<right && nums[right] == nums[right-1])
right--;
left++;
right--;
}
}
}
}
return result;
}
5、总结
一般来说哈希表都是用来快速判断一个元素是否出现集合里。
对于哈希表,要知道哈希函数和哈希碰撞在哈希表中的作用。
哈希函数是把传入的key映射到符号表的索引上。
哈希碰撞处理有多个key映射到相同索引上时的情景,处理碰撞的普遍方式是拉链法和线性探测法。
接下来是常见的三种哈希结构:
- 数组
- set(集合)
- map(映射)
同时,不管是在哈希、链表、数组,都不可避免的出现了双指针法,这套方式可以使时间复杂度下降一个等级。
三数之和、四数之和等不可重复的的元素都可以使用方法进行计算。