LeetCode之算法面试之数组3之两数之和II—输入有序数组(167)、验证回文串(125)、反转字符串(344)、反转字符串中的元音字母(345)、盛最多水的容器(11)、平方数之和(633)


————技巧点:对撞指针即左右开工

\quad \quad 对撞指针是双指针的一种。对撞指针是指在有序数组中,将指向最左侧的索引定义为左指针(left),最右侧的定义为右指针(right),然后从两头向中间进行数组遍历。对撞指针的终止条件是两个指针相遇。对撞指针常用于排序数组或者是两端交换元素位置。

1、两数之和II—输入有序数组(167)

题目描述:

【简单题】
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。

函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。

说明:

  • 返回的下标值(index1 和 index2)不是从零开始的。
  • 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

在这里插入图片描述
题目链接

思路分析

要求:在有序数组中找到两值所处索引位置+1
约束:两数相加之和等于目标值

题解一:暴力法—两层遍历

\quad \quad 最直观解法,即暴力解法,遍历数组每个元素 i,并查找除此元素之外的其余元素是否存在一个值与 target - i相等的目标元素。

  • 双层遍历:i从0到len-1,j从i+1到len-1
class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        res=[]# 存放结果
        for i in range(len(numbers)):
            for j in range(i+1,len(numbers)):
                if numbers[j]==target-numbers[i]:
                    res=[i+1,j+1]
        return res

在这里插入图片描述

注:这种方法虽然可以解答,但leetCode通不过,会超出时间限制,故此题不建议用暴力法,需另辟它径。

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)

    对于每个元素,我们试图通过遍历数组的其余部分来寻找它所对应的目标元素,这将耗费 O(n)的时间。因此时间复杂度为 O ( n 2 ) O(n^2) O(n2)

  • 空间复杂度: O ( 1 ) O(1) O(1)

\quad \quad 显然,暴力解法没有充分利用原数组的性质–有序,谈到有序数组,首先必须想到什么?二分查找!于是第二种思路就可以按照二分搜索对时间复杂度进行优化。

题解二:二分查找

复习二分查找

  • 依次来遍历每一个元素i,
    • 对于每一个i,都在剩余的有序数组中,使用二分查找的思路,寻找target-nums[i],找到即返回[i+1,mid+1],否则继续遍历。
class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        
        for i in range(len(numbers)):
            l=i+1
            r=len(numbers)
            while l<r:
                mid=l+(r-l)//2 #中间位置
                if numbers[mid]==target-numbers[i]:# 说明已找到另一值,则停止循环,返回结果
                    return [i+1,mid+1]#题目要求返回的下标值是从1开始的,而语言索引是从零开始的,故要在结果中加一
                elif numbers[mid]<target-numbers[i]:
                    l=mid+1
                else:
                    r=mid
  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
    遍历为n,二分搜索为logn,总体的时间复杂度为:nlogn
  • 空间复杂度: O ( 1 ) O(1) O(1)

题解三:对撞指针(双指针的一个特殊组合)

\quad \quad 在前几个题目中,为了避免额外开辟新的数组空间,于是用了两个指针,来做交换删除之类的操作,同样这里,为了少一层循环,我们也可以考虑多用一个指针索引。寻找两个指针索引,两个索引代表的数字和是target,因为数组有序,故其一定是一左一右存在,那么初始时两个指针l,r分别指向第一个元素位置和最后一个元素的位置。这样的双指针就可叫做对撞指针。

每次计算两个指针l,r指向的两个元素之和,并和目标值比较。

l<r,执行以下操作:(因为不可以是重复元素,所以循环条件l < r)

  • 如果两个元素之和等于目标值,则发现了唯一解

  • 如果两个元素之和小于目标值,则将左侧指针右移一位。即让l++,由于整个数组有序,故这个位置会比原来位置的值更大;

  • 如果两个元素之和大于目标值,则将右侧指针左移一位即r--;由于有序,得到的结果会比上一次更小。

  • 移动指针之后,重复上述操作,直到找到答案。

