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 - 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
        if j >= 0:
            nums1[:j+1] = nums2[:j+1]

141. 环形链表

判断双指针的一般方法:

使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        p = head
        if head == None or head.next == None :
            return False
        q = head.next
        while p != None and q != None and q.next != None:
            if p == q:
                return True
            p = p.next
            q = q.next.next
        return False

524. 通过删除字母匹配到字典里最长单词

自己写法:

class Solution:
    def findLongestWord(self, s: str, dictionary: List[str]) -> str:
        n = len(s)
        def isin(content:str,s:str):
            i = 0
            j = 0
            while i < len(s) and j < len(content):
                if s[i] == content[j]:
                    i += 1
                    j += 1
                else:
                    i += 1
            if j == len(content):
                return True
            else:
                return False
        ans = {}
        for idx in range(len(dictionary)):
            if isin(dictionary[idx],s):
                ans[dictionary[idx]]=len(dictionary[idx])
        ans_strmax = max(ans.values())
        if ans_strmax == '':
            return ''
        for i in ans.keys():
            if ans[i] == ans_strmax:
                return i

上述写法只是实现了找长度最长且在dictionary中下标最小的字符串,而题目要求是字典序最小,我的写法显然不符合要求。

请教了我对象,他给出以下做法:

class Solution:
    def findLongestWord(self, s: str, dictionary: List[str]) -> str:
        # 长度降序,相同按照字典序
        for d in sorted(dictionary,key=lambda x: (-len(x), x)):
            i,j = 0,0
            # 同向双指针
            n,m = len(d),len(s)
            while i < n and j < m:
                if d[i] == s[j]:
                    i += 1
                if i == n:
                    return d
                j += 1
            if i == n:
                 return d
        return ""

其中,对sorted()函数的用法不是很熟悉

sorted(iterable\[, key][, reverse])

iterable中的项目返回新的排序列表。

有两个可选参数,必须指定为关键字参数。

key指定一个参数的函数,用于从每个列表元素中提取比较键:key=str.lower。默认值为 None(直接比较元素)。

reverse 是一个布尔值。如果设置为 True,那么列表元素将按照每个比较反转进行排序。

匿名函数lambda的使用也不太会:

在Python中,lambda的语法是形式:

lambda argument_list: expression

lambda是Python预留的关键字,argument_list和expression由用户自定义。

这里的argument_list是参数列表。它的结构与Python中函数(function)的参数列表是一样的。

lambda函数三个特性

**lambda函数是匿名的:**所谓匿名函数,通俗地说就是没有名字的函数。lambda函数没有名字。

**lambda函数有输入和输出:**输入是传入到参数列表argument_list的值,输出是根据表达式expression计算得到的值。

**lambda函数一般功能简单:**单行expression决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能。由于其实现的功能一目了然,甚至不需要专门的名字来说明

lambda函数的用法扩展为以下几种:

1.将lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数。

执行语句add=lambda x, y: x+y,定义了加法函数lambda x, y: x+y,并将其赋值给变量add,这样变量add便成为具有加法功能的函数。例如,执行add(1,2),输出为3。

2.将lambda函数赋值给其他函数,从而将其他函数用该lambda函数替换。

为了把标准库time中的函数sleep的功能屏蔽(Mock),我们可以在程序初始化时调用:time.sleep=lambda x:None。这样,在后续代码中调用time库的sleep函数将不会执行原有的功能。例如,执行time.sleep(3)时,程序不会休眠3秒钟,而是什么都不做。

3.将lambda函数作为其他函数的返回值,返回给调用者。

函数的返回值也可以是函数。例如return lambda x, y: x+y返回一个加法函数。这时,lambda函数实际上是定义在某个函数内部的函数,称之为嵌套函数,或者内部函数。对应的,将包含嵌套函数的函数称之为外部函数。内部函数能够访问外部函数的局部变量,这个特性是闭包(Closure)编程的基础

4.将lambda函数作为参数传递给其他函数。

filter函数:

此时lambda函数用于指定过滤列表元素的条件。例如filter(lambda x: x % 3 == 0, [1, 2, 3])指定将列表[1,2,3]中能够被3整除的元素过滤出来,其结果是[3]。

sorted函数:

此时lambda函数用于指定对列表中所有元素进行排序的准则。例如sorted([1, 2, 3, 4, 5, 6, 7, 8, 9], key=lambda x: abs(5-x))将列表[1, 2, 3, 4, 5, 6, 7, 8, 9]按照元素与5距离从小到大进行排序,其结果是[5, 4, 6, 3, 7, 2, 8, 1, 9]。

map函数:

此时lambda函数用于指定对列表中每一个元素的共同操作。例如map(lambda x: x+1, [1, 2,3])将列表[1, 2, 3]中的元素分别加1,其结果[2, 3, 4]。

reduce函数:

此时lambda函数用于指定列表中两两相邻元素的结合条件。例如reduce(lambda a, b: ‘{}, {}’.format(a, b), [1, 2, 3, 4, 5, 6, 7, 8, 9])将列表 [1, 2, 3, 4, 5, 6, 7, 8, 9]中的元素从左往右两两以逗号分隔的字符的形式依次结合起来,其结果是’1, 2, 3, 4, 5, 6, 7, 8, 9’。

在本次所使用的sorted函数中,sorted(dictionary,key=lambda x: (-len(x), x)),含义是将dictionary中元素按照长度len(x)降序排序,若长度相同按照x排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值