双指针

由于之前学习过二分查找、滑动窗口的题目,不难看出,使用的方法本质上就是双指针。 这一篇中将学习另外两类技巧:

  1. 快慢指针:主要解决链表中的问题,如判定链表中是否包含环。
  2. 左右指针:主要解决数组(或字符串)中的问题,如二分查找。

快慢指针

快慢指针一般初始化都指向头节点,前进时快指针在前,慢指针在后。 下面通过一些应用题目来加深理解

  1. class Solutions {
  2. /**
  3. * 判定链表中是否含有环
  4. * 遍历链表时通常采用此种方式,但遇到链表中含有环就会陷入死循环
  5. * 经典解法使用两个指针指向链表,快慢指针
  6. * 不含环的情况下,快指针优先遇到null
  7. * 含有环的情况下,快指针最终会超过慢指针一圈,和慢指针相遇。
  8. * ??这里是不是个物理问题,为什么快指针会超过慢指针一圈?
  9. * @param head
  10. * @return
  11. */
  12. boolean traverse(ListNode head) {
  13. while (head != null) {
  14. head = head.next;
  15. }
  16. return false;
  17. }
  18. boolean hasCycle(ListNode head) {
  19. ListNode fast, slow;
  20. fast = slow = head;
  21. while (fast != null && fast.next != null) {
  22. fast = fast.next.next;
  23. slow = slow.next;
  24. if (fast == slow) {
  25. return true;
  26. }
  27. }
  28. return false;
  29. }
  30. /**
  31. * 已知链表中含有环,返回环的起始位置
  32. * @param head
  33. * @return
  34. */
  35. ListNode detectCycle(ListNode head) {
  36. ListNode fast, slow;
  37. fast = slow = head;
  38. while (fast != null && fast.next != null) {
  39. fast = fast.next.next;
  40. slow = slow.next;
  41. if (fast == slow) break;
  42. }
  43. // 无论绕几圈,肯定能遇见
  44. slow = head;
  45. while (slow != fast) {
  46. fast = fast.next;
  47. slow = slow.next;
  48. }
  49. return slow;
  50. }
  51. /**
  52. * 寻找链表中点
  53. * 寻找中点的应用价值在于能够对链表做二分,能够实现二分链表则能够实现归并等操作
  54. *
  55. * @param head
  56. * @return
  57. */
  58. ListNode middleNode(ListNode head) {
  59. ListNode fast, slow;
  60. fast = slow = head;
  61. while (fast != null && fast.next != null) {
  62. fast = fast.next.next;
  63. slow = slow.next;
  64. }
  65. return slow;
  66. }
  67. /**
  68. * 删除链表中倒数第N个节点
  69. * @param head
  70. * @param n
  71. * @return
  72. */
  73. ListNode removeNthFromEnd(ListNode head, int n) {
  74. ListNode fast, slow;
  75. fast = slow = head;
  76. while (n-- > 0) {
  77. // 该题目中链表长度不超过n,未声明则需要判断
  78. fast = fast.next;
  79. }
  80. if (fast == null) {
  81. // 此时倒数第n个节点就是head,删除后即为null
  82. return head.next;
  83. }
  84. while (fast != null && fast.next != null) {
  85. fast = fast.next;
  86. slow = slow.next;
  87. }
  88. // 删除第n个节点
  89. slow.next = slow.next.next;
  90. return head;
  91. }
  92. }

左右指针

左右指针在数组中实际指两个索引值,一般 left = 0, right = nums.length - 1。 常用于二分查找等,下面是部分应用场景示例。

  1. class Solutions {
  2. /**
  3. * 二分查找是否有等于目标值的
  4. * @param nums
  5. * @param target
  6. * @return
  7. */
  8. int binarySearch(int[] nums, int target) {
  9. int left = 0, right = nums.length - 1;
  10. while (left <= right) {
  11. int mid = left + (right - left) / 2;
  12. if (nums[mid] == target) {
  13. return mid;
  14. } else if (nums[mid] < target) {
  15. left = mid + 1;
  16. } else {
  17. right = mid - 1;
  18. }
  19. }
  20. return -1;
  21. }
  22. /**
  23. * 两数之和
  24. * @param nums
  25. * @param target
  26. * @return
  27. */
  28. int[] twoSum(int[] nums, int target) {
  29. int left = 0, right = nums.length - 1;
  30. while (left < right) {
  31. int sum = nums[left] + nums[right];
  32. if (sum == target) {
  33. return new int[]{left + right};
  34. } else if (sum < target) {
  35. left = left + 1;
  36. } else {
  37. right = right - 1;
  38. }
  39. }
  40. return new int[]{-1, -1};
  41. }
  42. /**
  43. * 反转数组
  44. * @param arr
  45. */
  46. void reverseString(char[] arr) {
  47. int left = 0, right = arr.length - 1;
  48. while (left <= right) {
  49. char temp = arr[left];
  50. arr[left] = arr[right];
  51. arr[right] = temp;
  52. left++;
  53. right--;
  54. }
  55. }
  56. // 最后一部分应用是滑动窗口,这部分不再重复,滑动窗口算是双指针高阶用法了,需要运用熟练。
  57. }