Datawhale编程实践LeetCode分类练习——Task04:查找2之对撞指针/滑动窗口(Python)

对撞指针

1. 两数之和

1. 两数之和(简单)
在这里插入图片描述
方法一: 暴力法
遍历数组
再遍历该元素前的所有元素(此处可以优化)

方法二:
借助字典保存遍历过的元素及其下标。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        if not nums:
            return [-1,-1]
        
        visited = {}
        for i,num in enumerate(nums):
            t = target-num
            if t in visited:
                return [i,visited[t]]
            visited[num] = i
        
        return [-1,-1]

复杂度分析:
时间复杂度:O(N),最坏情况下遍历一遍数组
空间复杂度:O(N),字典

方法三: 排序+对撞指针
如果数组是有序的,就可以采用对撞指针求解;此处需要用到下标,因此排序前需要对下标和元素的对应关系进行存储。

通过enumerate来把索引和值进行绑定,进而对value进行sort,前后对撞指针进行返回。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        nums = list(enumerate(nums))
        nums.sort(key=lambda x:x[1])
        l,r = 0,len(nums)-1
        while l<r:
            if nums[l][1]+nums[r][1]==target:
                return nums[l][0],nums[r][0]
            elif nums[l][1]+nums[r][1]>target:
                r -= 1
            else:
                l += 1
        return []

15. 三数之和

15. 三数之和(中等)
在这里插入图片描述
审题:
1、数组无序
2、不能包含重复的三元组(值重复)
3、多个解的顺序不需要考虑
4、没有解时返回空列表

思路:
固定一个元素,对之后的元素可以用2sum的思路来解决——会出现重复元素
考虑排序(要求返回值,不需要考虑下标对应的问题)

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        res = []
        for i in range(len(nums)-2):
            if nums[i]>0:
                return res #如果最小值都大于0了,就肯定不存在三个数和为0
            if i>0 and nums[i]==nums[i-1]: #去重复值
                continue
            #对撞指针
            l = i+1
            r = len(nums)-1
            while l<r:
                S = nums[i]+nums[l]+nums[r]
                if S==0:
                    res.append([nums[i],nums[l],nums[r]])
                    l += 1
                    r -= 1
                    while l<r and nums[l]==nums[l-1]:#去重复值
                        l += 1
                    while l<r and nums[r]==nums[r+1]:#去重复值
                        r -= 1
                elif S>0:
                    r -= 1
                else:
                    l += 1
        return res

Tips: 处理重复值的套路:先转换为有序数组,再循环判断其与上一次值是否重复:

# 1.外循环
for i in range(len(nums)):
    if i > 0 and nums[i] == nums[i-1]: continue
# 2.内循环
while l < r:
    while l < r and nums[l] == nums[l-1]: l += 1

18. 四数之和

18. 四数之和(中等)
在这里插入图片描述
与3Sum思路类似

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()
        res = []
        n = len(nums)
        for i in range(n-3):
            if nums[i]*4>target:
                break
            if i>0 and nums[i]==nums[i-1]:
                continue
            for j in range(i+1,n-2):
                if (nums[i]+nums[j])*2>target:
                    break
                if j>i+1 and nums[j]==nums[j-1]:#注意j>i+1
                    continue
                l = j+1
                r = n-1
                while l<r:
                    S = nums[i]+nums[j]+nums[l]+nums[r]
                    if S==target:
                        res.append([nums[i],nums[j],nums[l],nums[r]])
                        l += 1
                        r -= 1
                        while l<r and nums[l]==nums[l-1]:
                            l+=1
                        while l<r and nums[r]==nums[r+1]:
                            r-=1
                    elif S>target:
                        r -= 1
                    else:
                        l += 1
        return res

16. 最接近的三数之和

16. 最接近的三数之和(中等)
在这里插入图片描述

class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:

        nums.sort()#排序后才能使用对撞指针
        n = len(nums)

        dis = 9999#记录最小的差
        num = 9999#记录差最小的和,即返回值

        for i in range(n):
            l = i+1
            r = n-1
            while l<r:
                S = nums[i]+nums[l]+nums[r]
                if S==target:
                    return target
                elif S>target:
                    if S-target<dis:
                        dis = S-target
                        num = S
                    r -= 1
                else:
                    if target-S<dis:
                        dis = target-S
                        num = S 
                    l += 1
        return num

