3Sum

这道题其实没有做出来,是看别人的代码整理了一下思路。

想法一:这道题的标签就是two pionters,能不能用双指针来做呢?当然是可以的!用双指针的话,首先需要对数组排序。因为这道题是三个数相加等于0,我们可以按照顺序从数组中取出一个数x,再从剩下的数组中找出两个数,它们的和要等于-x。具体实现步骤如下:

1.设置一个指针i,它从数组的第一个数开始遍历,一直到数组的倒数第三个数。每次都将nums[i]的相反数作为target(因为是三个数的和,所以取到倒数第三个数)

2.每次得到一个target之后,设置一个初值为i+1的左指针left,一个初值为nums.length-1的右指针right。目的是为了在剩余的数组中找到两个数,它们的和要等于target。

因此,每一次都设置一个变量dit=target-nums[left],如果存在nums[right]==dit,就说明能够在剩余的数组元素中找到两个数,和为target。因此也就找到了满足三个数的和为

0的元素。

当left<right时,判断:

(1)如果nums[right]==dit,找到了满足三个数的和为0的元素。此时,left++,right--,继续查看是否还存在两个数和等于target的元素。

(2)如果nums[right]<dit,说明nums[left]太小了,以至于在数组中找不到足够大的数使nums[left]和nums[right]的和等于targer。此时,left++

(3)如果nums[right]>dit,说明nums[right]太大了,right--

3.直到left>=right时,就找到了剩余数组元素中能够满足两个数之和等于target的全部元素。因此,重复1、2步,最终会找到全部满足三个数之和等于零的元素。

问题:这样找到的结果有可能会重复,比如,对于数组元素为-1,-1,0,1的数组来说,它只需要返回[-1,0,1],但是由于上述步骤没有剔除重复元素,会导致找出了两组[-1,0,1],但其实这两组数据中的元素完全一样。

如何解决呢?其实要想找出的符合要求的两组数不完全一样,只需要保证其中两个数不完全一样就行了。我们可以在一开始得到target的时候就进行判断,如果i>0并且nums[i]==nums[i-1],就说明这一次得出的target也会是一样的,就没有必要继续第二步了,直接让i指针继续向前移动;但是只保证每次的target是不一样的仍然不能保证剔除了重复性,因此,可以在满足a=nums[left],b=nums[right],a+b==target的时候,left指针向后移动到nums[left]不等于a为止,相应的,right指针也移动到nums[right]不等于b为止。这样,就能够保证找出来的n组满足三个数和为零的数全部不完全一样。

代码如下:

class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>>result=new ArrayList<List<Integer>>();
Arrays.sort(nums);//先使nums从小到大排序方便接下来的操作
for(int i=0;i<nums.length-2;i++){//对于前n-2个数据,将其相反数作为target
if(i>0&&nums[i]==nums[i-1])//nums[i]已经被用来判断过了
continue;
int target=-nums[i];
int left=i+1;//左指针
int right=nums.length-1;//右指针
while(left<right){
int dit=target-nums[left];//另一数据要等于target-nums[left]
if(nums[right]==dit){//说明nums[i],nums[left],nums[right]三者和为0
result.add(Arrays.asList(nums[i],nums[left],nums[right]));
//target不变,而要让三者的和仍然等于0,那么就需要一个比
//原来的nums[left]更大的数和一个比原来的nums[right]更小的数
left++;
right--;
//为了防止添加重复元素,要移动left和right,直到找到和原来添加进
//了result中的nums[left](或nums[right])不同的数为止
while(left<right&&nums[left]==nums[left-1])
left++;
while(left<right&&nums[right]==nums[right+1])
right--;
}
else if(nums[right]<dit){//说明nums[left]太小了
left++;
}else{
right--;
}
}
}
return result;
}
}

思路二:首先将数组排序,然后用一个指针i遍历,从第一个数遍历到倒数第三个数,再用一个指针j进行遍历,j的初值为i+1,最大不能超过nums.length-1。这个方法充分利用了java中HashMap当键值相同时,新加入的value会覆盖原有的value的特性,从而可以避免加入重复的元素。本质上和双指针的效果是一样的,但是速度要慢很多。

代码如下:

class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> r=new ArrayList<>();
HashMap<Integer,Integer> index=new HashMap<>();
for(int i=0;i<nums.length;i++)
index.put(nums[i], i);
for(int i=0;i<nums.length-2;i++){
for(int j=i+1;j<nums.length-1;j++){
int target=0-nums[i]-nums[j];
if(index.containsKey(target) && index.get(target)>j){//不能取到j之前的数
r.add(Arrays.asList(nums[i], nums[j], target));
j=index.get(nums[j]);//因为HashMap特性,如果存在和nums[j]相等但是
//其索引值大于j,比如说索引值为k,k>j时,
//那么nums[j]键的value就会被k覆盖
}
i=index.get(nums[i]);//与j=index.get(nums[j])的原理相同
}
}
return r;
}
}

 

posted @ 2019-11-05 20:06  xbc121  阅读(114)  评论(0编辑  收藏  举报