双指针技术
一、背景
双指针技术是一种常用的算法设计技巧,广泛应用于数组、链表和字符串等数据结构中。它通过使用两个指针同时遍历数据结构,从而实现高效的解决方案。双指针技术通常分为两种类型:快慢指针和左右指针。
二、双指针的类型
- 快慢指针:这种方法使用两个指针,一个指针(快指针)以较快的速度移动,另一个指针(慢指针)以较慢的速度移动。常用于检测链表中的环、寻找链表的中间节点等问题。
- 左右指针:这种方法使用两个指针分别从数据结构的两端开始向中间移动。常用于排序数组的两数之和、反转字符串等问题。
三、双指针的应用示例
示例1:反转字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReverseString {
public void reverseString(char[] s) {
int left = 0;
int right = s.length - 1;
while (left < right) {
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
}
示例2:两数之和 II - 输入有序数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TwoSum {
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
return new int[]{left + 1, right + 1};
} else if (sum < target) {
left++;
} else {
right--;
}
}
return new int[]{-1, -1}; // No solution found
}
}
示例3:环形链表检测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LinkedListCycle {
public boolean hasCycle(ListNode head) {
if (head == null) return false;
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true; // Cycle detected
}
}
return false; // No cycle
}
}
四、LeetCode例题
解决双指针的问题,最重要的是要双指针的移动策略,理解题意后,确定两个指针的初始位置和移动条件。以下是一些经典的LeetCode双指针题目:
例1.15. 三数之和
1.题目描述 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。 2.解题思路
- 排序数组:首先对数组进行排序,这样可以方便地使用双指针法。
- 固定一个数:遍历排序后的数组,固定一个数 nums[i],然后使用双指针法在剩余的部分寻找两个数,使得它们的和为 -nums[i]。
- 双指针法:设置两个指针 left 和 right,分别指向固定数之后的部分的开始和结束位置,根据当前三个数的和调整指针位置。
3.算法正确性证明
通过排序数组,可以确保在使用双指针法时,能够有效地跳过重复的元素,从而避免重复的三元组。 固定一个数后,使用双指针法可以在 O(n) 的时间内找到所有满足条件的组合。
4.代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ThreeSum {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue; // Skip duplicates
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (left < right && nums[left] == nums[left + 1]) left++; // Skip duplicates
while (left < right && nums[right] == nums[right - 1]) right--; // Skip duplicates
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return result;
}
}
例2.160. 相交链表
这个题目不是传统意义上的双指针问题,需要通过敏锐的数学思维发现其中的双指针技巧。
1.题目描述 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 2.解题思路
暴力解法:使用哈希表存储一个链表的节点,然后遍历另一个链表,检查每个节点是否在哈希表中存在。时间复杂度为 O(m+n),空间复杂度为 O(m) 或 O(n)。
双指针法:使用双指针分别遍历两个链表,当一个指针到达链表末尾时,跳转到另一个链表的头节点。这样两个指针最终会在相交节点相遇,或者都到达 null。
3.算法正确性证明
假设链表 A 的长度为 a,链表 B 的长度为 b,相交部分的长度为 c。那么当指针 A 遍历完链表 A 后,跳转到链表 B 的头节点,此时它已经走了 a 步;
同理,指针 B 遍历完链表 B 后,跳转到链表 A 的头节点,它已经走了 b 步。
接下来,两个指针将分别走 c 步到达相交节点。
因此,当两个指针相遇时,它们已经走了 a + b 步。
4.代码实现
1
2
3
4
5
6
7
8
9
10
11
12
public class IntersectionOfTwoLinkedLists {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode pointerA = headA;
ListNode pointerB = headB;
while (pointerA != pointerB) {
pointerA = (pointerA == null) ? headB : pointerA.next;
pointerB = (pointerB == null) ? headA : pointerB.next;
}
return pointerA; // or pointerB
}
}