【活动打卡】【Datawhale】第16期 编程实践(LeetCode 分类练习) Task04:查找2

查找2

本文为查找的第二部分,包括 双指针滑动数组 两种进阶的查找方法。
(在补了

双指针

1. 两数之和

原题传送:LeetCode 1. 两数之和

给定一个整数数组 nums和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

思路:

暴力枚举,如果两数之和匹配到目标值,返回下标。

Python:

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        for i in range(len(nums)):
            for j in range(i+1, len(nums)):
                res = []
                if nums[i] + nums[j] == target:
                    res.append(i)
                    res.append(j)
                    return res

15. 三数之和

原题传送:LeetCode 15. 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums中是否存在三个元素 a,b,c , 使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

思路:

双指针法,先对数组排序,然后遍历。重复元素跳过,避免出现重复的三元组。令左指针 l = i + 1,右指针 r = n - 1,当 l < r 时,执行循环。当 nums[i],nums[l] 和 nums[r] 三者之和为0时,判断左界和右界是否和下一位置重复,去除重复解。并同时将左右指针移到下一位置,寻找新的解。若和大于 0,说明 nums[r] 太大,r 左移,若和小于 0,说明 nums[l] 太小,l 右移。

Python:

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        res = []
        nums.sort()

		## 从小到大遍历
        for i in range(n):
        	# 如果最小的数大于零,三数之和不可能为0,返回空列表
            if nums[i] > 0:
                return res
            # 重复元素跳过
            if i > 0 and nums[i] == nums[i-1]:
                continue
            # 设置左右边界
            l, r = i+1, n-1
            while l < r:
            	# 三者和为 0,加入结果列
                if nums[i] + nums[l] + nums[r] == 0:
                    res.append([nums[i], nums[l], nums[r]])
                    while l < r and nums[l] == nums[l+1]:
                        l += 1
                    while l < r and nums[r] == nums[r-1]:
                        r -= 1
                    l += 1
                    r -= 1
                # 和小于 0,左边界右移
                elif nums[i] + nums[l] + nums[r] < 0:
                    l += 1
                # 和大于 0,右边界左移
                else:
                    r -= 1
                    
        return res

16. 最接近的三数之和

原题传送:LeetCode 16. 最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

提示:

  • 3 <= nums.length <= 10^3
  • -10^3 <= nums[i] <= 10^3
  • -10^4 <= target <= 10^4

思路:

双指针法,对数组排序并遍历,固定当前 i , 从 [i+1, n) 中寻找最接近的 l、r。

Python:

class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        n = len(nums)
        ans = float('inf')
        nums.sort()

        ## 从小到大遍历
        for i in range(n):
            # 重复直接跳过,因为只存在唯一答案
            if i > 0 and nums[i] == nums[i-1]: 
                continue
            # 更新 t 值
            t = target - nums[i]
            # 设置左右边界
            l, r = i+1, n-1
            while l < r:
                # 相等的时候最接近,直接返回目标值
                if t == nums[l] + nums[r]: 
                    return target
                # 出现更接近的数时,更新最小值
                if abs(t - nums[l] - nums[r]) < abs(target - ans):
                    ans = nums[i] + nums[l] + nums[r]
                # 双指针缩小范围
                if t > nums[l] + nums[r]:
                    l += 1
                else:
                    r -= 1

        return ans

18. 四数之和

原题传送:LeetCode 18. 四数之和

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,cd ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

示例:

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]

思路:

借鉴楼主的思路,将题目以python的思路写了下来,具体的就是使用双指针:
1.特判,如果len(nums)<4,return[],nums进行排序
2.将元素分成四个部分,将第一与第二个元素first and second进行双层for循环遍历, 题目要求是不允许出现相同元素的数组,所以 使用nums[first] ==nums[first-1]进行去掉重复元素。 third,fourth是使用双指针思想 类似于left right思想进行循环遍历
3.分三种情况:
3.1 if nums[first]+ nums[second]+nums[third] + nums[fourth] 3.2 elif nums[first]+ nums[second]+nums[third] + nums[fourth] >target: fourth 左移处理
3.3 res.append([nums[first],nums[second],nums[third],nums[fourth]]) 满足条件。进行添加处理,同时使用while 防止出现重复元素

