1. 双指针算法概述
双指针技巧是算法领域中处理数组和链表问题的利器,它通过维护两个指针变量来高效解决各类遍历和搜索问题。这种技巧之所以高效,是因为它能在O(n)的时间复杂度内完成许多看似复杂的问题,同时保持空间复杂度为O(1)。
在实际应用中,双指针主要分为两大类:
- 快慢指针:两个指针同向移动但速度不同
- 左右指针:两个指针相向或背向移动
提示:双指针不是物理意义上的指针,而是表示数组索引或链表节点的引用变量。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 快慢指针深度解析
2.1 原地修改数组问题
快慢指针最典型的应用场景是需要在原数组上进行修改的问题。以LeetCode第26题"删除有序数组中的重复项"为例:
java复制public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int slow = 0, fast = 0;
while (fast < nums.length) {
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
}
fast++;
}
return slow + 1;
}
核心思想:
slow指针指向当前唯一元素的最后一个位置fast指针用于扫描整个数组- 当发现新元素时,先移动
slow再赋值
时间复杂度分析:仅需一次遍历,时间复杂度为O(n),空间复杂度O(1)
2.2 链表中的快慢指针
快慢指针在链表问题中同样大放异彩,比如判断链表是否有环:
java复制boolean hasCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
关键点:
- 快指针每次移动两步,慢指针每次移动一步
- 如果存在环,快慢指针必定会相遇
- 时间复杂度O(n),空间复杂度O(1)
3. 左右指针实战应用
3.1 二分查找的指针实现
二分查找是左右指针的经典应用:
java复制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;
}
注意事项:
- 循环条件
left <= right确保区间有效性 - 计算mid时使用
left + (right-left)/2防止整数溢出 - 每次迭代都将搜索范围减半,时间复杂度O(log n)
3.2 两数之和问题
对于有序数组的两数之和问题(LeetCode 167题):
java复制public int[] twoSum(int[] numbers, int target) {
int left = 0, 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};
}
优化点:
- 利用数组有序特性,可以安全地移动指针
- 当sum小于target时,只有增大left才能增加sum
- 当sum大于target时,只有减小right才能减少sum
4. 滑动窗口技巧
4.1 基本框架
滑动窗口是双指针的高级应用,适用于子串/子数组问题:
java复制void slidingWindow(String s) {
Map<Character, Integer> window = new HashMap<>();
int left = 0, right = 0;
while (right < s.length()) {
char c = s.charAt(right);
right++;
// 更新窗口数据
while (/* 窗口需要收缩的条件 */) {
char d = s.charAt(left);
left++;
// 更新窗口数据
}
}
}
4.2 最长回文子串问题
结合中心扩展法的双指针解法(LeetCode 5题):
java复制public String longestPalindrome(String s) {
String res = "";
for (int i = 0; i < s.length(); i++) {
String s1 = palindrome(s, i, i); // 奇数长度
String s2 = palindrome(s, i, i + 1); // 偶数长度
res = res.length() > s1.length() ? res : s1;
res = res.length() > s2.length() ? res : s2;
}
return res;
}
String palindrome(String s, int l, int r) {
while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) {
l--; r++;
}
return s.substring(l + 1, r);
}
性能分析:
- 外层循环O(n)
- 每个中心点的扩展操作最坏O(n)
- 总体时间复杂度O(n²),空间复杂度O(1)
5. 常见问题与调试技巧
5.1 指针越界问题
处理指针移动时,务必先检查边界条件:
java复制while (left < right && nums[left] == nums[left+1]) {
left++; // 避免数组越界
}
5.2 循环终止条件
不同问题的循环条件需要仔细设计:
while (left <= right):包含左右指针重合的情况while (left < right):不包含重合情况while (fast != null && fast.next != null):链表遍历的安全条件
5.3 调试可视化技巧
在IDE中设置断点观察指针移动:
- 打印指针当前位置的值
- 可视化指针移动轨迹
- 检查每次迭代后的中间结果
java复制System.out.println("slow: " + slow + "=" + nums[slow] +
", fast: " + fast + "=" + nums[fast]);
掌握双指针技巧的关键在于多练习、多总结。在实际编码中,我习惯先用注释写明每个指针的职责,再逐步实现具体逻辑。记住,清晰的思路比立即写代码更重要。
