欢迎关注我的CSDN:https://spike.blog.csdn.net/
本文地址:https://spike.blog.csdn.net/article/details/139270999

双指针算法是一种常见且灵活的技巧,通过使用两个指针协同完成任务。这些指针可以指向不同的元素,具体应用取决于问题的性质。双指针算法的常见用法:
- 对撞指针:一左一右向中间逼近。例如,反转字符串中的元音字母问题,可以使用对撞指针。
- 快慢指针:一快一慢,步长不同。例如,判断链表中是否有环问题,可以使用快慢指针,看慢指针是否能追上快指针。单链表找中间节点问题也可以用快慢指针,快指针到链表结尾,慢指针到一半。
- 滑动窗口:类似计算机网络中的滑动窗口。一般是右端向右扩充,达到停止条件后右端不动,左端向右端逼近,逼近达到停止条件后,左端不动,右端继续扩充。
双指针算法包括:对撞指针、快慢指针、滑动窗口、双链遍历。
- 167. 两数之和 II - 输入有序数组 - 对撞指针
- 633. 平方数之和 - 对撞指针,题目167变种
- 680. 验证回文串 II - 对撞指针,需要判断去除1个字符 l+1 或 r-1
- 15. 三数之和 - 两数之和的进阶版,先排序,逐步修改序列
- 142. 环形链表 II - 快慢指针
- 76. 最小覆盖子串 - 滑动窗口,Counter类的使用
- 88. 合并两个有序数组 - 双链遍历
- 524. 通过删除字母匹配到字典里最长单词 - 双链遍历,优先排序,再依次比较。
1. 对撞指针
167. 两数之和 II - 输入有序数组 - 对撞指针
class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        """
        时间复杂度O(n),空间复杂度O(1)
        """
        # 输入的已经排序
        n=len(numbers)  # 数量
        l,r=0,n-1  # 左右指针
        while l<r:
            v=numbers[l]+numbers[r]  # 两数之和
            if v>target: # 移动指针
                r-=1
            elif v<target:
                l+=1
            else:
                return [l+1,r+1]
633. 平方数之和 - 对撞指针,167变种
class Solution:
    def judgeSquareSum(self, c: int) -> bool:
        """
        时间复杂度 O(n),空间复杂度 O(1)
        """
        # 左右指针
        l, r = 0, int(sqrt(c))
        while l <= r:  # 遍历条件需要相等
            # 计算值
            v = pow(l, 2) + pow(r, 2)
            if v > c:  # 移动右指针
                r -= 1
            elif v < c:  # 移动左指针
                l += 1
            else:
                return True
        return False
680. 验证回文串 II - 对撞指针,需要判断去除1个字符 l+1 或 r-1
class Solution:
    def validPalindrome(self, s: str) -> bool:
        """
        时间复杂度 O(n), 空间复杂度 O(1)
        """
        def check(l, r):
            """
            检查s是否是回文
            """
            while l < r:
                if s[l] == s[r]:
                    l += 1
                    r -= 1
                else:
                    return False
            return True
        n = len(s)
        l, r = 0, n-1  # 左右指针
        while l < r:
            if s[l] == s[r]:  # 回文
                l += 1
                r -= 1
            else:
                # 越过1个值,最多可以从中删除一个字符
                return check(l+1, r) or check(l, r-1)
        return True
15. 三数之和 - 两数之和的进阶版,先排序,逐步修改序列
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        """
        时间O(N^2),空间O(logN) -> 排序
        """
        def two_sum(nums, t):
            """
            经典的两数之和,同时,避免重复添加
            """
            n = len(nums)
            l, r = 0, n-1
            res = []
            while l < r:
                x = nums[l] + nums[r]
                if x > t:
                    r -= 1
                elif x < t:
                    l += 1
                else:
                    sr = [nums[l], nums[r], -t]
                    if sr not in res:  # 避免重复添加
                        res.append(sr)
                    l += 1
            return res
        
        nums.sort()  # 排序
        n = len(nums)  # 序列长度
        res = []  # 输出结果
        for i in range(n):
            # 避免重复数字
            if i > 0 and nums[i-1] == nums[i]:
                continue
            t = nums[i]  # 依次遍历
            res += two_sum(nums[i+1:], -t)
        
        return res
2. 快慢指针
142. 环形链表 II - 快慢指针
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        """
        时间复杂度O(n),空间复杂度O(1)
        """
        if not head:
            return None
        # 快慢指针先指向head
        slow=fast=head
        # 判断是否运行到结尾
        while fast.next and fast.next.next:
            slow=slow.next  # 移动1步
            fast=fast.next.next  # 移动2步
            if slow==fast:
                fast=head # fast从head开始重新计数
                while slow!=fast:  # 移动到位置
                    slow=slow.next
                    fast=fast.next
                return fast # 相等即返回
        return None
3. 滑动窗口
76. 最小覆盖子串 - 滑动窗口
class Solution:
    def minWindow(self, s: str, t: str) -> str:
        """
        时间复杂度 O(m+n),空间复杂度 O(1)
        """
        rl,rr=-1,len(s) # 目标的最大窗口
        l=0  # 左指针
        cnt_s=Counter()  # 计数器字典
        cnt_t=Counter(t) # 目标计数器字典
        less=len(cnt_t)  # 不重复的t字母数量
        for r,c in enumerate(s):
            cnt_s[c]+=1  # s计数器
            if cnt_s[c]==cnt_t[c]:  # 数量相等
                less-=1  # 数量减一
            while less==0:  # 满足条件
                if r-l < rr-rl:  # 区间更小
                    rl,rr=l,r  # 更新左右指针
                # -----
                # 如果数量相等,之后数量需要减一,所以less需要提前+1
                x=s[l]  # 左指针当前字母
                if cnt_s[x]==cnt_t[x]:  
                    less+=1  # 不重复+1
                cnt_s[x]-=1  # 指针减1
                l+=1  # 已经更新,所以需要移动左指针
                # -----
        return "" if rl<0 else s[rl:rr+1]
4. 双链遍历
88. 合并两个有序数组 - 双链指针
class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        Do not return anything, modify nums1 in-place instead.
        时间复杂度 O(n),空间复杂度 O(1)
        """
        pos=(m-1)+(n-1)+1  # 最远位置
        m-=1  # 最后位置
        n-=1  # 最后位置
        while m>=0 and n>=0:  # 遍历两个数组
            # 注意: 大于等于确保,优先移动m
            if nums1[m]>=nums2[n]:
                nums1[pos]=nums1[m]  # 赋值大值
                m-=1
            else:
                nums1[pos]=nums2[n]
                n-=1
            pos-=1  # 移动指针
        while n>=0: # 优先移动m,所以剩下的就是n
            nums1[pos]=nums2[n]
            pos-=1
            n-=1
524. 通过删除字母匹配到字典里最长单词 - 双链指针,优先排序,再依次比较。
class Solution:
    def findLongestWord(self, s: str, dictionary: List[str]) -> str:
        """
        d 是字典长度,m 是s长度
        时间复杂度 O(d x (m+n)),空间复杂度 O(d x m)
        """
        # 按目标结果排序,优先做前面的
        words = sorted(dictionary, key=lambda x: (-len(x), x))
        for t in words:
            i = j = 0  # 双指针
            while i < len(t) and j < len(s): # 双指针遍历
                if t[i] == s[j]:  # 相同
                    i += 1  # t指针+1,满足条件
                j+=1  # s指针默认都+1
                if i == len(t):  # 长度满足
                    return t  # 直接返回
        return ""



















