查找-- 对撞指针

查找–对撞指针

给出一个整型数组nums,返回这个数组中两个数字的索引值i和j,使得nums[i] + nums[j]等于一个给定的target值,两个索引不能相等。

如:nums= [2,7,11,15],target=9
返回[0,1]

思路:

  1. 开始数组是否有序;
  2. 索引从0开始计算还是1开始计算?
  3. 没有解该怎么办?
  4. 有多个解怎么办?保证有唯一解。

先尝试强行解,O(n2)遍历

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

尝试优化:用排序+指针对撞(O(n) + O(nlogn) = O(nlogn))

因为问题本身不是有序的,因此需要对原来的数组进行一次排序,排序后就可以用O(n)的指针对撞进行解决。
因为返回的是索引值, 所以在排序前要先将数字所对应的索引值储存

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        record = dict()
        nums_copy = nums.copy()
        sameFlag = True;
        nums.sort()
        l,r = 0,len(nums)-1
        while l < r:
            if nums[l] + nums[r] == target:
                break
            elif nums[l] + nums[r] < target:
                l += 1
            else:
                r -= 1
        res = []
        for i in range(len(nums)):
            if nums_copy[i] == nums[l] and sameFlag:
                res.append(i)
                sameFlag = False
            elif nums_copy[i] == nums[r]:
                res.append(i)
        return res

更加pythonic的实现:
通过list(enumerate(nums))开始实现下标和值的绑定,不用专门的再copy加bool判断。

class Solution:
  nums = list(enumerate(nums))
  nums.sort(key = lambda x:x[1])
  i,j = 0, len(nums)-1
  while i < j:
    if nums[i][1] + nums[j][1] > target:
        j -= 1
    elif nums[i][1] + nums[j][1] < target:
        i += 1
    else:
        if nums[j][0] < nums[i][0]:
            nums[j],nums[i] = nums[i],nums[j]
        return num[i][0],nums[j][0]

拷贝数组 + bool型变量辅助
查找表–O(n)
遍历数组过程中,当遍历到元素v时,可以只看v前面的元素,是否含有target-v的元素存在。

  1. 如果查找成功,就返回解;
  2. 如果没有查找成功,就把v放在查找表中,继续查找下一个解。
    即使v放在了之前的查找表中覆盖了v,也不影响当前v元素的查找。因为只需要找到两个元素,只需要
    找target-v的另一个元素即可。
class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        record = dict()
        for i in range(len(nums)):
            complement = target - nums[i]
            # 已经在之前的字典中找到这个值
            if record.get(complement) is not None:
                res = [i,record[complement]]
                return res
            record[nums[i]] = i

题目描述
给出一个整型数组,寻找其中的所有不同的三元组(a,b,c),使得a+b+c=0
注意:答案中不可以包含重复的三元组。
如:nums = [-1, 0, 1, 2, -1, -4],
结果为:[[-1, 0, 1],[-1, -1, 2]]

解题思路

  1. 数组不是有序的;
  2. 返回结果为全部解,多个解的顺序是否需要考虑?–不需要考虑顺序
  3. 什么叫不同的三元组?索引不同即不同,还是值不同?–题目定义的是,值不同才为不同的三元组
  4. 没有解时怎么返回?–空列表
class Solution:
    def threeSum(self, nums: [int]) -> [[int]]:
        nums.sort()
        res = []
        for i in range(len(nums)-2):
            # 因为是排序好的数组,如果最小的都大于0可以直接排除
            if nums[i] > 0: break
            # 排除i的重复值
            if i > 0 and nums[i] == nums[i-1]: continue
            l,r = i+1, len(nums)-1
            while l < r:
                sum = nums[i] + nums[l] + nums[r]
                if sum == 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 sum < 0:
                    l += 1
                else:
                    r -= 1
        return res

小套路

  1. 采用for + while的形式来处理三索引;
  2. 当数组不是有序时需要注意,有序的特点在哪里,有序就可以用哪些方法解决?无序的话不便在哪
    里?
  3. 对撞指针套路:
