leetcode对撞双指针

137 阅读4分钟

167.两数之和(简单)

题目描述:在有序数组中找出两个数,使它们的和为 target。

思路:

使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。

如果两个指针指向元素的和 sum == target,那么得到要求的结果;

如果 sum > target,移动较大的元素,使 sum 变小一些;

如果 sum < target,移动较小的元素,使 sum 变大一些。

数组中的元素最多遍历一次,时间复杂度为 O(N)。只使用了两个额外变量,空间复杂度为 O(1)。

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 []

633.两数平方和(简单)

题目描述:判断一个非负整数是否为两个整数的平方和。

思路:

对于给定的非负整数c, 需要判断是否存在整数a和b,使得a2+b2=ca^2+b^2=c 。可以枚举a和b所有的情况,时间复杂度为O(c2)O(c^2),但是暴力的枚举法实在没必要,因此我们可以用双指针法来解决。

因为最多只需要遍历一次 0~sqrt(target),所以时间复杂度为 Osqrt(c)sqrt(c)又因为只使用了两个额外的变量,因此空间复杂度为 O(1)。

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 
        return False 

345.反转元音字符(简单)

给你一个字符串 s ,仅反转字符串中的所有元音字母,并返回结果字符串。 元音字母包括 'a''e''i''o''u',且可能以大小写两种形式出现。

使用双指针,一个指针从头向尾遍历,一个指针从尾到头遍历,当两个指针都遍历到元音字符时,交换这两个元音字符。

时间复杂度为 O(N):只需要遍历所有元素一次

空间复杂度 O(1):只需要使用两个额外变量

class Solution:
    def reverseVowels(self, s: str) -> str:
        vowel = "aeiouAEIOU" 
        ls = str(list(s)) 
        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) 

125.验证回文串(简单)

给定一个字符串,验证其是否是回文串,只考虑字母和数字字符,可以考虑字母的大小写。 本题中,我们将空字符串定义为有效的字符串。

思路: 所谓的回文字符串,是指具有左右对称特点的字符串,例如 "abcba" 就是一个回文字符串。 使用双指针可以很容易判断一个字符串是否是回文字符串:

  • 设i和j两个指针,一个指向首元素,一个指向尾元素。
  • 遍历字符串,当i和j指针指向字符都为字母或数字时进行比较,若比较结果为false,直接返回false,结束程序,否则继续遍历。
  • 当i>=j时,此时说明字符串遍历并比较完毕,直接返回true就行。
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
# 优化后:
class Solution:
    def isPalindrome(self, s: str) -> bool:
        sgood = "".join(i) for i in s if isalnum())
        n = len(sgood)
        start, end = 0, n-1
        
        while start < end:
            if sgood[start].lower != sgood[end].lower:
                return False
            start, end = start + 1, end - 1
        return True

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

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

思路: 同 125.验证回文串

本题的关键是处理删除一个字符。在使用双指针遍历字符串时,如果出现两个指针指向的字符不相等的情况,我们就试着删除一个字符,再判断删除完之后的字符串是否是回文字符串。

在判断是否为回文字符串时,我们不需要判断整个字符串,因为左指针左边和右指针右边的字符之前已经判断过具有对称性质,所以只需要判断中间的子字符串即可。

在试着删除字符时,我们既可以删除左指针指向的字符,也可以删除右指针指向的字符。

class Solution:
    def validPalindrome(self, s: str) -> bool:
        if len(s) <= 2:
            return True
        start, end = 0, len(s) - 1
        
        def isPalindrome(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]:
                start += 1
                end -= 1
            else:
                return isPalindrome(start+1, end) or isPalindrome(start, end-1)
        return True

344.反转字符串(简单)

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 说明: 本题中,我们将空字符串定义为有效的回文串。

  1. 定义左右指针,左指针指向字符串头, 右指针指向字符串尾;
  2. 当左指针所指的下标小于右指针所指的下标时,进入循环, 否则结束循环
  3. 交换左右指针指向的元素之后,左指针加1,右指针减1, 继续步骤1,2, 直至交换完成
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题, 盛最多水的容器(简单)

左右指针分别确定左右边界,两个指针都向中间移动

此时形成的最大面积为 min(height[left], height[right]) * (right - left);

即两个边界的较小值 与 边界间距离 的乘积;

这时遇到问题,需要移动左指针还是右指针呢?一旦移动指针,意味着上一个指针确定的边界被丢弃,首先我们知道 此时区域的最大面积为 两个边界的较小值 与 边界间距离 的乘积;

随着指针的移动,边界间的距离必然减小,想要使围成的区域面积变大,我们需要通过移动边界值较小的指针,来使面 积往可能增大的方向转变。

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