Loading......

文章背景图

算法 - 双指针专项 ②

2026-04-09
3
-
- 分钟
|

核心:有序数组 + 相向双指针,将暴力 O(n²)、O(n³) 复杂度优化到更高效的级别,是解决数组求和、计数、最接近值、三角形判定等问题的核心思路。

一、经典题目(讲解)

1. 盛最多水的容器(LeetCode 经典题)

题目描述

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

核心思路

- 容器容量 = 两线间距 × 两线中较短的高度

- 相向双指针:左指针L=0,右指针R=n-1短边指针向内移动

- 移动短边才有可能找到更大容量,移动长边无意义(间距缩小,高度不变 / 变小,容量必减小)

复杂度

  • 时间:O(n)(仅遍历一次数组,双指针移动总次数为 n)

  • 空间:O(1)(仅用常数变量,无额外数组)

Python 实现

def maxArea(height):
    # 初始化左指针在数组起始位置
    left = 0
    # 初始化右指针在数组末尾
    right = len(height) - 1
    # 记录最大盛水量
    max_water = 0
    # 当左右指针没有相遇时循环
    while left < right:
        # 计算当前容器宽度:右指针下标 - 左指针下标
        width = right - left
        # 容器高度取左右两边较小值
        h = min(height[left], height[right])
        # 计算当前盛水量
        current_water = width * h
        # 更新最大盛水量
        max_water = max(max_water, current_water)
        # 移动较短的边,才有可能获得更大容量
        if height[left] < height[right]:
            left += 1
        else:
            right -= 1
    return max_water

2. 接雨水(LeetCode 经典题)

题目描述

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

核心思路

每个位置能接的雨水量 = min(左侧最大高度,右侧最大高度) - 当前高度

两种解法核心一致,均围绕“左右最大高度”计算,差异在于空间复杂度:

  • 解法 1(前缀 / 后缀数组):用额外数组存储左右最大高度,直观易理解

  • 解法 2(双指针优化):用变量动态维护左右最大高度,无需额外数组,空间最优

解法 1:前缀 / 后缀最大值数组

  1. 预处理前缀最大值数组:记录每个位置左侧(含自身)的最高柱子高度

  2. 预处理后缀最大值数组:记录每个位置右侧(含自身)的最高柱子高度

  3. 遍历每个位置,按核心公式累加接水量

复杂度

  • 时间:O(n)(3 次遍历数组,分别处理前缀、后缀数组和计算结果)

  • 空间:O(n)(额外两个大小为 n 的数组)

Python 实现

def trap(height):
    n = len(height)
    if n == 0:
        return 0
    # 前缀最大值数组:pre_max[i]表示i位置左侧(含i)最高柱子
    pre_max = [0] * n
    pre_max[0] = height[0]
    for i in range(1, n):
        pre_max[i] = max(pre_max[i-1], height[i])
    # 后缀最大值数组:suf_max[i]表示i位置右侧(含i)最高柱子
    suf_max = [0] * n
    suf_max[-1] = height[-1]
    for i in range(n-2, -1, -1):
        suf_max[i] = max(suf_max[i+1], height[i])
    # 累加总接水量
    res = 0
    for i in range(n):
        # 当前位置能接的水 = 左右最小高度 - 当前高度
        res += min(pre_max[i], suf_max[i]) - height[i]
    return res

解法 2:双指针优化(空间 O(1))

  1. 单个变量维护左侧最大高度(pre_max)和右侧最大高度(suf_max),无需额外数组

  2. 相向双指针:左指针从 0 开始,右指针从末尾开始,逐步向中间靠拢

  3. 哪边最大高度更小,计算哪边的接水量,移动对应指针,动态更新最大高度

复杂度

  • 时间:O(n)(仅遍历一次数组,双指针移动总次数为 n)

  • 空间:O(1)(仅用常数变量,无额外数组)

Python 实现

def trap(height):
    n = len(height)
    if n == 0:
        return 0
    left = 0  # 左指针
    right = n - 1  # 右指针
    pre_max = 0  # 左侧遍历过的最大高度
    suf_max = 0  # 右侧遍历过的最大高度
    res = 0  # 总接水量
    # 左右指针未相遇时循环
    while left <= right:
        # 更新左侧最大高度
        pre_max = max(pre_max, height[left])
        # 更新右侧最大高度
        suf_max = max(suf_max, height[right])
        # 左侧最大高度更小,计算左指针位置接水量
        if pre_max < suf_max:
            res += pre_max - height[left]
            left += 1
        # 右侧最大高度更小,计算右指针位置接水量
        else:
            res += suf_max - height[right]
            right -= 1
    return res

二、双指针专项练习

  1. 验证回文串:核心考察“相向双指针跳过无效字符”的应用

  2. 给植物浇水 II:核心考察“相向双指针分区域处理”,结合条件判断优化流程

