【leetcode-python刷题】双指针(对撞指针)

第一次刷leetcode,结果的执行时间用同样的程序运行出来起伏都很大,我不太懂是为什么。

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

题目

给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。

你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我的答案V1

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        i = 1
        j = len(numbers)
        answer = [0,0]
        while i < j:
            if numbers[i-1] + numbers[j-1] == target:
                answer[0] = i
                answer[1] = j
                break
            elif numbers[i-1] + numbers[j-1] > target:
                j = j - 1
            else:
                i = i + 1
        return answer

结果V1:
在这里插入图片描述

出错的地方

1、answer未初始化。
2、numbers[i-1]和numbers[j-1]未减1,因为i从1开始,而数组索引从0开始。

V2

在循环中将相等的判断条件放在了最后。

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        i = 1
        j = len(numbers)
        answer = [0,0]
        while i < j:
            if numbers[i-1] + numbers[j-1] > target:
                j = j - 1
            elif numbers[i-1] + numbers[j-1] < target:
                i = i + 1
            else:
                answer[0] = i
                answer[1] = j
                break
        return answer

结果V2:
在这里插入图片描述

V3

将numbers[i-1] + numbers[j-1]先算好赋值给sum。

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        i = 1
        j = len(numbers)
        answer = [0,0]
        while i < j:
            sum = numbers[i-1] + numbers[j-1]
            if sum > target:
                j = j - 1
            elif sum < target:
                i = i + 1
            else:
                answer[0] = i
                answer[1] = j
                break
        return answer

结果V3:
在这里插入图片描述

V4

去掉answer。

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        i = 1
        j = len(numbers)
        while i < j:
            sum = numbers[i-1] + numbers[j-1]
            if sum > target:
                j = j - 1
            elif sum < target:
                i = i + 1
            else:
                return [i, j]
        return []

结果V4:
在这里插入图片描述

633. 两数的平方和(easy)

题目

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

我的答案V1

class Solution:
    def judgeSquareSum(self, c: int) -> bool:
        a = 0
        b = int(sqrt(c))
        while a <= b:
            sum = a*a + b*b
            if sum > c:
                b = b - 1
            elif sum < c:
                a = a + 1
            else:
                return True
        if a > b:
            return False

结果:
在这里插入图片描述

V2

class Solution:
    def judgeSquareSum(self, c: int) -> bool:
        assert c >= 0 # 严谨!
        a = 0
        b = int(sqrt(c))
        while a <= b:
            sum = a*a + b*b
            if sum > c:
                b = b - 1
            elif sum < c:
                a = a + 1
            else:
                return True
        return False # 删掉不必要的判断

结果:
在这里插入图片描述

345. 反转元音字符(easy)

题目

编写一个函数,以字符串作为输入,反转该字符串中的元音字母。

我的答案V1

将所有元音字母所在位置保存在index数组中,并将字符串转换为字符数组,替换其中符合条件的元素。

class Solution:
    def reverseVowels(self, s: str) -> str:
        vowel = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U']
        index = []
        string = list(s)
        for indexi, i in enumerate(s):
            if i in vowel:
                index.append(indexi)
        print(index)
        length = len(index)-1
        for i, j in enumerate(index):
            string[j] = s[index[length-i]]
        str = ''
        return str.join(string)

结果:
在这里插入图片描述

V2

依照前面两题的思路,从字符串两头开始寻找。

class Solution:
    def reverseVowels(self, s: str) -> str:
        vowel = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U']
        ls = list(s)
        string = ls
        length = len(s)
        a = 0
        b = length-1
        while a < b:
            if s[a] not in vowel:
                a = a + 1
            elif s[b] not in vowel:
                b = b - 1
            else:
                string[a] = s[b]
                string[b] = s[a]
                a = a + 1
                b = b - 1
        str = ''
        return str.join(string)

结果:
在这里插入图片描述

V3(最佳)