其他

454. 四数相加 II(查找表)

454. 四数相加 II(中等)
在这里插入图片描述
方法一: 查找表
时间复杂度:O(N3)
超时

class Solution:
    def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
        from collections import Counter
        dic = Counter(D)

        res = 0
        for a in A:
            for b in B:
                for c in C:
                    t = 0-a-b-c
                    if t in dic:
                        res += dic[t]
        
        return res

方法二:
把数组A和数组B中能得到的所有和都放入查找表
时间复杂度可以优化为O(N2)

class Solution:
    def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
        
        dic = {}
        for c in C:
            for d in D:
                t = c+d 
                if t in dic:
                    dic[t] += 1
                else:
                    dic[t] = 1

        res = 0
        for a in A:
            for b in B:
                t = 0-a-b
                if t in dic:
                    res += dic[t]
        
        return res

49. 字母异位词分组(字典)

49. 字母异位词分组(中等)
在这里插入图片描述

对于异位字符串
1、异位字符串中包含的字符串的字母个数都是相同的;
2、异位词排序后的字符串也是相同的。

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:

        dic = {}
        for word in strs:
            key = ''.join(sorted(word))#sorted(word):list, key:str
            if key in dic:
                dic[key].append(word)
            else:
                dic[key]=[word]
        return list(dic.values())

447. 回旋镖的数量(查找表)

447. 回旋镖的数量(简单)
在这里插入图片描述
距离: 对于距离值的求算,按照欧式距离的方法进行求算的话,容易产生浮点数,可以将根号去掉,用差的平方和来进行比较距离。
查找表: i,j两点距离等于i,k时,等价于:对key(i,j或i,k的距离),其值为2
做一个查找表,查找距离key的个数value是多少

在拿到对于元素i的距离查找表后,接下来就是排列选择问题了:
1、如果当距离为x的值有2个时,那么选择j,k的可能情况有:第一次选择有2种,第二次选择有1种,为21;
2、如果当距离为x的值有3个时,那么选择j,k的可能的情况有:第一次选择有3种,第二次选择有2种,为3
2;
3、那么当距离为x的值有n个时,选择j,k的可能情况有:第一次选择有n种,第二次选择有n-1种,为n*(n-1)

class Solution:
    def numberOfBoomerangs(self, points: List[List[int]]) -> int:
        res = 0

        n = len(points)

        for i in range(n):
            dic = {}#对于元素i的距离查找表
            for j in range(n):
                if i!=j:
                    d = self.dis(points[i],points[j])
                    if d in dic:
                        dic[d] += 1
                    else:
                        dic[d] = 1
            for k,v in dic.items():
                res += v*(v-1)
        return res

    def dis(self,p1,p2):
        return (p1[0]-p2[0])**2+(p1[1]-p2[1])**2

149. 直线上最多的点数(查找表)

149. 直线上最多的点数(困难)
在这里插入图片描述
与447类似,不同之处在于:
查找相同斜率key的个数value是多少

  • 除数,不够精确(eg.[[0,0],[94911150,94911151],[94911151,94911152]])
  • 最大约数方法(gcd), 把他化成最简形式, 3/6 = 2/4 = 1/2

取max,+1

注意一些细节问题:
1、求斜率时,存在分母为0的情况,需要额外考虑;
2、记录重复的点,最后取v+same的值作为结果数;全是相同值,返回same
3、边界情况,两点确定一条直线,因此数组长度小于3时,返回数组长度即可

class Solution:
    def maxPoints(self,points):
        #求最大公约数
        def gcd(x,y):
            if y==0:
                return x
            else:
                return gcd(y,x%y)
        
        if len(points) <= 1:
            return len(points)
        res = 0
        from collections import defaultdict
        for i in range(len(points)):
            slope = defaultdict(int)
            samepoint = 0
            for j in range(len(points)):
                if points[i][0] == points[j][0] and points[i][1] == points[j][1]:
                    samepoint += 1 #相同的点
                else:
                    dx = points[i][0] - points[j][0]
                    dy = points[i][1] - points[j][1]
                    g = gcd(dy,dx)
                    if g!=0:
                        dy //= g
                        dx //= g
                        slope["{}/{}".format(dy, dx)] += 1
                    else:
                        slope['a'] += 1#dx=0的情况
            for v in slope.values():
                res = max(res, v+samepoint)
            res = max(res, samepoint)#全部都是相同点的情况
        return res

