算法模板:两个指针 - 第二部分

发布日期:2026-07-05 04:17:04   来源 : 杭州电子商务研究院    浏览量 :22
杭州电子商务研究院 发布日期:2026-07-05 04:17:04  
22

介绍

这是算法模板系列文章的第二部分:两个指针。在第一部分中,我们深入研究了两个指针技术的前两种类型。如果你错过了,请查看。

在本指南(第 2 部分)中,我们将重点介绍另外两种常见用法:

  1. 左右边界
  2. 来自两个序列的指针 1 和指针 2

对于每一种类型,我们将通过两个步骤来了解它:

  1. 通过算法模板和流程图描述概念
  2. 提供典型示例,帮助您了解应用场景和使用方法

左右边界

模板

另一种常见用法是将指针放在最左侧和最右侧。一个指针从头开始,而另一个指针从尾开始。它们相互靠近,直到在中间相遇。

      # left & right boundary: left-> <-right
def left_right_boundary(self, seq):
    left, right = 0, len(seq) - 1
    while left < right:
        # left index moves when satisfy the condition
        if self.left_condition(left):
            left += 1
        # right index move when satisfy the condition
        if self.right_condition(right):
            right -= 1
        # process logic before or after pointers movement
        self.process_logic(left, right)
    

为了更好地理解左右边界的理论,您可以参考下面的示例图表。

总体来说,基于该技术算法可以有两种使用方式:

  1. 左指针和右指针形成一个区间来处理。
  2. 这两个指针携带信息并处理每次迭代中的逻辑。

示例

最经典、最著名的例子就是二分查找,通过不断缩小上下界来找到目标,每次迭代都会截断一半的区间,下面这个例子就是同时包含上下界的版本。

      # [lo, hi] version, modify to [lo, hi) version as you need
def binary_search(arr, target):
    lo, hi = 0, len(arr) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        # find the target, change to your comparison condition as you need
        if arr[mid] == target:
            break
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    

让我们看一个具有两个指针和二分搜索技术的实际例子。

给定一个已经按升序排序的整数数组,找到两个数字,使得它们加起来等于特定的目标数字。

      # [167] https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/
def twoSum(numbers: 'List[int]', target: 'int') -> 'List[int]':
    left, right = 0, len(numbers) - 1
    while left < right:
        if numbers[left] + numbers[right] == target:
            return [left + 1, right + 1]
        if numbers[left] + numbers[right] < target:
            left += 1
        else:
            right -= 1
    return [0, 0]
    

让我们让它稍微困难一些。将二和问题升级为四和问题。

找出数组中所有唯一的四元组,得出目标的总和。

思路:通过上面的例子,解法就很清晰了,可以利用左右边界来寻找解空间,可以把四和问题分解成三和问题,最后分解成二和问题。

      # [18] https://leetcode.com/problems/4sum/
def fourSum(nums: 'List[int]', target: int) -> 'List[int]':
    result, n = [], len(nums)
    if n < 4: return result
    nums = sorted(nums)
    if sum(nums[-4:]) < target:
        return result

    for i in range(n - 3):
        # boundary checker, stop early
        if sum(nums[i:i + 4]) > target:
            break
        # right boundary checker
        if nums[i] + sum(nums[-3:]) < target:
            continue
        # skip same element, but keep the first one
        if i > 0 and nums[i] == nums[i - 1]:
            continue
        # simplify the problem to three sum
        target2 = target - nums[i]
        for j in range(i + 1, n - 2):
            if sum(nums[j:j + 3]) > target2 or sum(nums[-3:]) < target2:
                break
            if nums[j] + sum(nums[-2:]) < target2:
                continue
            if j > i + 1 and nums[j] == nums[j - 1]:
                continue
            # simplify the problem to two sum
            target3 = target2 - nums[j]
            left = j + 1
            right = n - 1
            while left < right:
                if nums[left] + nums[right] == target3:
                    result.append([nums[i], nums[j], nums[left], nums[right]])
                    while left < right and nums[left] == nums[left + 1]:
                        left += 1
                    while left < right and nums[right] == nums[right - 1]:
                        right -= 1
                    left += 1
                    right -= 1
                elif nums[left] + nums[right] < target3:
                    left += 1
                else:
                    right -= 1
    return result
    

下面是一个我们重点关注间隔的例子。

找到两条线,与 x 轴一起形成一个容器,使得容器中装有尽可能多的水。

思路:为了方便大家理解,我画了一个示例图,通过当前左右边界形成的区间来计算水域面积。

      # [11] https://leetcode.com/problems/container-with-most-water/
