算法题中有些题目可以利用双指针的技巧来巧妙的解题。双指针一般分两种:

  1. 快慢指针:从同一个起点出发,两个指针的速度不同,通常用于链表中判断是否有环等。
  2. 左右指针:从数据结构(一般为数组)的两端出发,向中间移动,直到两个指针撞上。

本文主要记录一下LintCode中左右指针相关的题目。

LintCode 587:两数之和-不同组成

https://www.lintcode.com/problem/two-sum-unique-pairs/description

描述

给一整数数组, 找到数组中有多少组 不同的元素对 有相同的和, 且和为给出的 target 值, 返回对数.

样例

  1. 输入: nums = [1,1,2,45,46,46], target = 47
  2. 输出: 2
  3. 解释:
  4. 1 + 46 = 47
  5. 2 + 45 = 47
  6. 输入: nums = [1,1], target = 2
  7. 输出: 1
  8. 解释:
  9. 1 + 1 = 2

解题思路

给数组按从小到大排序,left指针从左除服,right指针从右出发,如果两数相加等于target,对数加一,然后left指针向右移动一位,如果和上一个数相等,继续向右移动,直至不相等或者与right碰撞,right指针向左移动一位,如果和上一个数相等,继续向左移动,直至不相等或者与left碰撞;碰撞则代表查找结束。如果两数相加不等于target,如果大于target,则表明两数过大,这时right指针向左移动一位;如果小于target,则表明两数过小,这时left指针向右移动一位。结束条件为left和right指针碰撞或left大于right。时间复杂度O(n)

AC代码

  1. public int twoSum6(int[] nums, int target) {
  2. // 数组为空或只有一个数直接返回0
  3. if (nums == null || nums.length < 2)
  4. return 0;
  5. // 满足条件的个数
  6. int ans = 0;
  7. // 给数组排序
  8. Arrays.sort(nums);
  9. // 左右指针起点
  10. int left = 0, right = nums.length - 1;
  11. while (left < right) {
  12. if (nums[left] + nums[right] == target) {
  13. // 等于target,对数加1,左指针右移,右指针左移
  14. ans++;
  15. left++;
  16. right--;
  17. // 去重
  18. while (left < right && nums[left] == nums[left - 1]) {
  19. left++;
  20. }
  21. // 去重
  22. while (left < right && nums[right] == nums[right +1]) {
  23. right--;
  24. }
  25. } else if (nums[left] + nums[right] > target) {
  26. // 大于target,右指针左移
  27. right--;
  28. } else {
  29. // 小于target,左指针右移
  30. left++;
  31. }
  32. }
  33. // 返回答案
  34. return ans;
  35. }

LintCode 148:颜色分类

https://www.lintcode.com/problem/sort-colors/description

描述

给定一个包含红,白,蓝且长度为 n 的数组,将数组元素进行分类使相同颜色的元素相邻,并按照红、白、蓝的顺序进行排序。
我们可以使用整数 0,1 和 2 分别代表红,白,蓝。
想出一个仅使用常数级额外空间复杂度且只扫描遍历一遍数组的算法

样例

  1. 输入 : [1, 0, 1, 2]
  2. 输出 : [0, 1, 1, 2]

解题思路

红颜色(0)全部放在左边,蓝颜色(2)全部放在右边,当我们把所有的0和2放在正确的位置时,颜色也就分好了类。所以可以用left指针表示当前0应该要放置的位置,right指针表示当前要放在的位置,然后遍历数组,如果为0将其与left指针的数互换,left指针右移;如果为2将其与右指针的数互换,right指针左移;为1不作处理;直至数组遍历完或者颜色已经分类完成。时间复杂度O(n)

AC代码

  1. public void sortColors(int[] nums) {
  2. // 红颜色从0位置开始放,蓝颜色从最后的位置开始放。从0开始扫描数组
  3. int left = 0, right = nums.length - 1, start = 0;
  4. // 当扫描指针遇到了right指针,表示所有颜色已经分类完毕
  5. while (start <= right) {
  6. if (nums[start] == 0) {
  7. // 当前为红颜色则与left指针的数交换
  8. if (start == left) {
  9. // 如果当前扫描指针即为left指针,双双加一,继续扫描
  10. start++;
  11. left++;
  12. // 已经处理完毕,不用再交换
  13. continue;
  14. }
  15. // 与left指针交换,left指针已经放置好了红颜色,要向右移动一位
  16. int temp = nums[start];
  17. nums[start] = nums[left];
  18. nums[left++] = temp;
  19. } else if (nums[start] == 2) {
  20. // 当前为蓝颜色则与right指针的数交换
  21. if (start == right) {
  22. // 扫描指针和right指针一致则表示颜色分类已经完毕
  23. break;
  24. }
  25. right指针交换,right指针放置好了蓝颜色,要向左移动一位
  26. int temp = nums[start];
  27. nums[start] = nums[right];
  28. nums[right--] = temp;
  29. } else {
  30. // 为1的话不用处理,扫描指针向右移动一位
  31. start++;
  32. }
  33. }
  34. }