那么小套路–对撞指针,就是指的是用两个索引,向中间的方向不断前行,就能找到所给的答案。

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        l=0
        r=len(numbers)-1
        while l<r:
            if numbers[l]+numbers[r]==target:
                return [l+1,r+1]
            elif numbers[l]+numbers[r]<target:
                l+=1
            else:#numbers[l]+numbers[r]>target
                r-=1
  • 时间复杂度 O ( n ) O(n) O(n)
    其中 n 是数组的长度。两个指针移动的总次数最多为 n 次。
  • 空间复杂度 O ( 1 ) O(1) O(1)

2、验证回文串(125)

题目描述:

【简单题】

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

说明:本题中,我们将空字符串定义为有效的回文串。
在这里插入图片描述
题目链接

思路分析

要求:判断是否是回文串?

什么是回文串?

\quad \quad 回文串就是指一串字符,无论是从前往后读或者从后往前读都是一样的。也就是说倒数第一与正数第一、倒数第二与正数第二,…是一样的。比如说“wow”,“level”,不会因为读的前后顺序而不同。

\quad \quad 对于回文串问题,即比较左右两端相对应位置的元素是否相同,就可以利用左右对撞指针的思想,设前后两个指针,一个指针从头进行,一个指针从尾部进行。依次判断两个指针的字符是否相等,只要有一个不相等,就返回False。当两个指针相遇的时候循环停止跳出。

字符串问题中常要考虑的几个问题是:

1.空字符串怎么处理?

本题目中已经将空字符串定义为有效的回文串,故不用特殊判断处理,默认循环完返回true就可以;

2.大小写问题

本题目中忽略字母的大小写,而忽略字母大小写的套路做法是:将对应的字母统一转化为大写或小写,再比较大写或小写的字母是否相同。

3.跳过非法字符

这里指除数字或字母的其他字符。

Python中有字符串方法isalnum(),用来检测字符串是否由字母和数字组成,是的话返回true,如果没有库的话,可以用判断字符的ascii码来实现。

isalnum()函数
描述:检测字符串是否由字母和数字组成。

