leecode题型整理之双指针

Posted WXiujie123456

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leecode题型整理之双指针相关的知识,希望对你有一定的参考价值。

双指针适用的题型的特点

双指针问题一般应用于字符串、列表等能直接访问的数据结构,且有序。让两个指针从这个数据结构开头和结尾分别遍历,使之满足条件。

或用于链表,指针速度不一。

leetcode原题

167. 两数之和 II - 输入有序数组

class Solution(object):
 def twoSum(self, numbers, target):
     """
     :type numbers: List[int]
     :type target: int
     :rtype: List[int]
     """
     #暴力,超出时间限制
     # n = len(numbers)
     # for i in range(n):
     #     for j in range(i,n):
     #         # print(j)
     #         if numbers[i] + numbers[j] == target and i != j:
     #             return [i+1,j+1]
     
     #对撞指针,和target比较,大了右指针调小,小了左指针调大
     n = len(numbers)
     i = 0
     j = n-1
     while i < j:
         if numbers[i] + numbers[j] > target:
             j -= 1
         elif numbers[i] + numbers[j] < target:
             i += 1
         elif numbers[j] == numbers[j-1] and (j-1) != i:
             j -= 1
         else:
             return [i+1,j+1]

633. 平方数之和

给定一个非负整数 c ,你要判断是否存在两个整数 ab,使得 a2 + b2 = c

class Solution(object):
    def judgeSquareSum(self, c):
        """
        :type c: int
        :rtype: bool
        """
        i = 0 
        j = int(sqrt(c))
        # print(j)
        while i <= j:
            if i * i + j * j == c:
                # print(i,j)
                return True
            elif i * i + j * j < c:
                i += 1
            else:
                j -= 1
        return False

345. 反转字符串中的元音字母

class Solution(object):
    def reverseVowels(self, s):
        """
        :type s: str
        :rtype: str
        """
        #字符串不可修改
        # str = ['a','e','i','o','u','A','E','I','O','U']
        str = 'aeiouAEIOU'
        n = len(s)
        ans = ['*'] * n
        i = 0
        j = n - 1
        while i <= j:
            if s[i] in str and s[j] in str:
                # print('交换')
                ans[j] = s[i]
                ans[i] = s[j]
                i += 1
                j -= 1
            elif s[i] not in str:
                ans[i] = s[i]
                i += 1
                # print('i++')
            elif s[j] not in str:
                ans[j] = s[j]
                j -= 1
                # print('j--')
        #列表转化成字符串的方法
        ans_str = ''.join(ans)
        return ans_str

本题中需要注意两个易错点:

  1. 字符串不能修改,故可以借助灵活的列表进行操作
  2. 列表转化成字符串的方法 ans_str = ''.join(ans)

680. 验证回文字符串 Ⅱ

给定一个非空字符串 s最多删除一个字符。判断是否能成为回文字符串。

自己最初的逻辑:

  1. 若两端相同,则指针往中间移动
  2. 两端不同时,若小下标这边的后一个元素与大下标相同,则小下表右移,且标志位置为false 模拟删除一位的操作 且仅能删除一位
  3. 两端不同时,若大下标这边的前一个元素与小下标相同,则大下表左移,且标志位置为false 模拟删除一位的操作 且仅能删除一位

自己写的版本的代码如下:

n = len(s)
        i = 0
        j = n-1
        print(n)
        flag = True #标记位,只删除一位
        while i < j:
            if s[i] == s[j]:
                i += 1
                j -= 1
            elif s[j-1] == s[i] and flag == True:#s[i+1] == s[j] and flag == True:
                # i += 1
                j -= 1
                print(i)
                flag = False
            elif s[i+1] == s[j] and flag == True:#s[j-1] == s[i] and flag == True:
                i += 1
                # j -= 1
                print(j)
                flag = False
            else:
                return False
        return True

但是形如:aucupuumuupucaacpuumuupucua,同时满足“若小下标这边的后一个元素与大下标相同”和‘大下标这边的前一个元素与小下标相同’这两种情况,大小指针哪个移动成为了问题。

和我对象交流之后,参考他的想法,不相同时,删左边一个,继续比较,若还有不相同的,退回去删右边一个,若继续后还有不相同,就说明不是回文,能够得出答案。

