【编程之路】面试必刷TOP101:双指针(87-94,Python实现)

【面试必刷TOP101】系列包含:


87.合并两个有序数组

87.1 合并后排序

class Solution:
    def merge(self, A, m, B, n):
        for i in range(m,m+n):
            A[i] = B[i-m]
        A.sort()
        return A

87.2 双指针

step 1:使用三个指针,i 指向数组A的最大元素,j 指向数组B的最大元素,k 指向数组A空间的结尾处。
step 2:从两个数组最大的元素开始遍历,直到某一个结束,每次取出较大的一个值放入数组A空间的最后,然后指针一次往前。
step 3:如果数组B先遍历结束,数组A前半部分已经存在了,不用管;但是如果数组A先遍历结束,则需要把数组B剩余的前半部分依次逆序加入数组A前半部分,类似归并排序最后的步骤。
在这里插入图片描述

class Solution:
    def merge(self, A, m, B, n):
        # 指向数组A的结尾
        i = m - 1
        # 指向数组B的结尾
        j = n - 1
        # 指向数组A扩容后的结尾
        k = m + n - 1
        # 从两个数组最大的元素开始,直到一个数组遍历完
        while i >= 0 and j >= 0:
            if A[i] > B[j]:
                A[k] = A[i]
                k = k - 1
                i = i - 1
            else:
                A[k] = B[j]
                k = k - 1
                j = j - 1
        # 若A遍历完了,数组B还有,则需要把B加到数组A前面
        if i < 0:
            while j >= 0:
                A[k] = B[j]
                k = k - 1
                j = j - 1
        # 若A遍历完了,数组A前面正好有,不用添加

时间复杂度: O ( n + m ) O(n+m) O(n+m),其中 m、n 分别为两个数组的长度,最坏情况遍历整个数组 A 和数组 B。
空间复杂度: O ( 1 ) O(1) O(1),常数级变量,无额外辅助空间。

88.判断是否为回文字符串

88.1 双指针

class Solution:
    def judge(self , str: str) -> bool:
        a = list(str)
        i = 0
        j = len(a) - 1
        while i < j:
            if a[i] == a[j]:
                i = i + 1
                j = j - 1
            else:
                return False
        return True

时间复杂度:O(n),其中 n 为字符串长度,最多遍历半个字符串。
空间复杂度:O(1),除了常数个临时变量,无额外辅助空间。

不使用双指针其实也可以,主要是前后一起遍历的思想,但双指针节省一半时间。

class Solution:
    def judge(self , str: str) -> bool:
        for i in range(len(str)):
            if str[i] != str[len(str)-1-i]:
                return False    
        return True

88.2 反转字符串比较法

class Solution:
    def judge(self , str: str) -> bool:
        a = str
        str = str[::-1]
        if a == str:
            return True
        return False

时间复杂度:O(n),反转字符串和比较字符串都是 O(n)。
空间复杂度:O(n),辅助字符串 a 记录原来的字符串。

89.合并区间

89.1 排序+贪心

step 1:既然要求重叠后的区间按照起点位置升序排列,我们就将所有区间按照起点位置先进行排序。使用 sort 函数进行排序,重载比较方式为比较 interval 结构的 start 变量。
step 2:排序后的第一个区间一定是起点值最小的区间,我们将其计入返回数组 res,然后遍历后续区间。
step 3:后续遍历过程中,如果遇到起点值小于 res 中最后一个区间的末尾值的情况,那一定是重叠,取二者最大末尾值更新 res 中最后一个区间即可。
step 4:如果遇到起点值大于 res 中最后一个区间的末尾值的情况,那一定没有重叠,后续也不会有这个末尾的重叠区间了,因为后面的起点只会更大,因此可以将它加入 res。
在这里插入图片描述
cmp_to_key 是 functools 库里的一个函数,它可以配合 sort 与 sorted 完成自定义排序的功能。

# class Interval:
#     def __init__(self, a=0, b=0):
#         self.start = a
#         self.end = b

from functools import cmp_to_key

