【数据结构】双指针算法:理论与实战

双指针(Two Pointers)是一种经典的算法思想,广泛应用于数组、链表等数据结构的处理。该方法通过设置两个指针,在某种规则下移动指针来实现高效的计算与查找。这种算法相比传统的嵌套循环能显著优化时间复杂度,通常能够达到 O(N) 的时间复杂度,从而大幅提升效率。

如果你还不了解什么是时间复杂度,可参考以下文章:

双指针算法的核心思想

双指针的基本思路是使用两个指针同时操作,以减少重复计算。两个指针可以根据具体情况分为同向指针和对向指针。

  1. 同向指针:两个指针从一端出发,彼此逐步靠近。例如,用于查找数组中满足条件的连续子数组。
  2. 对向指针:两个指针分别从两端出发,逐步相向移动,适用于查找满足条件的元素组合,如求数组中和为特定值的两个数。

通过双指针的移动,我们可以逐步缩小问题的范围,避免重复计算。它的应用场景广泛,通常用于解决有序数组的查找、链表的判环等问题。

算法步骤

  1. 初始化两个指针:通常从数组的两端出发(对向指针)或从数组的同一端出发(同向指针)。
  2. 根据问题的条件决定指针的移动方向。例如,若寻找数组中和为特定值的两数,若当前和大于目标值则移动右指针,反之则移动左指针。
  3. 在每一步迭代中,检查当前指针位置是否满足目标条件,若满足则记录结果或停止算法。
  4. 重复此过程,直到两个指针相遇或无法满足条件为止。

算法模板

以下是双指针算法的基本模板:

cpp 复制代码
int twoPointerExample(const 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, right};  // 找到目标
        } else if (sum < target) {
            left++;  // 增大和
        } else {
            right--; // 减小和
        }
    }
    return {};  // 无解
}

代码解析

  1. 初始化左右指针left 初始化为数组起始位置 0right 初始化为数组末尾位置 nums.size() - 1。双指针从两端相向移动,逐步缩小搜索范围。

  2. 循环条件while (left < right) 保证了左指针位于右指针左侧。当左右指针重叠时,说明已遍历所有可能组合。

  3. 计算当前和并判断 :在每次循环中,计算当前指针位置的和 sum = nums[left] + nums[right],并判断是否等于目标值 target。若相等则直接返回索引,表示找到了满足条件的解。

  4. 调整指针位置 :若 sum < target,表示当前和小于目标值,需要增大和,因此将 left 右移。反之,若 sum > target,则将 right 左移以减小和。通过这种调整,算法在满足条件的前提下高效地缩小搜索范围。

  5. 无解返回 :若循环结束未找到解,返回空数组 return {};。这种处理方式可以避免出现异常情况并确保程序的稳定性。

注意事项

  1. 指针范围与数组有序性:该示例依赖于输入数组的有序性,以确保双指针能够正确缩小范围。若数组无序,则无法正确缩小搜索区间。
  2. 边界情况:若数组为空或仅有一个元素,则算法无需运行或会直接返回无解结果。
  3. 时间复杂度 :由于每次操作仅需移动 leftright 指针之一,整个过程的时间复杂度为 O(N),相较于暴力求解的 O(N^2) 更加高效。
  4. 空间复杂度:本算法无需额外空间,空间复杂度为 O(1)。

算法实战:盛最多水的容器

给定一个长度为 n 的整数数组 height。有 n 条垂线,第 i 条线的两个端点是 (i, 0)(i, height[i])。要求找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

输入示例

cpp 复制代码
height = [1,8,6,2,5,4,8,3,7]

输出

cpp 复制代码
49

解释

在此情况下,最大水容量为 49,选择的两个端点分别在位置 1 和位置 8。

代码实现

cpp 复制代码
class Solution {
public:
    int maxArea(vector<int>& height) {
        int l = 0, r = height.size() - 1, ans = 0;
        while (l < r) {
            ans = max(ans, (r - l) * min(height[r], height[l]));
            if (height[r] < height[l]) r--;
            else l++;
        }
        return ans; 
    }
};

代码解析

  1. lr 初始化为左右两端的指针。
  2. ans 用于存储最大面积,初始为 0。
  3. while(l < r):当左右指针相遇时停止迭代。
  4. 每次迭代中更新 ans 为当前最大面积。
  5. 比较 height[l]height[r],移动较短的一端的指针,直到 l >= r

复杂度分析

  • 时间复杂度:O(N),因为只需要遍历数组一次。
  • 空间复杂度:O(1),除了输入外不需要额外的空间。

双指针的拓展

除了经典的求和问题,双指针还可以用于许多其他场景:

  • 链表的环检测:快慢指针用于判断链表是否存在环。
  • 分割数组:例如分割奇偶数组,通过同向双指针将奇数与偶数分开。
  • 字符串处理:用于判断回文串、删除字符串中的某些字符等问题。

注意事项

  1. 指针范围控制:使用双指针时需谨慎控制指针范围,以防止越界。
  2. 初始条件:在一些问题中需设定合适的初始条件,如链表判环问题中的初始 slow 和 fast 指针。

总结

双指针算法是一种简洁而高效的算法思路,广泛应用于数组和链表的处理。理解双指针的原理和不同应用场景,可以帮助我们在算法竞赛和日常编程中解决复杂问题。通过合理控制两个指针的移动,可以极大地降低时间复杂度,避免不必要的重复计算。

相关推荐
林爷万福2 分钟前
MATLAB光谱数据分析从入门到项目实战
算法·光纤光谱仪
吴可可1238 分钟前
AutoCAD2016二次开发环境配置指南
算法·机器学习
一条大祥脚11 分钟前
ABC461 枚举|扫描线|动态前缀和|数论|dfs枚举子集
算法·深度优先
计算机安禾15 分钟前
【数据库系统原理】第14篇:关系模式的语义约束:函数依赖的公理系统与闭包计算
人工智能·算法·机器学习
量化君也16 分钟前
快速入门量化交易都要学些什么?
大数据·人工智能·python·算法·金融
AbandonForce27 分钟前
滑动窗口:定长滑动窗口与不定长滑动窗口
数据结构·c++·算法
炸薯条!39 分钟前
二叉树的链式表示(2)
java·数据结构·算法
Tairitsu_H42 分钟前
[LC优选算法#2] 滑动窗口 | 长度最小的子数组 | 无重复字符的最长子串 | 最大连续1的个数
算法
小欣加油44 分钟前
leetcode3689最大子数组总值I
c++·算法·leetcode·职场和发展·贪心算法
下午写HelloWorld1 小时前
【概念与应用】轻量级加密算法LEA、动态脱敏算法DDA、零知识证明ZKP和优化协同交互协议OCIP
算法·区块链·密码学·安全架构·零知识证明