【算法】day6 双指针补充
1、接雨水(相撞,单调性)hot
题目:42. 接雨水 - 力扣(LeetCode)
分析:
时间复杂度:O(n),只遍历了一次柱子。
空间复杂度:O(1),只用了几个额外变量。
代码:
class Solution {public int trap(int[] height) {int left = 0, right = height.length - 1; // 最左、最右柱子int leftMax = 0, rightMax = 0; // 最左柱左边无柱,最右柱右边无柱int sum = 0;while(left <= right) {// leftMax 为左柱的两边最高柱的较小柱if (leftMax <= rightMax) {int tmp = leftMax - height[left];if (tmp >= 0) sum += tmp;// left 位置积水已算完,更新 leftMax,遍历下一个柱子leftMax = Math.max(leftMax, height[left++]);} else { // rightMax 为右柱的两边最高柱的较小柱int tmp = rightMax - height[right];if (tmp >= 0) sum += tmp;// right 位置积水已算完,更新 rightMax,遍历下一个柱子rightMax = Math.max(rightMax, height[right--]);}}return sum;}
}
2、轮转数组 hot
题目:189. 轮转数组 - 力扣(LeetCode)
分析:
① 朴素方法:遍历数组,每次将 i 处数字放到新数组 (i+k)%n 的位置,使用新数组是避免移动的数字覆盖该位置原数字。
时间:O(n)
空间:O(n)
② 双指针:实现数组翻转。
m = k % n,对于长度 n 为 3 的数组,轮转 k=10 次只需轮转 10 % 3 = 1 次。
第一次翻转,[0, n-1]:把 5、6、7 放到前面,1、2、3、4 放到后面。
第二次翻转,[0, m - 1]:把 5、6、7 恢复原来正序。
第三次翻转,[m, n-1]:把 1、2、3、4 恢复原来正序。
时间:O(2n)=O(n)
空间:O(1)
代码:
class Solution {// 数组翻转private void reverse(int[] nums, int start, int end) {int tmp;while(start < end) {if (nums[start] != nums[end]) {tmp = nums[start];nums[start] = nums[end];nums[end] = tmp;}start++;end--;}}// 数组轮转public void rotate(int[] nums, int k) {int n = nums.length;int m = k % n;if (k == 0) return;reverse(nums, 0, n-1);reverse(nums, 0, m - 1);reverse(nums, m, n-1);}
}
3、相交链表 hot
题目:160. 相交链表 - 力扣(LeetCode)
分析:可以让双指针相遇,相遇的地方就是相交结点;如果没有相遇,则没有相交节点。那么两指针需要走同样的步数,那么较长链表需要提前走 |链A长 - 链B长| 步。
代码:
public class Solution {// 求链长private int size(ListNode head) {ListNode cur = head;int cnt = 0;while(cur != null) {cnt++;cur = cur.next;}return cnt;}public ListNode getIntersectionNode(ListNode headA, ListNode headB) {// 求两条链的长度int sizeA = size(headA);int sizeB = size(headB);// 求链长差int len = Math.abs(sizeA - sizeB);// 让长的先走 len 步ListNode headMax = sizeA >= sizeB ? headA : headB;ListNode headMin = sizeA < sizeB ? headA : headB;while(len-- != 0) headMax = headMax.next;// 再同时走,相等的地方就是相交结点;若没有交点,A、B 都会在 null 处停下while(headMax != headMin) {headMax = headMax.next;headMin = headMin.next;}return headMax;}
}
4、环形链表(快慢指针测环) hot
题目:141. 环形链表 - 力扣(LeetCode)
分析:慢指针每次走1步,快指针每次走2步。如果有环,它们总会相遇;如果没有环,快指针会先到链尾(偶数个节点:fast.next == null 或 奇数个节点:fast == null)。
代码:
public class Solution {public boolean hasCycle(ListNode head) {if (head == null) return false;// 初始化快慢指针ListNode slow = head; // 每次走一步ListNode fast = head.next; // 每次走两步// 有环:slow 与 fast 相遇// 无环:fast == null 或 fast.next == nullwhile(fast != null && fast.next != null) {if(slow == fast) {return true;}slow = slow.next;fast = fast.next.next;}return false;}
}
5、环形链表Ⅱ(快慢指针测环)hot
题目:142. 环形链表 II - 力扣(LeetCode)
分析:只要求出 head 到入口节点的长度 x,就能求到入口节点。
slow 与 fast 第一次相遇:
slow 走了:x + (y-n)
fast 走了:x + (y-n) + ay
slow 与 fast 的关系:2[x + (y-n)] = x + (y-n) + ay
得到: x = (a-1)y + n
故从 head 走 x 步到达入口;从相遇点走 n + (a-1)y 步也到达入口。
结论:两节点从 head、slow 和 fast 相遇点同时走,最终会在入口处相遇。
代码:
public class Solution {// 求相遇点private ListNode getMeetNode(ListNode head) {if (head == null) return false;// 初始化快慢指针ListNode slow = head;ListNode fast = head.next;while(fast != null && fast.next != null) {if(slow == fast) {return slow;}slow = slow.next;fast = fast.next.next;}return null;}// 求入口点public ListNode detectCycle(ListNode head) {// 获得 slow 与 fast 的相遇结点ListNode meetNode = getMeetNode(head);// 链表无环,返回 nullif(meetNode == null) {return null;}// 分别从头指针,相遇结点开始走,两指针相遇处就是入口while(head != meetNode) {head = head.next;meetNode = meetNode.next;}return head;}
}
6、删除链表的倒数第 N 个节点 hot
题目:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
分析:
朴素方法:因为是单链表,不能从链尾往回数,所以要先遍历链表计算链长 size,再从头走 size - n + 1 步删除节点,所以时间复杂度是 O(size^2)。
双指针:右指针先多走 n-1 步,然后同时走。最终右指针走到链尾,左指针停下的地方就是倒数第 n 个结点。但是删除的是左指针指向的节点,所以还要记录左指针的前一个节点。
时间复杂度:O(size)
空间复杂度:O(1)
代码:
class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {// 初始左右指针,右指针先多走 n-1 步ListNode leftNode = head;ListNode rightNode = head;for (int i = 0; i < n-1; i++) rightNode = rightNode.next;// 然后同时走,直到右指针走到链尾,同时要记录左指针的前一个节点ListNode previous = null;while(rightNode.next != null) {previous = leftNode;leftNode = leftNode.next;rightNode = rightNode.next;}// 删除倒数第 n 个节点if(leftNode == head) head = leftNode.next;else previous.next = leftNode.next;leftNode = null;return head;}
}