1. 125. 验证回文串(LeetCode)

题目描述

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。

字母和数字都属于字母数字字符。

给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。

示例

输入: s = "A man, a plan, a canal: Panama"

输出:true

解释:"amanaplanacanalpanama" 是回文串。

Python 代码

class Solution:
    def isPalindrome(self, s: str) -> bool:
        # 初始化左右双指针,分别指向字符串首尾
        i, j = 0, len(s) - 1
        # 当左指针小于右指针时,持续判断
        while i < j:
            # 左指针指向非字母数字字符,右移跳过
            if not s[i].isalnum():
                i += 1
            # 右指针指向非字母数字字符,左移跳过
            elif not s[j].isalnum():
                j -= 1
            # 左右指针均为字母数字,转换为小写后判断是否相等
            elif s[i].lower() == s[j].lower():
                # 相等则继续向中间靠拢
                i += 1
                j -= 1
            # 不相等则直接返回false,不是回文串
            else:
                return False
        # 循环结束,所有对应位置均匹配,返回true
        return True

2. 2105. 给植物浇水 II(LeetCode)

题目描述

Alice 和 Bob 打算给花园里的 n 株植物浇水。植物排成一行,从左到右进行标记,编号从 0 到 n - 1 。其中,第 i 株植物的位置是 x = i 。

每一株植物都需要浇特定量的水。Alice 和 Bob 每人有一个水罐,最初是满的 。他们按下面描述的方式完成浇水:

  • Alice 按 从左到右 的顺序给植物浇水,从植物 0 开始。Bob 按 从右到左 的顺序给植物浇水,从植物 n - 1 开始。他们 同时 给植物浇水。

  • 无论需要多少水,为每株植物浇水所需的时间都是相同的。

  • 如果 Alice/Bob 水罐中的水足以 完全 灌溉植物,他们 必须 给植物浇水。否则,他们 首先(立即)重新装满罐子,然后给植物浇水。

  • 如果 Alice 和 Bob 到达同一株植物,那么当前水罐中水 更多 的人会给这株植物浇水。如果他俩水量相同,那么 Alice 会给这株植物浇水。

给你一个下标从 0 开始的整数数组 plants ,数组由 n 个整数组成。其中,plants[i] 为第 i 株植物需要的水量。另有两个整数 capacityA 和 capacityB 分别表示 Alice 和 Bob 水罐的容量。返回两人浇灌所有植物过程中重新灌满水罐的 次数 。

示例

输入:plants = [2,2,3,3], capacityA = 5, capacityB = 5

输出:1

解释:

  • 最初,Alice 和 Bob 的水罐中各有 5 单元水。

  • Alice 给植物 0 浇水,Bob 给植物 3 浇水。

  • Alice 和 Bob 现在分别剩下 3 单元和 2 单元水。

  • Alice 有足够的水给植物 1 ,所以她直接浇水。Bob 的水不够给植物 2 ,所以他先重新装满水,再浇水。

  • 所以,两人浇灌所有植物过程中重新灌满水罐的次数 = 0 + 0 + 1 + 0 = 1 。

Python 代码

from typing import List

class Solution:
    def minimumRefill(self, plants: List[int], capacityA: int, capacityB: int) -> int:
        # 记录重新灌水的总次数
        ans = 0
        # 植物的总数量
        n = len(plants)
        # 初始化左右双指针,Alice从左(0)开始,Bob从右(n-1)开始
        l, r = 0, n-1
        # 初始化Alice和Bob水罐当前的水量(初始为满罐)
        wata, watb = capacityA, capacityB
        
        # 当左指针小于等于右指针时,持续浇水(覆盖所有植物)
        while l <= r:
            # 情况1:左右指针相遇,即两人到达同一株植物
            if l == r:
                # 判断两人水罐中最大水量是否能浇完当前植物,不够则需重新灌水1次
                if max(wata, watb) < plants[l]:
                    ans += 1
                # 同一株植物只需浇一次,直接退出循环
                break
            # 情况2:Alice浇水(左指针位置)
            # 若当前水量不足,重新灌满,次数+1,更新水量为满罐
            if wata < plants[l]:
                ans += 1
                wata = capacityA
            # 浇完当前植物,减去对应水量
            wata -= plants[l]   
            # 情况3:Bob浇水(右指针位置)
            # 若当前水量不足,重新灌满,次数+1,更新水量为满罐
            if watb < plants[r]:
                ans += 1
                watb = capacityB
            # 浇完当前植物,减去对应水量
            watb -= plants[r]   
            # 两人同时向中间移动指针
            l += 1
            r -= 1
        # 返回总重新灌水次数
        return ans

评论交流