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

双指针(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 指针。

总结

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

相关推荐
落魄君子4 分钟前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘
菜鸡中的奋斗鸡→挣扎鸡12 分钟前
滑动窗口 + 算法复习
数据结构·算法
Lenyiin21 分钟前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
郭wes代码28 分钟前
Cmd命令大全(万字详细版)
python·算法·小程序
scan72442 分钟前
LILAC采样算法
人工智能·算法·机器学习
菌菌的快乐生活1 小时前
理解支持向量机
算法·机器学习·支持向量机
大山同学1 小时前
第三章线性判别函数(二)
线性代数·算法·机器学习
axxy20001 小时前
leetcode之hot100---240搜索二维矩阵II(C++)
数据结构·算法
黑客Ash2 小时前
安全算法基础(一)
算法·安全
AI莫大猫2 小时前
(6)YOLOv4算法基本原理以及和YOLOv3 的差异
算法·yolo