文章目录
- 双指针
- [leetcode167 两数之和](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/description/)
- [leetcode88 合并两个有序数组](https://leetcode.cn/problems/merge-sorted-array/description/)
- [leetcode142 环形链表](https://leetcode.cn/problems/linked-list-cycle-ii/description/)
- [leetcode76 最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/)
双指针
双指针用于处理数组和链表等线性结构。同时用2个指针遍历有序的数据结构,减少时间复杂度。
leetcode167 两数之和
分析
由于数组是有序的,因此两个指针,一个从前向后遍历,一个从后往前遍历,可以在O(n)时间复杂度完成查询。
假设结果为[i, j]。那么左指针遍历到i时,如果右指针大于j,那么和偏大,右指针左移,不会出现左指针右移的情况。直到右指针走到j,返回结果。右指针遍历到j也是同理,因此不会出现左指针大于i,右指针小于j的情况。
题解
class Solution {
public int[] twoSum(int[] numbers, int target) {
int first = 0; // 第一个指针
int last = numbers.length - 1; // 第二个指针
while (first < last) {
int sum = numbers[first] + numbers[last];
if (sum == target) {
return new int[]{first + 1, last + 1};
} else if (sum < target) {
first++;
} else {
last--;
}
}
return new int[]{-1, -1};
}
}
leetcode88 合并两个有序数组
分析
2个有序数组,每个数组用一个指针遍历。因为合并结果存在nums1
数组,而nums1
数组的前半部分有值,后半部分是空的。所以与正向双指针相比,逆向双指针更快。
题解
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int tail = m + n - 1;
int i = m - 1; // 第一个指针
int j = n - 1; // 第二个指针
int cur;
while (i >= 0 || j >= 0) {
if (i < 0) {
cur = nums2[j];
j--;
} else if (j < 0) {
cur = nums1[i];
i--;
} else if (nums1[i] > nums2[j]) {
cur = nums1[i];
i--;
} else {
cur = nums2[j];
j--;
}
nums1[tail] = cur;
tail--;
}
}
}
leetcode142 环形链表
分析
假设slow指针和fast指针均从head节点出发,每次slow移动1个节点,fast移动2个节点。它们相遇时的位置如图。a表示头节点到入环点的距离,b表示入环点到相遇点的距离,c表示相遇点到入环点的距离。
slow = a + b, fast = a + n(b + c) + b
其中n
表示fast节点在环中多走的次数。
fast = 2 * slow
,则
a + n(b + c) + b = 2(a + b)
,变换得到
(n - 1)(b + c) + c = a
,这个等式意味着,分别从相遇点出发和从头结点出发的两个节点终会在入环点相遇。
非公式分析
现在fast和slow在相遇点相遇,slow
走过的距离是x
。此时再来一个节点ptr
以slow
的速度从头结点走到相遇点,走过的距离也是x
。那么slow
在这期间走过的距离是多少?是2x
,恰好是之前fast
走过的距离。ptr
与slow
相遇的位置恰好是slow
与fast
相遇的位置。
由于slow
与ptr
在环里相遇,它们速度又相同,因此它们在环里的每个位置都相遇,自然包括入环点。
题解
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next; // 快指针
slow = slow.next; // 慢指针
if (slow == fast) {
fast = head; // 不浪费指针,fast表示ptr
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
return null;
}
}
leetcode76 最小覆盖子串
分析
左右指针在同一个字符数组上,分别表示子串的左右边界。由于要求最小子串,因此先用右指针保证覆盖子串内容,再停止右指针并用左指针压缩子串范围。
题解
public static String minWindow(String s, String t) {
char[] sArray = s.toCharArray();
char[] tArray = t.toCharArray();
int[] cnt = new int[128];
int count = 0;
for (char c : tArray) {
cnt[c]++;
count++;
}
int start = -1;
int end = sArray.length;
int l = 0;
for (int r = 0; r < sArray.length; r++) {
if (--cnt[sArray[r]] >= 0) {
count--;
}
while (count == 0) {
if (++cnt[sArray[l]] > 0) {
if (r - l < end - start) {
end = r;
start = l;
}
count++;
}
l++;
}
}
return start == -1 ? "" : s.substring(start, end + 1);
}
}