def maxArea(height: 'List[int]') -> int:
    left, right = 0, len(height) - 1
    area_max = (right - left) * min(height[left], height[right])

    while left < right:
        # judge which side decide the area (shorter height)
        if height[left] <= height[right]:
            last_height = height[left]
            # skip all the height less than current
            while height[left] <= last_height and left < right:  
                left += 1
        else:
            last_height = height[right]
            # skip all the height less than current
            while height[right] <= last_height and left < right:  
                right -= 1

        if left >= right:
            return area_max
        area_max = max(area_max, (right - left) * min(height[left], height[right]))
    return area_max
    

来自两个序列的指针 1 和指针 2

模板

在其他一些场景中,我们需要在两个序列中进行比较。每个指针代表相应序列中的当前逻辑位置。

      # p1 & p2 from two sequences: p1-> p2->
def pointers_from_two_seq(self, seq1, seq2):
    # init pointers
    p1, p2 = 0, 0       # or seq1[0], seq2[0]
    # or other condition
    while p1 < len(seq1) and p2 < len(seq2):
        # p1 index moves when satisfy the condition
        if self.p1_condition(p1):
            p1 += 1         # or p1 = next(seq1)
        # p2 index move when satisfy the condition
        if self.p2_condition(p2):
            p2 += 1         # or p2 = next(seq2)
        # process logic before or after pointers movement
        self.process_logic(p1, p2)
    

从下面的示例图中我们可以看出,这两个指针是相对独立的,它们如何前进是由各自的策略决定的。

示例

让我们看一个包含多个序列的例子。

设计一个类,在构造函数中接收一个单词列表,并实现一个方法,该方法接受两个单词word1word2,并返回列表中这两个单词之间的最短距离。

思路:首先我们为每个单词创建一组索引序列locations。每次调用shortest函数时,我们都会取出相应的序列loc1loc2。然后我们使用两个指针l1l2来计算最短距离。在每次迭代中,较小的指针向前移动一步。

      # [244] https://leetcode.com/problems/shortest-word-distance-ii/
class WordDistance:
    def __init__(self, words: 'List[str]'):
        self.locations = defaultdict(list)
        # Prepare a mapping from a word to all it's locations (indices).
        for i, w in enumerate(words):
            self.locations[w].append(i)

    def shortest(self, word1: str, word2: str) -> int:
        loc1, loc2 = self.locations[word1], self.locations[word2]
        l1, l2 = 0, 0
        min_diff = float("inf")

        # Until the shorter of the two lists is processed
        while l1 < len(loc1) and l2 < len(loc2):
            min_diff = min(min_diff, abs(loc1[l1] - loc2[l2]))
            if loc1[l1] < loc2[l2]:
                l1 += 1
            else:
                l2 += 1
        return min_diff
    

下面是与回溯相结合的另一个例子。

给定一个输入字符串 (s) 和一个模式 (p),实现支持“?”和“*”的通配符模式匹配。

思路:将一个指针s_cur指向目标字符串,另一个指针p_cur指向模式,两个指针记录当前匹配的位置,在匹配'*'时,matchstar是贪婪算法中匹配失败时用来回溯的标志位。

      # [44] https://leetcode.com/problems/wildcard-matching/
def isMatch(s: str, p: str) -> bool:
    s_cur, p_cur, match, star = 0, 0, 0, -1

    while s_cur < len(s):
        # match sucess, both two pointers move forward
        if p_cur < len(p) and (s[s_cur] == p[p_cur] or p[p_cur] == '?'):
            s_cur += 1
            p_cur += 1
        # if matching star, record current state and try to match in greedy
        elif p_cur < len(p) and p[p_cur] == '*':
            match = s_cur
            star = p_cur
            p_cur += 1
        # backtrack if match failed
        elif star != -1:
            p_cur = star + 1
            match += 1
            s_cur = match
        else:
            return False
    # corner case: remove continuous star in the rest
    while p_cur < len(p) and p[p_cur] == '*':
        p_cur += 1

    if p_cur == len(p):
        return True
    else:
        return False
    

结论

在本指南中,我们继续通过模板和示例学习另外两种类型的双指针技术:左边界和右边界以及来自两个序列的指针 1 和指针 2

第三部分中,我们将继续学习其他类型的两个指针。

要访问完整的代码,你可以从 Github 下载算法模板。我希望其中一些对你有用。

本指南是双指针技术系列指南的一部分:

希望你喜欢。如果您有任何问题,欢迎通过 recnac@foxmail.com 与我联系。

以上内容来自杭州电子商务研究院推送
关注
关于我们
热门推荐
合作伙伴
免责声明:本站部分资讯来源于网络,如有侵权请及时联系客服,我们将尽快处理
Copyright © 2025-2027 ToB产业网址导航 公安备案 浙公网安备33010602013138号 浙ICP备16025413号-9
支持 反馈 关注 数据