语法:str.isalnum()  -> bool  返回值为布尔类型(True,Falsestr中至少有一个字符且所有字符都是字母或数字则返回 True,否则返回 False
class Solution:
    def isPalindrome(self, s: str) -> bool:
        l=0       #左指针索引
        r=len(s)-1#右指针索引
        while l<r:
            # 每次while都需要判断下l<r,保证大前提正确
            while l<r and not s[l].isalnum():#左指针遇到非法字符,加一
                l+=1
            while l<r and not s[r].isalnum():#右指针遇到非法字符,减一,寻找到字母或数字那一索引
                r-=1
            if s[l].upper()!=s[r].upper():#都转化为大写字母再比较,如果不相等,则返回False
                return False
            # while循环中要改变循环变量,每次比较一对,左指针右移1,右指针左移1    
            l+=1
            r-=1
        return True
  • 时间复杂度: O ( n ) O(n) O(n),遍历了一遍数组
  • 空间复杂度: O ( 1 ) O(1) O(1)

3、反转字符串(344)

题目描述:

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

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

你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
在这里插入图片描述

题目链接
思路分析

要求:反转即倒序输出
约束:原地修改

\quad \quad 同样,使用对撞指针的思路,一个指针从前到后,一个指针从后向前,然后两个指针相互交换,当两个指针相同时跳出循环。

class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        i,j=0,len(s)-1
        while i<j:
            s[i],s[j]=s[j],s[i]
            i+=1
            j-=1
  • 时间复杂度 O ( N ) O ( N ) O(N),进行了 N / 2 N / 2 N/2次的交换
  • 空间复杂度 O ( 1 ) O ( 1 ) O(1)

4、反转字符串中的元音字母(345)

题目描述:

【简单题】

编写一个函数,以字符串作为输入,反转该字符串中的元音字母
在这里插入图片描述
题目链接

思路分析

要求:仅对元音字母进行反转

1、反转两字,同样可以使用对撞指针思想进行解决。从前到后,从后到前,遇到两个元音字母的元素进行交换。
这时要注意,传入的为字符串,属于不可变对象,故要将字符串转化为list,交换后,再将list拼接为字符串

基础知识点:

1.str->list: list(str)

2.list->str:’’.join(list)

2、仅对元音字母进行反转,因此我们在对撞的时候还要判断当前两指针元素是否为元音字母:建立一个大写的元音字母表,若元素在其中,则为元音字母。

大致思路:

  • 初始化左右指针,l,r=0,len-1

  • 建立元音字母表v=['A','E','I','O','U']

  • 将字符串转化为列表形式

  • 当l<r:执行以下操作

    • 当l<r且左指针当前指向元素的大写形式不在元音字母表,l++
    • 当l<r且右指针当前指向元素的大写形式不在元音字母表,r- -
    • 交换指针元素
    • l++,r- -
  • 返回列表的字符串形式。

class Solution:
    def reverseVowels(self, s: str) -> str:
        l=0
        r=len(s)-1
        v=['A','E','I','O','U']#元音字母表
        list_s=list(s)#将字符串转换为列表的形式
        while l<r:
            while l<r and list_s[l].upper() not in v:
                l+=1
            while l<r and list_s[r].upper() not in v:
                r-=1
            list_s[l],list_s[r]=list_s[r],list_s[l]
            l+=1
            r-=1
        return ''.join(list_s)
  • 时间复杂度 O ( N ) O ( N ) O(N)
  • 空间复杂度 O ( n ) O ( n ) O(n)

5、盛最多水的容器(11)

题目描述:

【中等题】

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

说明:你不能倾斜容器,且 n 的值至少为 2。

在这里插入图片描述
在这里插入图片描述
题目链接
思路分析

要求:最大矩阵容器可容纳水的面积

\quad \quad 容器的面积与两个因素有关:

  • 容器的长度:两条垂直线的距离即输入数组所在索引的差值
  • 容器的宽度:两条垂直线其中较短一条的长度即数组中所在位置的数值的最小值。

因此,要容器面积最大化,两条垂直线的距离越远越好,两条垂直线的最短长度也要越长越好。

我们设置两个指针 left 和 right,分别指向数组的最左端和最右端。此时,两条垂直线的距离是最远的,若要下一个矩阵面积比当前面积来得大,必须要把 height[left] 和 height[right] 中较短的垂直线往中间移动,看看是否可以找到更长的垂直线。

题解:对撞指针法

  • 初始化指针:l=0,r=len-1

  • 初始化为最大容器面积为其最小小值maxarea=0

  • 当l<r:

    • 计算当前容器面积curarea=min(list[l],list[r])*(r-l)【list为数组】
    • 更新最大容器面积为当前容器面积与最大容器面积的最大值maxarea=max(maxarea,curarea)
      移动指针,找到更多可行解
    • 如果list[l]<list[r],则l++
    • 否则,r- -
  • 返回maxarea

class Solution:
    def maxArea(self, height: List[int]) -> int:
        l,r=0,len(height)-1
        maxarea=0
        while l<r:
            curarea=min(height[l],height[r])*(r-l)
            maxarea=max(maxarea,curarea)
            if height[l]<height[r]:
                l+=1
            else:
                r-=1
        return maxarea
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

6、平方数之和(633)

题目描述:

【中等题】
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a 2 + b 2 = c a^2 + b^2 = c a2+b2=c

示例1

输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5

题目链接
思路分析

1、本题和 167. 两数之和类似,只有一个明显区别:一个是和为 target,一个是平方和为 target。本题同样可以使用双指针得到两个数,使其平方和为 target。

2、本题的关键是右指针的初始化,实现剪枝,从而降低时间复杂度。设右指针为 x,左指针固定为 0,为了使 0 2 + x 2 0^2 + x^2 02+x2的值尽可能接近 target,我们可以将 x 取为 sqrt(target)。

class Solution:
    def judgeSquareSum(self, c: int) -> bool:
        i,j=0,int(c**0.5)
        while i<=j:
            if i*i+j*j==c:
                return True
            elif i*i+j*j>c:
                j-=1
            elif i*i+j*j<c:
                i+=1
        return False
  • 时间复杂度: O ( c ) O(\sqrt c) O(c )
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值