双指针算法是算法设计中非常实用且高效的技巧,特别适合处理线性数据结构(数组、链表、字符串)。我将从原理、分类、应用场景到具体代码实现,为你全面解析这个算法思想。


一、双指针算法核心思想

双指针通过使用两个指针变量在数据结构中有规律地移动,以 O (n) 的时间复杂度解决问题。两个指针的移动方式主要有三种模式:

  1. 同向移动(快慢指针)
  2. 相向移动(左右指针)
  3. 滑动窗口(特殊双指针变种)

二、双指针算法分类详解

1. 快慢指针(同向指针)

应用场景:链表操作、原地修改数组、检测循环

典型示例:删除排序数组中的重复项

int removeDuplicates(vector<int>& nums) {
    if(nums.empty()) return 0;
    
    int slow = 0;
    for(int fast = 1; fast < nums.size(); ++fast) {
        if(nums[fast] != nums[slow]) {
            nums[++slow] = nums[fast];
        }
    }
    return slow + 1;
}

代码解析

  • slow 指针标记有效序列末尾
  • fast 指针扫描所有元素
  • 时间复杂度:O (n),空间复杂度:O (1)
2. 左右指针(相向指针)

应用场景:有序数组、两数之和、回文判断

典型示例:两数之和 II

vector<int> twoSum(vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1;
    while(left < right) {
        int sum = nums[left] + nums[right];
        if(sum == target) {
            return {left+1, right+1};
        } else if(sum < target) {
            ++left;
        } else {
            --right;
        }
    }
    return {};
}

代码解析

  • 利用数组有序特性缩小搜索范围
  • 每次排除不可能的情况
  • 时间复杂度:O (n),空间复杂度:O (1)
3. 滑动窗口(特殊双指针)

应用场景:子串/子数组问题

典型示例:无重复字符的最长子串

int lengthOfLongestSubstring(string s) {
    unordered_set<char> window;
    int max_len = 0, left = 0;
    
    for(int right = 0; right < s.size(); ++right) {
        while(window.count(s[right])) {
            window.erase(s[left++]);
        }
        window.insert(s[right]);
        max_len = max(max_len, right - left + 1);
    }
    return max_len;
}

代码解析

  • leftright 维护当前窗口
  • 哈希集合快速判断重复
  • 时间复杂度:O (n),空间复杂度:O (字符集大小)

三、双指针的四大经典应用场景

1. 链表操作
// 检测链表环
bool hasCycle(ListNode *head) {
    ListNode *slow = head, *fast = head;
    while(fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast) return true;
    }
    return false;
}
2. 数组去重
// 移除指定元素
int removeElement(vector<int>& nums, int val) {
    int slow = 0;
    for(int fast = 0; fast < nums.size(); ++fast) {
        if(nums[fast] != val) {
            nums[slow++] = nums[fast];
        }
    }
    return slow;
}
3. 字符串处理
// 反转字符串
void reverseString(vector<char>& s) {
    int left = 0, right = s.size()-1;
    while(left < right) {
        swap(s[left++], s[right--]);
    }
}
4. 多维数组扫描
// 搜索二维矩阵 II
bool searchMatrix(vector<vector<int>>& matrix, int target) {
    int row = 0, col = matrix[0].size()-1;
    while(row < matrix.size() && col >= 0) {
        if(matrix[row][col] == target) return true;
        matrix[row][col] > target ? --col : ++row;
    }
    return false;
}

四、双指针算法设计要点

  1. 指针移动条件:明确每个指针何时需要移动
  2. 终止条件:正确处理边界条件防止越界
  3. 初始位置:合理设置指针初始位置(头尾、同起点等)
  4. 移动步长:确定每次移动的步长(固定 1 步或动态调整)

五、常见错误及调试技巧

  1. 死循环:检查指针移动条件和终止条件
  2. 数组越界:确保访问前检查边界
  3. 遗漏情况:使用测试用例验证边界条件
  4. 指针同步:注意多指针间的联动关系

六、复杂度分析对比

问题类型暴力解法双指针解法
两数之和O (n²)O (n)
数组去重O (n²)O (n)
最长无重复子串O (n³)O (n)
链表环检测O (n) 哈希法O (n)

七、LeetCode 进阶练习题

  1. 中等难度:

      1. 三数之和
      1. 盛最多水的容器
      1. 接雨水
  2. 困难难度:

      1. 最小覆盖子串
      1. 串联所有单词的子串
      1. 最小区间

八、总结与提升建议

双指针算法的核心在于 通过合理组织指针运动,将暴力解法中的冗余计算消除。掌握这个算法需要:

  1. 理解指针移动的内在逻辑
  2. 积累常见问题模式
  3. 进行系统性题目训练
  4. 学会分析指针移动的数学原理

建议从简单题开始,逐步体会不同场景下指针的移动策略,最终达到灵活运用的水平。记住:双指针不是死板的模板,而是一种通过组织计算顺序来优化效率的思想。

更多推荐