然后我就开始按照他的想法,设计自己的思路(太艰难了,o(╥﹏╥)o),思路如下:

  1. 若两端相同,则指针往中间移动,输出TRUE
  2. 若两端不同,则有以下情况:
    • 若s[i+1]!=s[j]且s[i]!=s[j+1],则删掉一位也不满足回文,返回False
    • 若s[i+1]=s[j]或s[i]=s[j+1],(此时提前设置标志位flag_L,flag_R设置初值为零)又要考虑如下情况:
      • s[i+1]=s[j]时,用left_i = i+1和left_j=j来记录这部分的位置信息
        • 当s[left_i ] = s[left_j],指针一直向中间移动,直到left_i >left_j,跳出循环,表明该部分满足回文特征,则整体满足回文特征,此时设置flag_L>= 1,以便后续判断
        • 当s[left_i ] != s[left_j],把flag_L置为-1,跳出循环
      • s[i]=s[j-1]时,用right_i = i和right_j=j-1来记录这部分的位置信息
        • 当s[right_i ]=s[right_j],指针一直向中间移动,直到right_i >right_j,跳出循环,表明该部分满足回文特征,则整体满足回文特征,此时设置flag_R>= 1,以便后续判断
        • 当s[right_i ]!=s[right_j],把flag_R置为-1,跳出循环
      • 当flag_L或flag_R大于等于1时,整体字符满足删去一位后是回文字符串,返回TRUE
      • 当flag_L和flag_R都为-1时,整体才不满足题目条件,返回False

