Task4 查找2

两数之和

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

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

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

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

解题思路:
第一反应是暴力破解,但是超时了(理所当然的失败)。
讨论区提示用哈希表,python中可以使用字典,像这种需要索引和值的,可以用到enumerate函数。
首先创建空字典dic用于保存索引和值
使用enumerate遍历nums中的索引和值,这里的值称为num1
计算num2 = target - num1,检查num2是否存在于字典dic中
如果存在,那么自然返回num2的索引dic[num2]和num1的索引index
如果不存在,那么将当前的num1值与其索引存入字典dic中

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        hashset={}
        for i in range(len(nums)):
            if hashset.get(target-nums[i]) is not None :
                return [hashset.get(target-nums[i]),i]
            hashset[nums[i]]=i

三数之和

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

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

示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]

解题思路:
固定一个值a,然后在剩余的值中找b,c使得a+b+c=0。
因此至少需要两种循环,一个遍历找a,另一个遍历找b,c,那么就可以用上排序,毕竟两个循环都要O(N^2)了。
对数组排序后,从前往后遍历固定一个值a,我们需要找到b+c = -a的b,c,那么怎么找剩余两个值呢?
可以使用二分的思想,对于一个排好序的数组,将其头尾相加,与目标值对比会有三种结果
1.相等,说明找到了
2.大于目标值,说明尾部太大,将尾部索引左移
3.小于目标值,说明头部太小,将头部索引右移
此外还有一些细节,比如需要跳过和上一次枚举相同的数,以及保持左指针小于右指针

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        res = []
        #固定一个值 然后对剩余值进行搜索
        for i in range(len(nums)-2):
            #跳过重复的结果
            if i>0 and nums[i] == nums[i-1]:
                continue
            left = i + 1 
            right = len(nums) - 1
            target = -nums[i]
            while left<right:
                if nums[left] + nums[right] == target:
                    res.append([nums[i], nums[left], nums[right]])
                    left += 1
                    right -= 1
                    #跳过重复的结果
                    while left<right and nums[left] == nums[left-1]:
                        left += 1
                    #跳过重复的结果
                    while left<right and nums[right] == nums[right+1]:
                        right -= 1
                elif nums[left] + nums[right] > target:
                    right -= 1
                else:
                    left += 1
        return res

最接近的三数之和

给定一个包括 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

解题思路:
和上一题一样,排序后双指针遍历,题目要求返回与target最接近(差值的绝对值最小)的三数和(一开始还以为返回那三个数,晕),所以我们用三个数的和sum_3与target进行对比,还是一样,固定一个数,然后使用两个指针,分别从头部和尾部进行遍历,还是有三种结果:
1.三数和=target,直接返回三数和
2.三数和大于target:尾部值大了,右指针左移
3.三数和小于target:头部值小了,左指针右移
然后我们需要更新三数和与target间的差值,最终返回一个差值最小的三数和

class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        nums.sort()
        best = 10**7

        # 根据差值的绝对值来更新答案
        def update(cur):
            nonlocal best
            if abs(cur - target) < abs(best - target):
                best = cur

        #固定一个值,然后搜索剩余值
        for i in range(len(nums) - 2):
            #跳过和上次枚举相同的值
            if i>0 and nums[i] == nums[i-1]:
                continue
            left = i + 1
            right = len(nums) - 1
            while left < right:
                sum_3 = nums[i] + nums[left] + nums[right] 
                #如果三数之和恰好等于目标,直接返回
                if target == sum_3:
                    return target
                #更新差值
                update(sum_3)
                #如果三数之和小于目标,则左指针右移
                if target > sum_3:
                    left += 1
                    #跳过和上次枚举相同的值
                    while left<right and nums[left] == nums[left-1]:
                        left += 1
                #否则右指针左移
                else:
                    right -= 1
                    #跳过和上次枚举相同的值
                    while left<right and nums[right] == nums[right+1]:
                        right -= 1
                    
        
        return best

