双指针
由于之前学习过二分查找、滑动窗口的题目,不难看出,使用的方法本质上就是双指针。 这一篇中将学习另外两类技巧:
- 快慢指针:主要解决链表中的问题,如判定链表中是否包含环。
- 左右指针:主要解决数组(或字符串)中的问题,如二分查找。
快慢指针
快慢指针一般初始化都指向头节点,前进时快指针在前,慢指针在后。 下面通过一些应用题目来加深理解
class Solutions {/*** 判定链表中是否含有环* 遍历链表时通常采用此种方式,但遇到链表中含有环就会陷入死循环* 经典解法使用两个指针指向链表,快慢指针* 不含环的情况下,快指针优先遇到null* 含有环的情况下,快指针最终会超过慢指针一圈,和慢指针相遇。* ??这里是不是个物理问题,为什么快指针会超过慢指针一圈?* @param head* @return*/boolean traverse(ListNode head) {while (head != null) {head = head.next;}return false;}boolean hasCycle(ListNode head) {ListNode fast, slow;fast = slow = head;while (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;if (fast == slow) {return true;}}return false;}/*** 已知链表中含有环,返回环的起始位置* @param head* @return*/ListNode detectCycle(ListNode head) {ListNode fast, slow;fast = slow = head;while (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;if (fast == slow) break;}// 无论绕几圈,肯定能遇见slow = head;while (slow != fast) {fast = fast.next;slow = slow.next;}return slow;}/*** 寻找链表中点* 寻找中点的应用价值在于能够对链表做二分,能够实现二分链表则能够实现归并等操作** @param head* @return*/ListNode middleNode(ListNode head) {ListNode fast, slow;fast = slow = head;while (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;}return slow;}/*** 删除链表中倒数第N个节点* @param head* @param n* @return*/ListNode removeNthFromEnd(ListNode head, int n) {ListNode fast, slow;fast = slow = head;while (n-- > 0) {// 该题目中链表长度不超过n,未声明则需要判断fast = fast.next;}if (fast == null) {// 此时倒数第n个节点就是head,删除后即为nullreturn head.next;}while (fast != null && fast.next != null) {fast = fast.next;slow = slow.next;}// 删除第n个节点slow.next = slow.next.next;return head;}}
左右指针
左右指针在数组中实际指两个索引值,一般 left = 0, right = nums.length - 1。 常用于二分查找等,下面是部分应用场景示例。
class Solutions {/*** 二分查找是否有等于目标值的* @param nums* @param target* @return*/int binarySearch(int[] nums, int target) {int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + (right - left) / 2;if (nums[mid] == target) {return mid;} else if (nums[mid] < target) {left = mid + 1;} else {right = mid - 1;}}return -1;}/*** 两数之和* @param nums* @param target* @return*/int[] twoSum(int[] nums, int target) {int left = 0, right = nums.length - 1;while (left < right) {int sum = nums[left] + nums[right];if (sum == target) {return new int[]{left + right};} else if (sum < target) {left = left + 1;} else {right = right - 1;}}return new int[]{-1, -1};}/*** 反转数组* @param arr*/void reverseString(char[] arr) {int left = 0, right = arr.length - 1;while (left <= right) {char temp = arr[left];arr[left] = arr[right];arr[right] = temp;left++;right--;}}// 最后一部分应用是滑动窗口,这部分不再重复,滑动窗口算是双指针高阶用法了,需要运用熟练。}
