三数之和 --双指针解法
题目:
一般解法
三重循坏,分别取三个数,查看和是否为0即可,是就加入集合,不是进入下次迭代。
双指针解法
思路:
-
本题是解决集合内三数之和的问题,可以将问题简化为求两数之和的问题。只需要在第一次循环确定第一个数,二三次循环的数的和为总和减去第一个数即可。由于结果不唯一,且不能重复,我们可以先对数组进行排序,再用双指针操作数组得到所有和为目标值的序列。
-
解决双指针问题三种常用思想:
- 左右指针:需要两个指针,一个指向开头,一个指向末尾,然后向中间遍历,直到满足条件或者两个指针相遇
- 快慢指针:需要两个指针,开始都指向开头,根据条件不同,快指针走得快,慢指针走的慢,直到满足条件或者快指针走到结尾
- 后序指针:常规指针操作是从前向后便利,对于合并和替换类型题,防止之前的数据被覆盖,双指针需从后向前便利
- 记忆口诀:左右指针中间夹,快慢指针走到头,后序指针往回走
- 这里使用的是左右指针
-
实现代码:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
qsort(nums, 0, nums.length-1); //为了防止重复结果,故对初始数组进行了排序
List<List<Integer>> ret = new ArrayList<>();
List<Integer> list = new ArrayList<>();
for(int i=0;i<nums.length-2;i++){
if(nums[0] > 0) //最小的都为正数,加起来一定没结果,排除
break;
if(i!=0 && nums[i] == nums[i-1]) //对相同的多个数,只用处理第一个就可以了,其余可以略过
continue;
List<List<Integer>> twoSumList = twoSum(nums, i+1, -1*nums[i]); //在余下的数组中查找是否有和为-nums[i]的两个数,返回结果列表
for(List<Integer> alist:twoSumList){ //将两数之和计算得到的列表的数据加入到三数之和结果表中
ret.add(new ArrayList<>(Arrays.asList(nums[i], nums[alist.get(0)], nums[alist.get(1)])));
}
}
return ret;
}
/**
*求一个数组后半段指定区域和为target的两个数的序列
*
*/
public List<List<Integer>> twoSum(int[] nums, int left, int target){
int i=left;
int j=nums.length-1;
List<List<Integer>> ret = new ArrayList<>();
while(i<j){ //当左右指针相遇时返回,反之没有相遇时一直循环
if(nums[i]+nums[j] == target){ //如果找到了和为target的两个数
ret.add(new ArrayList<>(Arrays.asList(i, j)));
//找到数后,不管怎么样都要先至少指针移动一下
i++; //左指针右移
j--; //右指针左移
while(i<j && nums[i] == nums[i-1]){ //去重
i++;
}
while(i<j && nums[j] == nums[j+1]){ //去重
j--;
}
}else if(nums[i]+nums[j] < target){ //和小于target,左指针右移
i++;
}else{ //和大于target,右指针左移
j--;
}
}
return ret;
}
//快速排序
public void qsort(int[] nums, int low, int high){
if(low>=high)
return;
int key=nums[low];
int i=low;
int j=high+1;
while(true){
while(nums[++i] < key){
if(i==high)
break;
}
while(nums[--j] > key){
if(j==low)
break;
}
if(i>=j)
break;
int tmp = nums[j];
nums[j] = nums[i];
nums[i] = tmp;
}
int tmp = nums[j];
nums[j] = key;
nums[low] = tmp;
qsort(nums, low, j-1);
qsort(nums, j+1, high);
}
}