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

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

文章目录

🔗 查看完整专栏(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 从右向左,根据条件移动 lr,直到两者相遇。

典型应用:区间翻转(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 且不重复的三元组。

解题思路

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

核心代码

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_maxright_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
相关推荐
hef2882 小时前
怎么诊断MongoDB Config Server响应极慢的问题_高频Auto-split导致的元库写入压力
jvm·数据库·python
别或许2 小时前
5、高数----一元函数微分学的应用(一)几何应用
算法
天地沧海2 小时前
关于 RAG 的十个核心问题
人工智能
河南博为智能科技有限公司2 小时前
边缘计算物联网关丨配电站房区域集中边缘计算解决方案!
人工智能·物联网·边缘计算
雷工笔记2 小时前
WMS 仓库管理系统核心功能模块全景图
人工智能·mes
颜酱2 小时前
语音合成与视觉模型api接入实现
前端·javascript·人工智能
qq_380619162 小时前
html怎么用deno运行_Deno如何作为本地服务器运行HTML文件
jvm·数据库·python
水如烟2 小时前
孤能子视角:“三线模型“,AI“不再““黑箱“?
人工智能
小鱼~~2 小时前
进程和线程
python