class Solution:
    def merge(self , intervals: List[Interval]) -> List[Interval]:
        res = []
        if len(intervals) == 0:
            return res
        # 按照区间首排序
        intervals.sort(key=cmp_to_key(lambda a,b: a.start - b.start))
        # 放入第一个区间
        res.append(intervals[0])
        # 遍历后续区间,查看是否与末尾有重叠
        for i in range(len(intervals)):
            # 区间有重叠,更新结尾
            if intervals[i].start <= res[-1].end:
                res[-1].end = max(res[-1].end, intervals[i].end)
            # 区间没有重叠,直接加入
            else:
                res.append(intervals[i])
        return res

时间复杂度: O ( n l o g 2 n ) O(nlog2n) O(nlog2n),排序的复杂度为 O ( n l o g 2 n ) O(nlog2n) O(nlog2n),后续遍历所有区间的复杂度为 O ( n ) O(n) O(n),属于低次幂,忽略。
空间复杂度: O ( 1 ) O(1) O(1),res 为返回必要空间,没有使用额外辅助空间。

90.最小覆盖子串

90.1 滑动窗口+哈希表

step 1:建立哈希表,遍历字符串 T,统计各个字符出现的频率,频率计为负数。
step 2:依次遍历字符串 S,如果匹配则将哈希表中的相应的字符加 1。
step 3:在遍历过程中维护一个窗口,如果哈希表中所有元素都大于 0,意味着已经找全了,则窗口收缩向左移动,找最小的窗口,如果不满足这个条件则窗口右移继续匹配。窗口移动的时候需要更新最小窗口,以取得最短子串。
step 4:如果匹配到最后,窗口 left(初始为-1)也没有右移,说明没有找到,返回空串即可。
step 5:最后使用字符串截取函数,截取刚刚记录下的窗口即可得到符合条件的最短子串。
在这里插入图片描述

class Solution:
    # 检查是否有小于0的
    def check(self, dic:dict()):
        for key,value in dic.items():
            if value < 0:
                return False
        return True
            
    def minWindow(self , S: str, T: str) -> str:
        # cnt 其实就是最小窗口的大小
        cnt = len(S) + 1
        dic = dict()
        # 初始化哈希表都为负数
        for i in range(len(T)):
            if T[i] in dic:
                dic[T[i]] -= 1
            else:
                dic[T[i]] = -1
        # slow、fast 用作移动的双指针
        slow = 0
        fast = 0
        # left、right 用于记录最小窗口的左右边界
        left = -1
        right = -1
        while fast < len(S):
            c = S[fast]
            if c in dic:
                dic[c] += 1
            # 没有小于 0 的说明都覆盖了
            while self.check(dic):
                # 维护最小窗口
                if fast - slow + 1 < cnt:
                    cnt = fast - slow + 1
                    left = slow
                    right = fast
                # 缩小窗口
                c = S[slow]
                if c in dic:
                    dic[c] -= 1
                slow += 1
            fast += 1
        if left == -1:
            return ''
        return S[left:right+1]

时间复杂度: O ( C ∗ n S + n T ) O(C*n_S+n_T) O(CnS+nT),其中 C C C T T T 字符串的字符集大小,本题中为 52 个字母, n S n_S nS 为字符串 S S S 的长度, n T n_T nT 为字符串 T T T 的长度。
空间复杂度: O ( C ) O(C) O(C),哈希表长度不会超过字符串 T T T 的字符集大小。

91.反转字符串

91.1 内置方法

class Solution:
    def solve(self , str: str) -> str:
        return str[::-1]

91.2 从后遍历

class Solution:
    def solve(self , str: str) -> str:
        res = ''
        for i in range(len(str)-1,-1,-1):
            res = res + str[i]
        return res

92.最长无重复子数组

92.1 滑动窗口

step 1:构建一个哈希表,用于统计数组元素出现的次数。
step 2:窗口左右界都从数组首部开始,每次窗口优先右移右界,并统计进入窗口的元素的出现频率。
step 3:一旦右界元素出现频率大于1,就需要右移左界直到窗口内不再重复,将左边的元素移除窗口的时候同时需要将它在哈希表中的频率减1,保证哈希表中的频率都是窗口内的频率。
step 4:每轮循环,维护窗口长度最大值。
在这里插入图片描述