LintCode 57:三树之和

https://www.lintcode.com/problem/3sum/description

描述

给出一个有n个整数的数组S,在S中找到三个整数a, b, c,找到所有使得a + b + c = 0的三元组。
在三元组(a, b, c),要求a <= b <= c。
结果不能包含重复的三元组。

样例

  1. 输入:[2,7,11,15]
  2. 输出:[]
  3. 输入:[-1,0,1,2,-1,-4]
  4. 输出:[[-1, 0, 1],[-1, -1, 2]]

解题思路

前面做了两数之和的题目,三数之和可以转换成两数之和,将数组按从小到大排序,遍历数组,假设当前数arr[i] = a,题目就可以转化成在剩下的数中找到两个数之和为-a,这样就将三数之和转化成了两数之和(解题思路往上翻)。时间复杂度O(n**2**)

AC代码

  1. public List<List<Integer>> threeSum(int[] numbers) {
  2. List<List<Integer>> list = new LinkedList<>();
  3. // 排序
  4. Arrays.sort(numbers);
  5. // 遍历数组
  6. int i = 0;
  7. while (i < numbers.length - 2) {
  8. // 转化为两数之和
  9. int j = i + 1, k = numbers.length - 1;
  10. while (j < k) {
  11. if (numbers[i] + numbers[j] + numbers[k] == 0) {
  12. List<Integer> l = new LinkedList<>();
  13. l.add(numbers[i]);
  14. l.add(numbers[j]);
  15. l.add(numbers[k]);
  16. list.add(l);
  17. j++;
  18. k--;
  19. while (j < k && numbers[j] == numbers[j - 1]) {
  20. j++;
  21. }
  22. while (j < k && numbers[k] == numbers[k + 1]) {
  23. k--;
  24. }
  25. } else if (numbers[i] + numbers[j] + numbers[k] > 0) {
  26. k--;
  27. } else {
  28. j++;
  29. }
  30. }
  31. i++;
  32. // 去重
  33. while (i < numbers.length - 2 && numbers[i] == numbers[i - 1]) {
  34. i++;
  35. }
  36. }
  37. return list;
  38. }

LintCode 31:数组划分

https://www.lintcode.com/problem/partition-array/description

描述

给出一个整数数组 nums 和一个整数 k。划分数组(即移动数组 nums 中的元素),使得:

  • 所有小于k的元素移到左边
  • 所有大于等于k的元素移到右边

返回数组划分的位置,即数组中第一个位置 i,满足 nums[i] 大于等于 k
你应该真正的划分数组 nums,而不仅仅只是计算比 k 小的整数数,如果数组 nums 中的所有元素都比 k 小,则返回 nums.length。

样例

  1. 输入:[],9
  2. 输出:0
  3. 输入:
  4. [3,2,2,1],2
  5. 输出:1
  6. 解释:
  7. 真实的数组为[1,2,2,3].所以返回 1

解题思路

这题怎么看怎么跟快速排序中的partition一样。思路和颜色分类差不多,left指针表示小于k的数放置的位置,right指针表示大于等于k的数放置的位置,遍历数组,小于k,与left指针交换,left指针右移;大于等于k,与right指针交换,right指针左移。最后left指针便是第一个大于等于k的位置。

AC代码

  1. public int partitionArray(int[] nums, int k) {
  2. // 设置左右指针
  3. int left = 0, right = nums.length - 1;
  4. while (left <= right) {
  5. // 当前数小于k,left指针右移
  6. while (left <= right && nums[left] < k) {
  7. left++;
  8. }
  9. // 当前数大于等于k,right指针左移
  10. while (left <= right && nums[right] >= k) {
  11. right--;
  12. }
  13. // 交换
  14. if (left <= right) {
  15. int temp = nums[left];
  16. nums[left] = nums[right];
  17. nums[right] = temp;
  18. left++;
  19. right--;
  20. }
  21. }
  22. return left;
  23. }