# 对撞指针套路
l,r = 0, len(nums)-1
while l < r:
    if nums[l] + nums[r] == target:
        return nums[l],nums[r]
    elif nums[l] + nums[r] < target:
        l += 1
    else:
        r -= 1`

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

# 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

题目描述
给出一个整形数组,寻找其中的所有不同的四元组(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]]

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()
        res = []
        if len(nums) < 4: return res
        if len(nums) == 4 and sum(nums) == target:
            res.append(nums)
            return res
        for i in range(len(nums)-3):
            if i > 0 and nums[i] == nums[i-1]: continue
            for j in range(i+1,len(nums)-2):
                if j > i+1 and nums[j] == nums[j-1]: continue
                l,r = j+1, len(nums)-1
                while l < r:
                    sum_value = nums[i] + nums[j] + nums[l] + nums[r]
                    if sum_value == 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 sum_value < target:
                        l += 1
                    else:
                        r -= 1
        return res

还可以使用combinations(nums, 4)来对原数组中得4个元素全排列,在开始sort后,对排列得到得元素进行set去重。但单纯利用combinations实现会超时。

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()
        from itertools import combinations
        res = []
        for i in combinations(nums, 4):
            if sum(i) == target:
                res.append(i)
        res = set(res)
        return res

题目描述

给出一个整形数组,寻找其中的三个元素a,b,c,使得a+b+c的值最接近另外一个给定的数字target。

如:给定数组 nums = [-1,2,1,-4], 和 target = 1.

与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).

分析
判断三个数之和是否等于target值, 如果等于则直接返回这三个值, 如果不等于target值则比较数组中三数组合的和与target之间的差值, 返回差值最小的三个数。

nums.sort()
res = []
for i in range(len(nums)-2):
    l,r = i+1, len(nums)-1
    while l < r:
        sum = nums[i] + nums[l] + nums[r]
        if sum == 0:
            res.append([nums[i],nums[l],nums[r]])
        elif sum < 0:
            l += 1
        else:
            r -= 1
class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        nums.sort()
        diff = abs(nums[0]+nums[1]+nums[2]-target)
        res = nums[0] + nums[1] + nums[2]
        for i in range(len(nums)):
            l,r = i+1,len(nums)-1
            t = target - nums[i]
            while l < r:
                if nums[l] + nums[r] == t:
                    return nums[i] + t
                else:
                    if abs(nums[l]+nums[r]-t) < diff:
                        diff = abs(nums[l]+nums[r]-t)
                        res = nums[i]+nums[l]+nums[r]
                    if nums[l]+nums[r] < t:
                        l += 1
                    else:
                        r -= 1
        return res

题目描述

给出四个整形数组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;

输入:

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

输出:2

分析实现
这个问题同样是Sum类问题得变种,其将同一个数组的条件,变为了四个数组中,依然可以用查找表的思想来实现。
首先可以考虑把D数组中的元素都放入查找表,然后遍历前三个数组,判断target减去每个元素后的值是否在查找表中存在,存在的话,把结果值加1。那么查找表的数据结构用dict来存储重复的元素,最后的结果值加上dict相应key的value,代码如下:

from collections import Counter
record = Counter()
# 先建立数组D的查找表
`for i in range(len(D)):
    record[D[i]] += 1
res = 0 
for i in range(len(A)):
    for j in range(len(B)):
        for k in range(len(C)):
            num_find = 0-A[i]-B[j]-C[k]
            if record.get(num_find) != None:
                res += record(num_find)
return res

此时因为遍历三层 时间复杂度为O(n3), N<= 500, 如果N过大那该算法消耗依旧过大,优化:

class Solution:
    def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: 
List[int]) -> int:
        from collections import Counter
        record = Counter()
        for i in range(len(A)):
            for j in range(len(B)):
                record[A[i]+B[j]] += 1
        res = 0
        for i in range(len(C)):
            for j in range(len(D)):
                find_num = 0 - C[i] - D[j]
                if record.get(find_num) != None:
                    res += record[find_num]
        return res 