滑动窗口

滑动窗口代码框架(来自labuladong)

int left = 0, right = 0;
while (right < s.size()) {
	window.add(s[right]);
	right++;
	while (valid) {
		window.remove(s[left]);
		left++;
	}
}

219. 存在重复元素 II

219. 存在重复元素 II(简单)
在这里插入图片描述
暴力解法双层循环,用查找表优化为一重循环
方法一: 查找表

class Solution:
    def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
        seen = {}
        for i in range(len(nums)):
            if nums[i] not in seen:
                seen[nums[i]] = i
            else:
                if i-seen[nums[i]]<=k:
                    return True
                else:
                    seen[nums[i]] = i
        return False

复杂度分析:
时间复杂度:O(N)
空间复杂度:O(N)

方法二: 滑动窗口

class Solution:
    def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
        seen = set()
        l,r = 0,0
        while r<len(nums):
            if nums[r] in seen:
                return True
            seen.add(nums[r])               
            r += 1
            while r-l>k:
                seen.remove(nums[l])
                l += 1
        return False

复杂度分析:
时间复杂度:O(N)
空间复杂度:O(N)

220. 存在重复元素 III

220. 存在重复元素 III(中等)
在这里插入图片描述
方法一: 滑动窗口
滑动窗口的长度<=K+1
在滑动数组中查找在[v-t,v+t]范围内的数 -> 查找比v-t大的最小元素,判断它是否小于等于v+t

class Solution:
    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:

        record = set()

        for i in range(len(nums)):
            if len(record)!=0:
                rec = list(record)
                idx = self.lower_bound(rec,nums[i]-t)
                if idx!=-1 and rec[idx]<=nums[i]+t:
                    return True
            record.add(nums[i])
            if len(record)==k+1:
                record.remove(nums[i-k])
        return False

    def lower_bound(self,nums,target):
        lo = 0
        hi = len(nums)-1
        while lo<hi:
            mid = (lo+hi)//2
            if nums[mid]<target:
                lo = mid+1
            else:
                hi = mid
        return lo if nums[lo]>=target else -1

复杂度分析:
时间复杂度:O(nlogn)
空间复杂度:O(n)

方法二: 桶排序

class Solution:
    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
        if t<0 or k<0:
            return False
            
        buckets = {}
        buckets_size = t+1
        for i in range(len(nums)):
            b = nums[i]//buckets_size
            if b in buckets:#在同一个桶里,满足差值小于等于t的要求
                return True
            buckets[b] = nums[i]

            if (b-1) in buckets and abs(buckets[b-1]-nums[i])<=t:
                return True
            if (b+1) in buckets and abs(buckets[b+1]-nums[i])<=t:
                return True
            
            #保证buckes里的元素都满足下标的要求
            if i>=k:
                buckets.pop(nums[i-k]//buckets_size)
        return False

复杂度分析:
时间复杂度:O(N)
空间复杂度:O(min(n,k))

76. 最小覆盖子串

76. 最小覆盖子串(困难)
在这里插入图片描述

class Solution:
    def minWindow(self, s: str, t: str) -> str:
        if not s or not t:
            return ""

        min_Len = len(s)+1
        min_l = 0

        need = {}
        match = 0
        for c in t:
            if c in need:
                need[c] += 1
            else:
                need[c] = 1
        
        l,r = 0,0
        while r<len(s):
            c1 = s[r]
            if c1 in need:
                need[c1]-=1
                if need[c1]==0:
                    match += 1
            r += 1
            while match==len(need):
                c2 = s[l]
                if min_Len>r-l:
                    min_Len = r-l
                    min_l = l
                if c2 in need:
                    need[c2]+=1
                    if need[c2]>0:
                        match -= 1
                l += 1
        if min_Len == len(s)+1:#不存在这样的子串,返回“”
            return ""
        return s[min_l:min_l+min_Len]

参考资料:
https://github.com/datawhalechina/team-learning-program/blob/master/LeetCodeClassification/3.%E6%9F%A5%E6%89%BE.md

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值