但是上述思路并不完备,没有考虑到s[i+1] != s[j] and flag_R == -1s[i] != s[j-1] and flag_L == -1这两种情况,导致程序成了死循环。此外,在进入内层的while循环前,应该利用标志位flag_L和flag_R,判断是否已经进行过这遍循环了(对应代码中elif s[i+1] == s[j] and flag_L == 0:#保证只进去一次)

class Solution(object):
    def validPalindrome(self, s):
        """
        :type s: str
        :rtype: bool
        """
        n = len(s)
        i = 0
        j = n-1
        flag_L = 0 #标记删除左侧一位能否满足是回文
        flag_R = 0 #标记删除右侧一位能否满足是回文
        while i < j:
            if s[i] == s[j]:
                i += 1
                j -= 1
            elif s[j-1] != s[i] and s[i+1] != s[j]:
                return False
            elif flag_L>=1 or flag_R>=1:
                return True  
            elif flag_L==-1 and flag_R==-1:#和初始状态0区别开来
                return False
            elif s[i+1] == s[j] and flag_L == 0:#保证只进去一次
                left_i = i + 1
                left_j = j
                flag_L = 1
                while left_i <= left_j:
                    if s[left_i] == s[left_j]:
                        left_i += 1
                        left_j -= 1
                        flag_L += 1
                    else:
                        flag_L = -1
                        break      
            elif s[j-1] == s[i] and flag_R ==0 :
                right_j = j - 1
                right_i = i
                flag_R = 1
                while right_i <= right_j:
                    if s[right_i] == s[right_j]:
                        right_i += 1
                        right_j -= 1 
                        flag_R += 1
                    else:  
                        flag_R = -1
                        break
            elif s[i+1] != s[j] and flag_R == -1:
                return False
            elif s[i] != s[j-1] and flag_L == -1:
                return False 
        return True

自己的思路完全就是用了穷举法,试图遍历所有可能的情况。但是人的智慧是有限的,可能没办法想全面所有情况。而且,我对象指出,使用flag这种标志位,增加了思考的难度。下面我列出他对我的改进方法。

改进方法的思路:

  1. 当s[left]=s[right],指针往中间移动,返回TRUE
  2. 当s[left]!=s[right],设置一个标志计数器cnt=1:
    • 当cnt = 0时,说明存在过删除一位的操作,且删除一位后仍不满足回文,返回False
    • 当s[left] = s[right-1]且 s[left+1]=s[right]时,说明删掉左边一位或者右边一位均可满足题目要求,这里巧妙的是用了or操作,先判断删除左边一位后剩余字符串是否满足,满足则不会进行or后面的操作(即发生短路),前面不满足为真时,才会进行后面的操作,or两边全为False整体才为False。他的这个步骤直接替代了我上面的若干步骤。
    • 接下来讨论其他情况:
      • 当s[left] == s[right-1]时,right指针直接减1就行,然后cnt也相应减1
      • 否则,当 s[left+1] == s[right]时,left指针直接加1就行,然后cnt也相应减1
      • 否则,即满足s[left] != s[right-1]且 s[left+1]!=s[right],此时此字符串直接不符合回文要求,返回False
class Solution:
    def validPalindrome(self, s: str) -> bool:
        n = len(s)
        left,right = 0,n-1
        cnt = 1
        def huiwen(s): #可在函数内定义函数,此处定义一个判断s是否是回文字符串的函数
            i,j = 0,len(s)-1
            while i <= j:
                if s[i] != s[j]:
                    return False
                i += 1
                j -= 1
            return True
        
        while left <= right:
            if s[left]!=s[right]:
                if cnt == 0:
                    return False
                if s[left] == s[right-1] and s[left+1]==s[right]:
                    return huiwen(s[left:right]) or huiwen(s[left+1:right+1])
                if s[left] == s[right-1]:
                    right -= 1
                elif s[left+1] == s[right]:
                    left += 1
                else:
                    return False
                cnt -= 1
            left += 1
            right -= 1
        return True

在这部分程序实现中,我不仅学到了我方法的优化方法,还学会使用函数封装一些操作(huiwen()),使程序更简练。

然后我还学习到,python中函数内可以再定义函数,当然huiwen()也可以定义为类函数,或者类外函数。

huiwen()定义为类函数:

class Solution:
    def huiwen(self,s:str): 
            i,j = 0,len(s)-1
            while i <= j:
                if s[i] != s[j]:
                    return False
                i += 1
                j -= 1
            return True
    def validPalindrome(self, s: str) -> bool:
        n = len(s)
        left,right = 0,n-1
        cnt = 1
        while left <= right:
            if s[left]!=s[right]:
                if cnt == 0:
                    return False
                if s[left] == s[right-1] and s[left+1]==s[right]:
                    return self.huiwen(s[left:right]) or self.huiwen(s[left+1:right+1])
                if s[left] == s[right-1]:
                    right -= 1
                elif s[left+1] == s[right]:
                    left += 1
                else:
                    return False
                cnt -= 1
            left += 1
            right -= 1
        return True

huiwen()也作为类外函数:

def huiwen(s): 
        i,j = 0,len(s)-1
        while i <= j:
            if s[i] != s[j]:
                return False
            i += 1
            j -= 1
        return True
class Solution:
    def validPalindrome(self, s: str) -> bool:
        n = len(s)
        left,right = 0,n-1
        cnt = 1
        while left <= right:
            if s[left]!=s[right]:
                if cnt == 0:
                    return False
                if s[left] == s[right-1] and s[left+1]==s[right]:
                    return huiwen(s[left:right]) or huiwen(s[left+1:right+1])
                if s[left] == s[right-1]:
                    right -= 1
                elif s[left+1] == s[right]:
                    left += 1
                else:
                    return False
                cnt -= 1
            left += 1
            right -= 1
        return True

huiwen()定义为静态函数(静态函数关键字@staticmethod,定义时不需要self默认参数,但是类内调用时需要self.huiwen()):

class Solution:
    @staticmethod
    def huiwen(s): 
        i,j = 0,len(s)-1
        while i <= j:
            if s[i] != s[j]:
                return False
            i += 1
            j -= 1
        return True
    def validPalindrome(self, s: str) -> bool:
        n = len(s)
        left,right = 0,n-1
        cnt = 1
        while left <= right:
            if s[left]!=s[right]:
                if cnt == 0:
                    return False
                if s[left] == s[right-1] and s[left+1]==s[right]:
                    return self.huiwen(s[left:right]) or self.huiwen(s[left+1:right+1])
                if s[left] == s[right-1]:
                    right -= 1
                elif s[left+1] == s[right]:
                    left += 1
                else:
                    return False
                cnt -= 1
            left += 1
            right -= 1
        return True

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.
        """
        n1 = len(nums1)
        n2 = len(nums2)
        zong = m + n -1
        i = m -1
        j = n - 1
        while i >= 0 and j >= 0:
            if nums1[i] > nums2[j]:
                nums1[zong] = nums1[i]
                i -= 1
            else:
                nums1[zong] = nums2[j]
                j -= 1
            zong -= 1
        while i >= 0:
            nums1[zong] = nums1[i]
            i -= 1
            zong -= 1
        while j >= 0:
            nums1[zong] = nums2[j]
            j -= 1
            zong -= 1

还可以优化:

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.
        """
        n1 = len(nums1)
        n2 = len(nums2)
        zong = m + n -1
        i = m - 1
        j = n -以上是关于leecode题型整理之双指针的主要内容,如果未能解决你的问题,请参考以下文章

力扣刷题之双指针(C++&Java)

leetcode刷题之双指针(C++&Java)

LeetCode小白算法成长记之双指针

LeetCode刷题总结之双指针法

算法之双指针(共同点:核心逻辑思路:即先找到比较小的区域(例如决定了存水量),然后在比较小的区域中找到一个最大值))~盛最多水的容器~~~接雨水

基础算法(数据结构笔试复测Leecode牛客)