参考链接:链接

Python:

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()
        res = []
        n = len(nums)

        ## 从小到大遍历
        for first in range(n-3):
            # 跳过重复元素
            if first >0 and nums[first] ==nums[first-1]:
                continue 
            for second in range(first+1,n-2):
                if second >first+1 and nums[second] ==nums[second-1]:
                    continue
                third,fourth = second+1,n-1
                while third<fourth:
                    if nums[first]+ nums[second]+nums[third] + nums[fourth] <target:
                        third += 1
                    elif nums[first]+ nums[second]+nums[third] + nums[fourth] >target:
                        fourth -= 1
                    else:
                        res.append([nums[first],nums[second],nums[third],nums[fourth]])
                        while third < fourth and nums[third]==nums[third+1]:
                            third+=1
                        while third < fourth and nums[fourth]==nums[fourth-1]:
                            fourth -= 1
                        third +=1 
                        fourth -=1
        return res

454. 四数相加 II

原题传送:LeetCode 454. 四数相加 II

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l),使得 A[i] + B[j] + C[k] + D[l] = 0

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 0 ≤ N ≤ 500 0N500 。所有整数的范围在 − 2 28 -2^{28} 228 2 28 − 1 2^{28} - 1 2281 之间,最终结果不会超过 2 31 − 1 2^{31} - 1 2311

示例 :

输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]

输出:
2

解释:
两个元组如下:
1. (0, 0, 0, 1) -> A[0] + B[0]v + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

思路:

初始化字典 dic,dic 记录数组 A 和 B 元素的和及其次数,遍历数组 C 和 D,累加满足四数相加和为 0 的个数。

Python:

class Solution:
    def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
        dic = Counter()
        ans = 0
        for i in A:
            for j in B:
                dic[i+j] += 1
        for i in C:
            for j in D:
                ans += dic[-i-j]
        return ans

49. 字母异位词分组

原题传送:LeetCode 49. 字母异位词分组

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:

输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]

思路:

通过排序把字符串转成从小到大的排列,利用字典进行分组。

Python:

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        dic = {
     }
        for i in strs:
            key = tuple(sorted(i))
            dic[key] = dic.get(key, []) + [i]
        return list(dic.values())

149. 直线上最多的点数

原题传送:LeetCode 149. 直线上最多的点数

给定一个二维平面,平面上有 n 个点,求最多有多少个点在同一条直线上。

示例 1:

输入: [[1,1],[2,2],[3,3]]
输出: 3
解释:
^
|
|        o
|     o
|  o  
+------------->
0  1  2  3  4

示例 2:

输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
输出: 4
解释:
^
|
|  o
|     o        o
|        o
|  o        o
+------------------->
0  1  2  3  4  5  6

思路:

  • 首先如果点数小于 3 个,直接返回点数(因为肯定能组成直线)。
  • 我们对所有点遍历,记录包含这个点在内的所有直线中,能组成的点数最多的直线的点数数量。
    返回这些数量的最大值。
  • 怎么遍历呢?
  • 我们对一个点遍历的时候,再遍历所有点
    • 维护两个变量
    • 一个来记录和这个点相同的点(重复点)
    • 一个来记录非重复点和这个点组成的各个直线以及它们拥有的点数
    • 即使用哈希表,键为斜率,值是这个直线拥有的点数。这里使用 Counter 直接统计各个直线拥有的点数。
    • 返回最多拥有点数的直线所拥有的点数与重复点之和。
  • 可以参考分步代码
  • 重复点的处理是难点。

参考链接:链接

Python:

from decimal import *

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

        def K(i,j):
            return float('Inf') if i[1] - j[1] == 0 else Decimal(i[0] - j[0])/Decimal(i[1] - j[1])

        ## 两点确定一条直线
        if len(points) <= 2:
            return len(points)
        
        ## 遍历所有点
        for i in points:
            same = sum(1 for j in points if j == i)
            hashmap = Counter([K(i,j) for j in points if j != i])
            tempmax = hashmap.most_common(1)[0][1] if hashmap else 0
            maxans = max(same + tempmax, maxans)
        
        return maxans

219. 存在重复元素 II

原题传送:LeetCode 219. 存在重复元素 II

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 ij,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k

示例 1:

输入: nums = [1,2,3,1], k = 3
输出: true

示例 2:

输入: nums = [1,0,1,1], k = 1
输出: true

示例 3:

输入: nums = [1,2,3,1,2,3], k = 2
输出: false

思路:

若出现字典里没有的数,添加到字典;若出现字典有的数,下标差若小于等于 k ,返回 True,反之,更新下标。最后遍历完没有满足条件,返回 False。

Python:

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

220. 存在重复元素 III

原题传送:LeetCode 220. 存在重复元素 III

在整数数组 nums 中,是否存在两个下标 ij,使得 nums [i]nums [j] 的差的绝对值小于等于 t ,且满足 ij 的差的绝对值也小于等于 ķ

如果存在则返回 true,不存在返回 false

示例 1:

输入: nums = [1,2,3,1], k = 3, t = 0
输出: true

示例 2:

输入: nums = [1,0,1,1], k = 1, t = 2
输出: true

示例 3:

输入: nums = [1,5,9,1,5,9], k = 2, t = 3
输出: false

思路:

滑动窗口 + 桶的思路,设桶大小为 bucket_size,桶编号算法 no/bucket_size, 如果两个数,no1, no2, 算下来的桶编号是一样的,则表示他们的大小之差小于桶大小。
或者看看桶左右相邻桶的元素,是否绝对值小于t,如果是,也满足要求。
利用这个原理,我们设桶编号为 t + 1, 如果桶大小为t,也能得出正确的结果,但是如果t为0,会有除0的问题。
时间复杂度 O(n),一遍过。
空间复杂度,正常为O(k),但是有可能K> 数组大小n,所以为 O(min(n, k))

参考链接:链接

Python:

class Solution:
    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
        if k < 0 or t < 0:
            return False
        # 窗口大小 t+1 避免除数为0的问题
        bucket_size = t + 1 
        bucket = {
     }

        for i in range(len(nums)):
            bucket_no = nums[i]//bucket_size
            # 看当前桶是否存在,存在就是找到结果了
            if bucket_no in bucket: 
                return True
            # 看相邻的两个桶
            if bucket_no - 1 in bucket and abs(bucket[bucket_no - 1] - nums[i]) <= t:
                return True
            if bucket_no + 1 in bucket and abs(bucket[bucket_no + 1] - nums[i]) <= t:
                return True
            #没有就创建这个桶
            bucket[bucket_no] = nums[i]
            #窗口滑动,删除当前位置的第一个,因为窗口要往前滑了
            if i>=k:
                del bucket[nums[i-k]//bucket_size]

        return False

447. 回旋镖的数量

原题传送:LeetCode 447. 回旋镖的数量

给定平面上 n 对不同的点,“回旋镖” 是由点表示的元组 (i, j, k),其中 ij 之间的距离和 ik 之间的距离相等 (需要考虑元组的顺序)

找到所有回旋镖的数量。你可以假设 n 最大为 500,所有点的坐标在闭区间 [-10000, 10000] 中。

示例 :

输入:
[[0,0],[1,0],[2,0]]

输出:
2

解释:
两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]

思路:

遍历两次,计算每一个点到其他所有点的距离,然后计算所有可能性。

Python:

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

        for i in points:
            dis = []
            for j in points:
                dis.append((j[1]-i[1])**2 + (j[0]-i[0])**2)
            for j, k in Counter(dis).items():
                if j != 0 and k >= 2:
                    ans += k*(k-1)

        return ans

参考文章

  1. 第16期 Datawhale 组队学习活动
  2. Datawhale课程——查找

你可能感兴趣的:(#,Datawhale)