🌻算法,不如说它是一种思考方式🍀
算法专栏: 👉🏻123
一、🌱15. 三数之和
-
题目描述:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足
i != j、i != k 且 j != k
,同时还满足nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。 -
来源:力扣(LeetCode)
-
难度:中等
-
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105 -
示例 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 。
🌴解题
1.哈希法
当我们确定其中两个数 a、b 之后,那么另一个数 c=-(a+b),也因此这题在 O(n2) 的时间复杂度选定 a、b,然后判断 c 是否存在的问题。很容易想到 hash 法~
我们可以先用 HashMap 统计出数字和对应的出现次数:Map<Integer,Integer> numsMap=new HashMap<>();
。
题目要求不能有重复的三元组,则可以考虑使用 HashSet 来存储我们的结果并去重:Set<List<Integer>> ans=new HashSet<>();
,注意到我们 Set 中嵌套的 List 是存储每一轮的一个三元组,我们需要对这个集合进行排序,否则不同顺序的重复三元组不会被 HashSet 去重。最后再把 Set 转换为 List 返回。
- Code:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Set<List<Integer>> ans=new HashSet<>();
Map<Integer,Integer> numsMap=new HashMap<>();
for (int i = 0; i < nums.length; i++) {//添加到Map,元素 - 个数
if(numsMap.containsKey(nums[i])){
numsMap.put(nums[i],numsMap.get(nums[i])+1);
}else{
numsMap.put(nums[i], 1);
}
}
for (int i = 0; i < nums.length; i++) {
for (int j = i+1; j < nums.length; j++) {
//nums[i] nums[j] -(nums[i] + nums[j])
if(numsMap.containsKey(-(nums[i] + nums[j]))){
if((nums[i] == -(nums[i] + nums[j]))||(nums[j] == -(nums[i] + nums[j]))){
if(nums[i] == nums[j]){
if(numsMap.get(nums[i])<3)
continue;//三个数相同,但是没有3个
}
else{
if(numsMap.get(-(nums[i] + nums[j]))<2)
continue;//2个数相同,但是没有2个
}
}
List<Integer> listi=new LinkedList<>();
listi.add(nums[i]);
listi.add(nums[j]);
listi.add(-(nums[i] + nums[j]));
listi.sort((o1, o2) -> {
if(o1>o2)
return -1;
else return 0;
});
ans.add(listi);
}
}
}
return new ArrayList<>(ans);
}
}
这一方法存在一些(很多)排序的操作,时间复杂度还太高了。我们可以有更好的方法。
2.双指针
如果给我们的一个数组是有序的(小到大),如果存在三元组,那么左边的的必定是负数,右边是正数,我们可以使用 index 进行一次变量,选定左右指针(left、right)指向 index 之后的数组左边和右边,且向对方移动直到相遇。
如果三数和(index、left、right 所指数组元素)大于 0,那么需要减小,即 right 针向左移,三数和小于 0,那么需要增大,即 left 针向右移。
在左右指针的移动过程中会出现下一个元素一样的情况(重复三元组),就需要继续移动。
- Code:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> ans=new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
if(nums[i]>0)//排序后第一个如果大于0,就没有合适的三元组了
break;
if(i>0&&nums[i]==nums[i-1]){//去除重复
continue;
}
int left=i + 1,right= nums.length - 1;
while(left<right){
int sum=nums[i]+nums[left]+nums[right];
if(sum<0){
left++;continue;
}
if(sum>0){
right--;continue;
}
//找到一个三元组
List<Integer> listi=new ArrayList<>();
listi.add(nums[i]);
listi.add(nums[left]);
listi.add(nums[right]);
ans.add(listi);
while (right > left && nums[right] == nums[right - 1])//右边重复数
right--;
while (right > left && nums[left] == nums[left + 1])//左边重复数
left++;
right--;
left++;
}
}
return ans;
}
}
☕物有本末,事有终始,知所先后。🍭
🍎☝☝☝☝☝我的优快云☝☝☝☝☝☝🍓