该算法复杂度为O(n2)

再使用Pythonic的列表生成式和sum函数进行优化,如下:

class Solution:
    def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: 
List[int]) -> int:
        record = collections.Counter(a + b for a in A for b in B)
        return sum(record.get(- c - d, 0) for c in C for d in D)

题目描述

给出一个字符串数组,将其中所有可以通过颠倒字符顺序产生相同结果的单词进行分组。

示例:
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”],
输出:[[“ate”,“eat”,“tea”],[“nat”,“tan”],[“bat”]]

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

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        from collections import defaultdict
        strs_dict = defaultdict(list)
        res = []
        for str in strs:
            key = ''.join(sorted(list(str)))
            strs_dict[key] += str.split(',')
        for v in strs_dict.values():
            res.append(v)
        return res

再将能用列表生成式替换的地方替换掉,代码实现如下:

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        from collections import defaultdict
        strs_dict = defaultdict(list)
        for str in strs:
            key = ''.join(sorted(list(str)))
            strs_dict[key] += str.split(',')
        return [v for v in strs_dict.values()]

题目描述
给出一个平面上的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]]

距离
对于距离值的求算,按照欧式距离的方法进行求算的话,容易产生浮点数,可以将根号去掉,用差的平方和来进行比较距离。
实现代码如下:

class Solution:
    def numberOfBoomerangs(self, points: List[List[int]]) -> int:
        res = 0
        from collections import Counter
        for i in points:
            record = Counter()
            for j in points:
                if i != j:
                    record[self.dis(i,j)] += 1
            for k,v in record.items():
                res += v*(v-1)
        return res
    def dis(self,point1,point2):
        return (point1[0]-point2[0]) ** 2 + (point1[1]-point2[1]) ** 2

优化
对实现的代码进行优化:

  1. 将for循环遍历改为列表生成式;
  2. 对sum+=的操作,考虑使用sum函数。
  3. 对不同的函数使用闭包的方式内嵌;
class Solution:
    def numberOfBoomerangs(self, points: List[List[int]]) -> int:
        from collections import Counter
        def f(x1, y1):
            # 对一个i下j,k的距离值求和
            d = Counter((x2 - x1) ** 2 + (y2 - y1) ** 2 for x2, y2 in 
points)
            return sum(t * (t-1) for t in d.values())
        # 对每个i的距离进行求和
        return sum(f(x1, y1) for x1, y1 in points)

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

示例 1:
输入: [[1,1],[2,2],[3,3]]
输出: 3

示例 2:
输入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
输出: 4

分析实现
本道题目的要求是:看有多少个点在同一条直线上,那么判断点是否在一条直线上,其实就等价于判断i,j两点的斜率是否等于i,k两点的斜率。
回顾上道447题目中的要求:使得i,j两点的距离等于i,k两点的距离,那么在这里,直接考虑使用查找表实现,即查找相同斜率key的个数value是多少。
在上个问题中,i和j,j和i算是两种不同的情况,但是这道题目中,这是属于相同的两个点,因此在对遍历每个i,查找与i相同斜率的点时,不能再对结果数res++,而应该取查找表中的最大值。如果有两个斜率相同时,返回的应该是3个点,故返回的是结果数+1。

class Solution:
    def maxPoints(self,points):
        if len(points) <= 1:
           return len(points)
        res = 0
        from collections import defaultdict
        for i in range(len(points)):
            record = 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:
                    record[self.get_Slope(points,i,j)] += 1
            for v in record.values():
                res = max(res, v+samepoint)
            res = max(res, samepoint)
        return res
    def get_Slope(self,points,i,j):
        if points[i][1] - points[j][1] == 0:
            return float('Infinite')
        return (points[i][0] - points[j][0]) / (points[i][1] - points[j][1])

该解决方法时间复杂度为O(n2), 空间复杂度为O(n).

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值