class Solution:
    def maxLength(self , arr: List[int]) -> int:
        num = 0
        dic = dict() # 哈希表用于记录窗口内非重复的数字
        i = 0
        for j in range(len(arr)):
            # 窗口右移,哈希表统计次数
            if arr[j] in dic:
                dic[arr[j]] += 1
            else:
                dic[arr[j]] = 1
            while dic[arr[j]] > 1:
                # 窗口左移,同时减去该数字出现的次数
                dic[arr[i]] -= 1
                i = i + 1
            num = max(num, j-i+1)
        return num

时间复杂度:O(n),外循环窗口右界从数组首右移到数组尾,内循环窗口左界同样如此,因此复杂度
为O(n+n)=O(n)。
空间复杂度:O(n),最坏情况下整个数组都是不重复的,哈希表长度就为数组长度 n。

92.2 模拟队列

class Solution:
    def maxLength(self , arr: List[int]) -> int:
        num = 0
        a = []
        for item in arr:
            while item in a:
                a = a[1:]
            a.append(item)
            num = max(num,len(a))
        return num

93.盛水最多的容器

93.1 贪心思想

我们都知道容积与最短边长和底边长有关,最长的底边一定以首尾为边,但是首尾不一定够高,中间可能会出现更高但是底边更短的情况,因此我们可以使用对撞双指针向中间靠,这样底边长会缩短,因此还想要有更大容积只能是增加最短边长,此时我们每次指针移动就移动较短的一边,因为贪心思想下较长的一边比较短的一边更可能出现更大容积。

step 1:优先排除不能形成容器的特殊情况。
step 2:初始化双指针指向数组首尾,每次利用上述公式计算当前的容积,维护一个最大容积作为返回值。
step 3:对撞双指针向中间靠,但是依据贪心思想,每次指向较短边的指针向中间靠,另一指针不变。
在这里插入图片描述

class Solution:
    def maxArea(self , height: List[int]) -> int:
        if len(height) < 2:
            return 0
        res = 0
        # 双指针
        left = 0
        right = len(height) - 1
        while left < right:
            capacity = min(height[left],height[right]) * (right-left)
            res = max(res,capacity)
            # 优先舍弃较短的边
            if height[left] < height[right]:
                left = left + 1
            else:
                right = right - 1
        return res

时间复杂度: O ( n ) O(n) O(n),双指针共同遍历一次数组。
空间复杂度: O ( 1 ) O(1) O(1),常数级变量,没有额外辅助空间。

94.接雨水问题

94.1 双指针

我们都知道水桶的短板问题,控制水桶水量的是最短的一条板子。这道题也是类似,我们可以将整个图看成一个水桶,两边就是水桶的板,中间比较低的部分就是水桶的底,由较短的边控制水桶的最高水量。但是水桶中可能出现更高的边,比如上图第四列,它比水桶边还要高,那这种情况下它是不是将一个水桶分割成了两个水桶,而中间的那条边就是两个水桶的边。

有了这个思想,解决这道题就容易了,因为我们这里的水桶有两个边,因此可以考虑使用对撞双指针往中间靠。

step 1:检查数组是否为空的特殊情况
step 2:准备双指针,分别指向数组首尾元素,代表最初的两个边界
step 3:指针往中间遍历,遇到更低柱子就是底,用较短的边界减去底就是这一列的接水量,遇到更高的柱子就是新的边界,更新边界大小。

class Solution:
    def maxWater(self , arr: List[int]) -> int:
        if len(arr) == 0:
            return 0
        res = 0
        # 左右双指针
        left = 0
        right = len(arr) - 1
        # 中间区域的边界高度
        maxL = 0
        maxR = 0
        while left < right:
            # 每次维护往中间的最大边界
            maxL = max(maxL,arr[left])
            maxR = max(maxR,arr[right])
            # 较短的边界确定该格子的水量
            if maxL <= maxR:
                res = res + (maxL - arr[left])
                left = left + 1
            else:
                res = res + (maxR - arr[right])
                right = right - 1
        return res

时间复杂度: O ( n ) O(n) O(n),两个指针最多共同遍历整个数组。
空间复杂度: O ( 1 ) O(1) O(1),常数个变量,没有额外的辅助空间。

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

G皮T

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值