双指针基础原理与题目说明

双指针基础原理与题目说明

文章目录

🔗 查看完整专栏(LeetCode基础算法专栏)

专栏文章

点击阅读:Python 数据结构与语法速查笔记

点击阅读:哈希表基础原理与题目说明

特别说明: 本文为个人的 LeetCode 刷题与学习笔记,内容仅供学习与交流使用,禁止转载或用于商业用途。需要强调的是,文中的题目解法不一定是最优解(可能存在时间或空间复杂度的进一步优化空间),主要目的是分享个人的解题思路与逻辑实现,仅供参考。 笔记内容为个人理解与总结,可能存在疏漏或偏差,欢迎读者自行甄别并交流探讨。

一、 什么是双指针算法?

双指针(Two Pointers)是一种核心的算法技巧,通过在序列(数组、字符串或链表)中维护两个指针(通常是慢指针和快指针 、左右指针 ,或者双序列指针 ),来减少不必要的重复遍历,从而大幅提高算法效率。

常用于解决以下问题:

  • 有序数组 / 链表问题
  • 子数组 / 子序列问题
  • 回文字符串判断 / 数组原地翻转
  • 滑动窗口问题(本质是同向双指针)
  • 合并区间 / 两数之和 / 三数之和 / 接雨水等

核心思想:

  1. 两个指针同时遍历或从两端向中间靠拢。
  2. 根据题意设定的条件动态移动指针,避免嵌套循环(将 O ( n 2 ) O(n^2) O(n2) 优化为 O ( n ) O(n) O(n))。
  3. 利用指针之间的相对位置与滑动状态判断问题结果。

二、 常见类型与标准模板

2.1 左右指针(相向双指针 / Two Ends)

通常用于有序数组 或字符串 。左指针 l 从左向右,右指针 r 从右向左,根据条件移动 l 或 r,直到两者相遇。

典型应用:区间翻转(Reverse)

核心逻辑就是标准左右指针交换模板:l 指向当前区间左端,r 指向右端。条件 while l < r 保证每对元素只交换一次。

py 复制代码
def reverse(nums, l, r):
    while l < r:
        # 原地交换
        nums[l], nums[r] = nums[r], nums[l]
        l += 1
        r -= 1

通用左右指针模板:

py 复制代码
l, r = 0, len(arr) - 1
while l < r:
    # 根据问题逻辑处理 arr[l], arr[r]
    if condition1:
        l += 1
    elif condition2:
        r -= 1
    else:
        # 视情况可能同时移动
        l += 1
        r -= 1

2.2 快慢指针(同向双指针 / Slow & Fast)

常用于链表 或数组原地修改。快指针走得快(如探路者),慢指针走得慢(如收集者)。

py 复制代码
slow = fast = start_point
while fast and fast.next:
    slow = slow.next
    fast = fast.next.next
    # 例如:判断环形链表
    if slow == fast:
        break

三、 快慢/同向双指针应用

283. 移动零

题目描述 :给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。必须在不复制数组的情况下原地对数组进行操作。

解题思路:

选用同起点双指针交换法。填充指针 l 标记下一个非零元素要填充的位置,遍历指针 r 逐个扫描寻找非零元素。通过原地交换,将非零元素按原始相对顺序"筛选"到前端,零元素自然归位到后端。

核心代码:

py 复制代码
from typing import List

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        核心思路:双指针均从0起步,非零元素换至前端,零自然退至后端
        """
        l, r = 0, 0
        n = len(nums)
        
        while r < n:
            # 找到非零元素时,将其交换到 l 指向的位置
            if nums[r] != 0:
                nums[l], nums[r] = nums[r], nums[l]
                l += 1
            # 遍历指针持续右移
            r += 1

四、 左右/相向双指针应用

11. 盛最多水的容器

题目描述 :给定一个长度为 n 的整数数组 height,找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

解题思路:

初始定位在两端(宽度最大)。盛水面积 = 指针间距(宽度) × 较矮的高度。因此,每次始终移动高度较矮的指针,牺牲宽度以换取寻找更高边界的可能。

核心代码:

py 复制代码
from typing import List

class Solution:
    def maxArea(self, height: List[int]) -> int:
        l, r = 0, len(height) - 1
        ans = 0

        while l < r:
            if height[l] < height[r]:
                cur = (r - l) * height[l]
                l += 1  # 移动更矮的左指针
            else:
                cur = (r - l) * height[r]
                r -= 1  # 移动更矮的右指针

            ans = max(ans, cur)

        return ans

15. 三数之和

题目描述 :给你一个整数数组 nums,判断是否存在三元组和为 0。请你返回所有和为 0 且不重复的三元组。

解题思路:

排序 + 双指针法。外层循环固定一个数,内层将剩余空间转化为"两数之和等于其相反数"的问题。利用升序数组特性移动左右指针 j 和 k,同时注意过滤相邻重复元素以达到去重目的。

核心代码:

py 复制代码
from typing import List

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        ans = []
        nums.sort()  # 排序是去重与双指针移动的基础

        for i in range(len(nums)):
            # 去重:跳过 i 的重复值
            if i > 0 and nums[i] == nums[i-1]:
                continue
            
            target = -nums[i]
            k = len(nums) - 1

            for j in range(i + 1, len(nums)):
                # 去重:跳过 j 的重复值
                if j > i + 1 and nums[j] == nums[j-1]:
                    continue
                
                # 和过大则左移 k,缩小数值
                while j < k and nums[j] + nums[k] > target:
                    k -= 1
                
                if j == k:
                    break
                
                if nums[j] + nums[k] == target:
                    ans.append([nums[i], nums[j], nums[k]])
                    
        return ans

42. 接雨水

题目描述 :给定 n 个非负整数表示柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

解题思路:

单个位置接水量由「左右两侧最大高度的较小值」决定。双指针从两端向中间移动,实时维护 left_max 和 right_max。由当前较矮的一侧决定实际接水高度,计算完毕后向中间逼近。

核心代码:

py 复制代码
from typing import List

class Solution:
    def trap(self, height: List[int]) -> int:
        ans = 0
        l, r = 0, len(height) - 1
        left_max = right_max = 0

        while l < r:
            left_max = max(left_max, height[l])
            right_max = max(right_max, height[r])

            # 由较矮的一侧决定接水量
            if height[l] < height[r]:
                ans += (left_max - height[l])
                l += 1
            else:
                ans += (right_max - height[r])
                r -= 1
                
        return ans

189. 轮转数组

题目描述 :给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置。

解题思路:

通过三次翻转实现原地右旋。基于前面提到的 reverse 左右指针交换模板:先翻转整个数组,再翻转前 k mod n 个元素,最后翻转剩余元素。

核心代码:

py 复制代码
from typing import List

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        n = len(nums)
        index = k % n

        def reverse(l, r):
            while l < r:
                nums[l], nums[r] = nums[r], nums[l]
                l += 1
                r -= 1
        
        # 1. 翻转全部
        reverse(0, n - 1)
        # 2. 翻转前段 [0, (k mod n)-1]
        reverse(0, index - 1)
        # 3. 翻转后段 [k mod n, n-1]
        reverse(index, n - 1)

234. 回文链表

解题思路:

利用「数组可随机访问」的特性,先将链表元素按序存入数组。随后使用经典的左右指针从数组两端向中间靠拢,对比元素是否一致。

py 复制代码
from typing import Optional

# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def isPalindrome(self, head: Optional[ListNode]) -> bool:
        listarray = []
        current = head
        
        while current:
            listarray.append(current.val)
            current = current.next
            
        n = len(listarray)
        l, r = 0, n - 1
        
        while l <= r:
            if listarray[l] != listarray[r]:
                return False
            l += 1
            r -= 1
            
        return True

五、 双序列/链表指针应用

处理两个独立的序列或链表时,通常需要维护两个独立的遍历指针,比较后进行逻辑流转。

21. 合并两个有序链表

解题思路:

使用双指针+虚拟头节点。逐节点比较两个链表的当前值,优先拼接更小值的节点以保证升序。循环结束后,将未遍历完的链表直接追加到尾部。

核心代码:

py 复制代码
from typing import Optional

class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = cur_node = ListNode(0)
        
        while list1 and list2:
            if list1.val < list2.val:
                cur_node.next = list1
                list1 = list1.next
            else:
                cur_node.next = list2
                list2 = list2.next
            cur_node = cur_node.next
        
        # 拼接剩余节点
        cur_node.next = list1 if list1 else list2
        
        return dummy.next

2. 两数相加

解题思路:

逆序存储天然匹配从个位开始相加的逻辑。设置两个指针同步遍历链表,同时维护一个 carry(进位标记)。空节点按 0 处理,直至两链表遍历完且进位为 0 时终止。

核心代码:

py 复制代码
from typing import Optional

class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = cur = ListNode(0)
        carry = 0 

        while l1 or l2 or carry:
            val1 = l1.val if l1 else 0 
            val2 = l2.val if l2 else 0

            total = val1 + val2 + carry
            cur_val = total % 10
            carry = total // 10

            cur.next = ListNode(cur_val)
            cur = cur.next

            if l1: l1 = l1.next
            if l2: l2 = l2.next
        
        return dummy.next

4. 寻找两个正序数组的中位数

解题思路:

通过双指针将两个有序数组合并为一个完整的升序数组。谁更小就把谁加入合并数组,最后根据合并数组的长度奇偶性计算中位数。

(注:严格要求的 O ( log ⁡ ( m + n ) ) O(\log (m+n)) O(log(m+n)) 需使用二分查找,此处合并法的时间复杂度为 O ( m + n ) O(m+n) O(m+n),胜在逻辑直观,适合作为基础掌握)

核心代码:

py 复制代码
from typing import List

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        nums = []
        idx1, idx2 = 0, 0

        # 合并过程
        while idx1 < len(nums1) and idx2 < len(nums2):
            if nums1[idx1] < nums2[idx2]:
                nums.append(nums1[idx1])
                idx1 += 1
            else:
                nums.append(nums2[idx2])
                idx2 += 1

        # 追加剩余元素
        nums.extend(nums1[idx1:])
        nums.extend(nums2[idx2:])
        
        n = len(nums)
        if n % 2 == 1:
            return nums[n // 2]
        else:
            left, right = n // 2 - 1, n // 2
            return (nums[left] + nums[right]) / 2

六、 双指针与其他算法的巧妙结合

238. 除了自身以外数组的乘积 (前后缀指针)

解题思路:

利用左右双向遍历维护两个列表(或直接利用输出数组进行优化),分别存储当前节点左边的乘积和右边的乘积,最终相乘。避免了使用除法导致的零位异常。

核心代码:

py 复制代码
from typing import List

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        left_ans = [1] * n
        right_ans = [1] * n
        ans = [0] * n

        for l in range(1, n):
            left_ans[l] = left_ans[l-1] * nums[l-1]
        
        for r in range(n-2, -1, -1):
            right_ans[r] = right_ans[r+1] * nums[r+1]
        
        for i in range(n):
            ans[i] = left_ans[i] * right_ans[i]
        
        return ans

101. 对称二叉树 (DFS递归 + 镜像双指针)

解题思路:

初始化两个指针均指向根节点,在 DFS 递归验证时保持"左指针向左,右指针向右"的镜像移动规则。重点控制双空、单空及值不等时的终止条件。

核心代码:

py 复制代码
from typing import Optional

# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:
    def isSymmetric(self, root: Optional[TreeNode]) -> bool:
        if root is None:
            return True

        def check(l: Optional[TreeNode], r: Optional[TreeNode]) -> bool:
            if l is None and r is None:
                return True
            if l is None or r is None:
                return False
                
            if l.val == r.val:
                left_check = check(l.left, r.right)
                right_check = check(l.right, r.left)
                return True if left_check and right_check else False
            else:
                return False
        
        return check(root, root)

131. 分割回文串 (回溯 + 左右指针)

解题思路:

start 索引控制分割起点,i 遍历控制终点。在尝试切分时,抽离出一个辅函数,使用双指针判断子串是否为回文串。如果是,才做选择并继续向下一层递归(剪枝操作)。

核心代码:

py 复制代码
from typing import List

class Solution:
    def partition(self, s: str) -> List[List[str]]:
        res = []
        path = []

        def ishuiwen(left, right):
            while left < right:
                if s[left] != s[right]:
                    return False
                left += 1
                right -= 1
            return True

        def backtrack(start):
            if start == len(s):
                res.append(path.copy())
                return
            
            for i in range(start, len(s)):
                if ishuiwen(start, i):
                    path.append(s[start:i+1])
                    backtrack(i+1)
                    path.pop()
            
        backtrack(0)
        return res
相关推荐
sell-cloud1 小时前
Prompt Engineering要凉了?Fable 5.5能“猜“意图,但银行正在封杀AI Agent——企业AI的真正护城河是什么
人工智能·prompt
Navicat中国2 小时前
Navicat 18 内置 AI 助手让数据库开发从繁琐操作迈向智能协作
大数据·人工智能·数据库开发
4SAPI3 小时前
多模型请求路由怎么设计?成本、速度和能力如何平衡
人工智能·python·gateway·php
魔乐社区3 小时前
翼芯・智算测试与适配优化专区,让国产算力“选得准、跑得通、用得好“
人工智能·开源·大模型
XCDoris5 小时前
郴州logo设计机构
大数据·人工智能·python
Goboy5 小时前
一个人带三个Agent,我用全新TRAE从0到1搭了个资讯周刊站
人工智能·trae
俊哥V5 小时前
每日 AI 研究简报 · 2026-10-19
人工智能·ai
AI_AGENT_DEV_AI5 小时前
AI英语教培智能体的开发
人工智能
物联网软硬件开发-轨物科技5 小时前
【技术白皮书】光伏清检一体化机器人系统解决方案技术白皮书
大数据·运维·网络·人工智能·机器人
gCode Teacher 格码致知5 小时前
Pandas教学-17:Series与Dataframe的关系
开发语言·python·pandas