小优化(减少枚举次数,但是无法减少时间复杂度):
当我们枚举 a,b,c 中任意元素并移动指针时,可以直接将其移动到下一个与这次枚举到的不相同的元素,减少枚举的次数。

if s > target:
    # 如果和大于 target,移动 c 对应的指针
    k0 = k - 1
    # 移动到下一个不相等的元素
    while j < k0 and nums[k0] == nums[k]:
            k0 -= 1
            k = k0
    else:
    # 如果和小于 target,移动 b 对应的指针
            j0 = j + 1
    # 移动到下一个不相等的元素
            while j0 < k and nums[j0] == nums[j]:
                 j0 += 1
                 j = j0

时间复杂度:O(N^2),其中 N 是数组nums 的长度。我们首先需要 O(N \log N)的时间对数组进行排序,随后在枚举的过程中,使用一重循环 O(N)枚举 a,双指针 O(N)枚举 b 和 c,故一共是 O(N^2)。

空间复杂度:O(logN)。排序需要使用O(logN) 的空间。然而我们修改了输入的数组 nums,在实际情况下不一定允许,因此也可以看成使用了一个额外的数组存储了nums 的副本并进行排序,此时空间复杂度为 O(N)。

四数之和

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 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]
]

解题思路:
老套娃了,把四数和看做两个两数和的和,令n等于数组长度
首先第一个数从0到n-4,第二个数从1到n-3,这两个数作为第一部分,在集合中保存所有可能出现的两数和以及两个数的索引
然后计算第二部分,第三个数从2到n-2,第四个数从3到n-1,计算所有可能的两数和,然后是目标值减去第二部分的两数和,判断结果是否存在第一部分的两数和,如果存在,还需要判断第一部分的第二个数索引比第三个数的索引小

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()
        d={}
        n = len(nums)
        res = set()
        for i in range(n-3):
            for j in range(i+1, n-2):
                crr_sum = nums[i] + nums[j]
                d.setdefault(crr_sum,[]).append((i,j))

        for i in range(2, n-1):
            for j in range(i+1, n):
                crr_sum2 = nums[i] + nums[j]
                pre_pairs = d.get(target - crr_sum2)
                if pre_pairs == None:
                    continue
                
                for pair in pre_pairs:
                    if pair[1] < i:
                        res.add((nums[pair[0]], nums[pair[1]], nums[i], nums[j]))
        return list(res)

思路比较简单,使用了集合set达到去重的目的,最后需要转为列表返回
保存第一部分使用了字典的setdefault()方法:dict.setdefault(key,[]).append(value)
接收两个参数,如果key不在字典时,给字典的key设定一个值,值通过append方法添加

字母异位词分组

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

示例:

输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]
说明:

所有输入均为小写字母。
不考虑答案输出的顺序。

解题思路:
方法一:排序数组分类
字母异位词的排序字符串相等,比如‘eat’、‘tea’排序后都是‘aet’
排序后将键存储为散列化元组,如(‘a’,‘e’,‘t’),映射的value为输入的字符串列表

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        ans = collections.defaultdict(list)
        for s in strs:
            ans[tuple(sorted(s))].append(s)
            print(ans)
        return list(ans.values())

时间复杂度:O(NK log K),其中 N是 strs 的长度,而 K 是 strs 中字符串的最大长度。当我们遍历每个字符串时,外部循环具有的复杂度为 O(N)。然后,我们在 O(K log K) 的时间内对每个字符串排序。

空间复杂度:O(NK),排序存储在 ans 中的全部信息内容。

避免当Key不存在时,会引发‘KeyError’异常,可以使用collections类中的defaultdict()方法来为字典提供默认值。
语法格式:
collections.defaultdict([default_factory[, …]])
第一个参数为default_factory属性提供初始值,默认为None
1.使用list作第一个参数,可以将键-值对序列转换为列表字典。