class Solution:
    def reverseVowels(self, s: str) -> str:
        vowel = "aeiouAEIOU" # 改成字符串,输入方便
        ls = list(s)
        string = ls
        a, b = 0, len(s)-1 # 一起初始化
        while a < b:
            if s[a] not in vowel:
                a = a + 1
            elif s[b] not in vowel:
                b = b - 1
            else:
                ls[a], ls[b] = ls[b], ls[a] #一起交换,避免值已发生改变的问题
                a = a + 1
                b = b - 1
        return ''.join(ls) # 不需要str

在这里插入图片描述

125. 验证回文串(easy)

题目

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

我的答案V1

class Solution:
    def isPalindrome(self, s: str) -> bool:
        start = 0
        end = len(s) - 1
        while start < end:
            while not s[start].isalnum():
                if start == end:
                    break
                start = start + 1
            while not s[end].isalnum():
                if start == end:
                    break
                end = end - 1
            if s[start].upper() != s[end].upper():
                return False
            start = start + 1
            end = end - 1
        return True

结果:
在这里插入图片描述

V2

class Solution:
    def isPalindrome(self, s: str) -> bool:
        if len(s) == 1: #判断是否为单个字符
            return True
        start, end = 0, len(s) - 1
        while start < end:
            while not s[start].isalnum():
                if start == end:
                    break
                start = start + 1
            while not s[end].isalnum():
                if start == end:
                    break
                end = end - 1
            if s[start].upper() != s[end].upper():
                return False
            start = start + 1
            end = end - 1
        return True

结果:
在这里插入图片描述

680. 验证回文字符串2(easy)

题目

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

我的答案V1

class Solution:
    def validPalindrome(self, s: str) -> bool:
        flag = False
        if len(s) == 1:
            return True
        start, end = 0, len(s) - 1
        while start < end:
            if s[start] != s[end]:
                if flag:
                    break
                i, j = start, end
                start += 1
                flag = True
                continue
            start += 1
            end -= 1
        if start >= end:
            return True
        start, end = i, j-1
        flag = False
        while start < end:
            if s[start] != s[end]:
                return False
            start += 1
            end -= 1
        return True

结果:
在这里插入图片描述

V2

把出现start!=end之后的判断过程写成一个函数。

class Solution:
    def validPalindrome(self, s: str) -> bool:
        if len(s) <= 2:
            return True
        start, end = 0, len(s) - 1

        def isPalindrome(s, start, end):
            while start < end:
                if s[start] != s[end]:
                    return False
                start += 1
                end -= 1
            return True

        while start < end:
            if s[start] != s[end]:
                return isPalindrome(s, start+1, end) or isPalindrome(s, start, end-1)
            start += 1
            end -= 1
        return True

结果:
在这里插入图片描述

344. 反转字符串(easy)

题目

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-string
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我的答案V1

class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        start, end = 0, len(s)-1
        while start < end:
            s[start], s[end] = s[end], s[start]
            start += 1
            end -= 1
        return s

结果:
在这里插入图片描述

V2

class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        if not s:  # 考虑s为空字符串的情况
            return []
        start, end = 0, len(s)-1
        while start < end:
            s[start], s[end] = s[end], s[start]
            start += 1
            end -= 1
        return s

11.盛最多水的容器(medium)

题目

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器。

示例 1:
在这里插入图片描述

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/container-with-most-water
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我的答案

我的第一个想法是以固定宽度interval(interval在迭代中逐渐减小)穷举,为了缩小这个“固定宽度”的范围,先找了height中最大的两个数,得到可能的最高水位,在interval迭代的过程中,若当前interval即使乘上最高水位都不可能比当前最大面积大,则不必再减小interval了,也就是结束循环,当前最大面积即为最终要求的最大面积。