from collections import defaultdict
s=[('yellow',1),('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d=defaultdict(list)
for k, v in s:
    d[k].append(v)
a=sorted(d.items())
print(a)

在这里插入图片描述

当字典中没有的键第一次出现时,default_factory自动为其返回一个空列表,list.append()会将值添加进新列表;再次遇到相同的键时,list.append()将其它值再添加进该列表。
上一节的dict.setdefault()也可以实现相同的功能:

s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d={}
for k, v in s:
    d.setdefault(k,[]).append(v)
print('\n',d)
a=sorted(d.items())
print('\n',a)

在这里插入图片描述

2.将default_factory设为int还可以被用来计数:

from collections import defaultdict
s = 'mississippi'
d = defaultdict(int)
for k in s:
    d[k] += 1
print('\n',d)
a=sorted(d.items())
print('\n',a)

在这里插入图片描述

字符串中的字母第一次出现时,字典中没有该字母,default_factory函数调用int()为其提供一个默认值0,加法操作将计算出每个字母出现的次数。
3.default_factory设为set时,可以用defaultdict建立集合字典

from collections import defaultdict
s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)]
d = defaultdict(set)
for k, v in s:
    d[k].add(v)
print('\n',d)
a=sorted(d.items())
print('\n',a)

在这里插入图片描述

方法二:按计数分类
字母异位词每个字符的出现次数相同,将strs中每个字符串s转换为字符数count,count为26个非负整数数组,记录小写字符’a’-'z’的出现次数,最后将count为键,将字符作为value,存入相同的键中

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        ans = collections.defaultdict(list)
        for s in strs:
            count = [0] * 26
            for c in s:
                count[ord(c) - 97] += 1
            ans[tuple(count)].append(s)
        
        return list(ans.values())

时间复杂度:O(NK),其中 N 是 strs 的长度,而 K 是 strs 中字符串的最大长度。计算每个字符串的字符串大小是线性的,我们统计每个字符串。

空间复杂度:O(NK),排序存储在 ans 中的全部信息内容

存在重复元素 II

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 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

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

存在重复元素 III

在整数数组 nums 中,是否存在两个下标 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值小于等于 t ,且满足 i 和 j 的差的绝对值也小于等于 ķ 。
如果存在则返回 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

解题思路:桶排序,同一个桶之间的差的绝对值≤t
定义桶的大小是t+1, nums[i]//(t+1)决定放入几号桶,这样在一个桶里面的任意两个的绝对值差值都<=t
例如t=3, nums=[0 ,5, 1, 9, 3,4],那么0号桶就有[0,1,3],1号桶就有[4,5],2号桶就有[9]
遍历nums每一个元素,并把他们放入相应的桶中,有两种情况会返回True:
要放入的桶中已经有其他元素了,这时将nums[i]放进去满足差值<=t
可能存在前面一个桶的元素并且与nums[i]的差值<=t 或者 存在后面一个桶的元素并且与nums[i]的差值<=t
根据返回True的第一个条件,可以知道前后桶的元素最多也只能有一个。
接着考虑限制桶中的索引差最大为K,当i>=k的时候:
我们就要去删除存放着nums[i-k]的那个桶(编号为nums[i-k]//(t+1))
这样就能保证遍历到第i+1个元素时,全部桶中元素的索引最小值是i-k+1,就满足题目对索引的限制了

class Solution:
    def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
        if t<0 or k<0:
            return False
        
        #用字典模拟桶,key为桶号
        all_buckets = {}
        bucket_size = t + 1

        for i in range(len(nums)):
            bucket_num = nums[i] // bucket_size #决定放入几号桶
            #桶中已经有元素,说明肯定存在两个数差的绝对值小于t
            if bucket_num in all_buckets:
                return True
            #将元素放入桶中
            all_buckets[bucket_num] = nums[i]

            #前后桶的元素也可能与当前值相差的绝对值<=t
            #检查前一个桶
            if (bucket_num - 1) in all_buckets and abs(all_buckets[bucket_num - 1] - nums[i]) <= t:
                return True
            #检查后一个桶
            if (bucket_num + 1) in all_buckets and abs(all_buckets[bucket_num + 1] - nums[i]) <= t:
                return True

             # 如果不构成返回条件,那么当i >= k 的时候就要删除旧桶了,以维持桶中的元素索引跟下一个i+1索引只差不超过k
            if i >= k:
                all_buckets.pop(nums[i-k]//bucket_size)

        return False

回旋镖的数量

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

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

示例:
输入:
[[0,0],[1,0],[2,0]]
输出:
2
解释:
两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]

解题思路:要求输出个数,使用字典保存key为距离,value为出现该距离的次数
外层遍历每个点 i,内层遍历并计算其他点 j 到 i 的距离并通过 Map 保存相等距离的频次
计算距离公式不用开根号
计算排列组合公式 n * (n - 1)

class Solution:
    def dis(self, p1, p2):
        return (p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1])
    
    def numberOfBoomerangs(self, points: List[List[int]]) -> int:
        res = 0
        for i in points:
            freqMap = {}
            for j in points:
                if j != i:
                    d = self.dis(i, j)
                    freqMap[d] = freqMap[d] + 1 if d in freqMap else 1
            for v in freqMap.values():
                res += v * (v - 1)
        
        return res

四数相加 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 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。

例如:
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2
解释:
两个元组如下:

  1. (0, 0, 0, 1) -> A[0] + B[0] + 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

解题思路:与四数和相同,先计算前面的两数和,将结果存入字典中,key为两数和,value为出现该两数和的次数;然后我们的target就是0-后面的两数和,在字典中找到target,并将计数器加上target出现的次数
还是使用defaultdict()方法,出现字典中没有的key时不会报错,factory_function设为int才可用于计数

class Solution:
    def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
        dic = defaultdict(int)
        count = 0
        for a in A:
            for b in B:
                dic[a+b] += 1
        
        for c in C:
            for d in D:
                target = -(c + d)
                if target in dic:
                    count += dic[target]

        return count

直线上最多的点数

给定一个二维平面,平面上有 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

解题思路:没有 抄就完事了
固定一点, 找其他点和这个点组成直线, 统计他们的斜率
求斜率有两种方法:
用最大约数方法(gcd), 把他化成最简形式, 3/6 == 2/4 == 1/2
除数(不太精确, 速度快!)
由于需要考虑符号,所以要重写gcd函数;如果用除数的话,如果x,y相差特别大,可能会报错

class Solution:
    def maxPoints(self, points: List[List[int]]) -> int:
        from collections import Counter, defaultdict
        # 统计所有点出现的次数
        points_dict = Counter(tuple(point) for point in points)
        # 保存所有点,结果不重复
        not_repeat_points = list(points_dict.keys())
        n = len(not_repeat_points)
        if n == 1: return points_dict[not_repeat_points[0]]
        res = 0
        # 求最大公约数,用于求斜率,斜率需要保留符号
        def gcd(x, y):
            if y == 0:
                return x
            else:
                return gcd(y, x % y)

        for i in range(n - 1):
            # 点1
            x1, y1 = not_repeat_points[i][0], not_repeat_points[i][1]
            # 斜率
            slope = defaultdict(int)#用来计数,没有出现的key默认为0
            for j in range(i + 1, n):
                # 点2
                x2, y2 = not_repeat_points[j][0], not_repeat_points[j][1]
                dy, dx = y2 - y1, x2 - x1
                # 求斜率
                # 方式一 利用公约数
                g = gcd(dy, dx)
                if g != 0:
                    dy //= g
                    dx //= g
                
                slope["{}/{}".format(dy, dx)] += points_dict[not_repeat_points[j]]
                
                # --------------------
                # 方式二, 利用除法(不准确, 速度快)
                # if dx == 0:
                #     tmp = "#"
                # else:
                #     tmp = dy * 1000 / dx * 1000
                # slope[tmp] += points_dict[not_repeat_points[j]]
                #------------------------------
            res = max(res, max(slope.values()) + points_dict[not_repeat_points[i]])
        return res
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值