class Solution:
    def maxArea(self, height: List[int]) -> int:
        interval = len(height) - 1
        n = interval - 1
        h1, h2 = 0, 0
        # 找到最大的两个值
        for i in range(len(height)):
            if height[i] >= h2:
                if height[i] >= h1:
                    h1, h2 = height[i], h1
                else:
                    h2 = height[i]
        max_height = min(h1, h2)
        current_maxArea = 0
        if h2 == 0: # 一开始没考虑到
            return 0
        while interval > int(current_maxArea/max_height):
            start, end = 0, interval
            while end < len(height):
                current_Area = min(height[start], height[end]) * interval
                if current_Area > current_maxArea:
                    current_maxArea = current_Area
                start += 1
                end += 1
            interval -= 1
        return current_maxArea

结果很悲惨,超出时间限制了:
在这里插入图片描述

V2

后来看了双指针(对撞指针)中的方法,理解他的思路。我发现在思考这个问题的时候我一直漏掉一个关键点:水位的高度取决于两端中最矮的那个值,当我的两端从首尾开始循环时,我的目的是找到能让水位更高的值(因为宽度已经是最大的了,只有水位更高才有可能超过初始面积),所以总是让左端和右端中最矮的那个向内移动,试图在*height[start]到height[end]*之间找到更大的值使水位升高。

class Solution:
    def maxArea(self, height: List[int]) -> int:
        if len(height) < 2:
            return 0
        start, end = 0, len(height) - 1
        current_maxArea = 0
        while start < end:
            current_Area = (end - start) * min(height[start], height[end]) # end和start一开始写反了
            if current_Area > current_maxArea:
                current_maxArea = current_Area
            if height[start] < height[end]:
                start += 1
            else:
                end -= 1
        return current_maxArea

结果:
在这里插入图片描述

V3

在V2中,可以发现每次移动指针都需要计算一次面积,为了进一步减少面积的计算次数,在移动指针时一直移动到比原本值更大时才会计算面积。

class Solution:
    def maxArea(self, height: List[int]) -> int:
        if len(height) < 2:
            return 0
        start, end = 0, len(height) - 1
        current_maxArea = 0
        while start < end:
            current_Area = (end - start) * min(height[start], height[end]) # end和start一开始写反了
            if current_Area > current_maxArea:
                current_maxArea = current_Area
            min_start_end = min(height[start], height[end])
            if height[start] < height[end]:
                while height[start] <= min_start_end: # 一直移动到找到更大的值
                    start += 1
                    if start == end:
                        break
            else:
                while height[end] <= min_start_end: # 一直移动到找到更大的值
                    end -= 1
                    if start == end:
                        break
        return current_maxArea

结果:
在这里插入图片描述

V4(四个版本中最优)

更进一步的,受
盛最多水的容器(双指针法,易懂解析,图解)
的启发,V3中第8行和第12行重复比较了height[start]和height[end]的值,因此将面积计算公式放在第12行判断了大小之后。

class Solution:
    def maxArea(self, height: List[int]) -> int:
        if len(height) < 2:
            return 0
        start, end = 0, len(height) - 1
        current_maxArea = 0
        while start < end:
            min_start_end = min(height[start], height[end])
            if height[start] < height[end]:
                current_maxArea = max(current_maxArea, (end - start) * height[start])
                while height[start] <= min_start_end:
                    start += 1
                    if start == end:
                        break
            else:
                current_maxArea = max(current_maxArea, (end - start) * height[end])
                while height[end] <= min_start_end:
                    end -= 1
                    if start == end:
                        break
        return current_maxArea

结果:
在这里插入图片描述

总结

  1. 涉及数组或字符串中前后元素关系的问题考虑双指针
  2. 在最开始先考虑一些特殊情况,比如“盛水最多的容器”这道题,受限排除掉height只有一个值的情况。
  3. 断言确保输入变量符合题目所给条件,如“两数平方和”中通过assert c >= 0确保c为非负整数,这样更加严谨。
  4. 多个变量一起初始化时可以写在同一行,缩短代码长度。
  5. 交换两个数时同第4点,避免用到额外变量,如“反转元音字母”的V3版本。
  6. 需要多次实现的功能可以写成一个函数,缩短代码长度,如“验证回文字符串2”的V2版本。
  7. 减少重复的判断,如“盛水最多的容器”的V4。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值