Leetcode分类练习-查找(2)对撞指针
对撞指针
leetcode1 Two Sum
题目描述
给出一个整型数组nums,返回这个数组中两个数字的索引值i和j,使得nums[i]+nums[j]等于给定的一个target值,两个索引不能相等。
如:nums = [2, 7, 11, 15], target=9, 返回[0, 1] 。
审题
需要考虑:
1、开始数组是否有序?
2、索引从0开始计算还是从1开始计算?
3、没有解该怎么办?
4、有多个解该怎么办?保证有唯一解。
分析实现
暴力法 O(n^2)
两次遍历数组,代码就不写了。
排序+指针对撞 O(n)+O(nlogn)=O(n)
因为问题本身不是有序的,因此需要对原来的数组进行一次排序,排序后就可以用O(n)的指针对撞进行解决。
但是问题是,返回的是数字的索引,如果只是对数组的值进行排序,那么数组原来表示的索引的信息就会丢失,所以在排序前要进行些处理。
错误代码示例–只使用dict来进行保存
class Solution:def twoSum(self, nums:List[int], target:int) -> List[int]:record = dict()for index in range(len(nums)):record[nums[index]] = indexnums.sort()l, r = 0, len(nums)-1while l < r:if nums[l] + nums[i] == target:return [record[nums[l]], record[nums[r]]elif nums[l] + nums[r] < target:l += 1else:r -= 1
当遇到相同的元素的索引问题时,会不满足条件:
如:[3,3] 6
在排序前先使用一个额外的数组拷贝一份原来的数组,对于两个相同元素的索引问题,使用一个bool型变量辅助将两个索引都找到,总的时间复杂度为O(n)+O(nlogn)=O(nlogn)
class Solution:def twoSum(self, nums, target):nums_copy = nums.copy()sameFlag_l = True #用于取重复元素的第一个元素sameFlag_r = Truenums.sort()l,r = 0,len(nums)-1while l < r:if nums[l] + nums[r] == target:breakelif nums[l] + nums[r] < target:l += 1else:r -= 1res = []for i in range(len(nums)):if nums_copy[i] == nums[l] and sameFlag_l:res.append(i)sameFlag_l = Falseelif nums_copy[i] == nums[r] and sameFlag_r:res.append(i)sameFlag_r = Falsereturn res
小tips
如果只是对数组的值进行排序,那么数组原来表示的索引的信息就会丢失的情况,可以在排序前:
更加pythonic的实现:
通过list(enumrate(mums))开始实现下标(也就是索引)和值的绑定,不用专门的再copy加bool判断。
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:return nums[i][0], nums[j][0]elif nums[i][1] + nums[j][1] < target:i += 1else:j -= 1return nums[i][0], nums[j][0]
查找表–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 resrecond[nums[i]] = i
代码只进行了一次循环,所以时间复杂度和空间复杂度都为O(n).
LeetCode15 3Sum
题目描述
给出一个整形数组,寻找其中的所有不同的三元组,(a,b,c),使得a+b+c=0。
注意:答案中不可以包含重复的三元组。
如:nums=[-1, 0, 1, 2, -1, -4],
结果为:[[-1, 0, 1], [-1, -1, 2]]
审题
1、数组不是有序的;
2、返回结果为全部解,多个解的顺序是否需要考虑?——不需要考虑顺序;
3、什么叫不同的三元组?索引不同即不同,还是值不同?——题目的定义是,值不同才为不同的三元组;
4、没有解的时候怎么返回?——空列表
分析实现
因为上篇中已经实现了Two Sum的问题,因此对于3Sum,首先想到的思路就是,开始固定一个k,然后在气候都当作two sum的问题来解决,但是这样并不是很ok。
没有考虑重复元素导致错误
直接使用Two Sum问题中查找表的解法,根据第一层遍历的i,将i之后数组作为two sum的问题进行解决。
但是这样会导致一个错误,错误用例如下:
输入:[-1, 0, 1, 2, -1, -4]
输出:[[-1, 1, 0], [-1, -1, 2], [0,-1, 1]]
预期结果:[[-1, -1, 2], [0, 1, -1]]
主要原因是代码在实现的过程中没有把第一次遍历i的索引指向相同元素的情况排除掉,于是出现了当i指针后面位置的元素有和之前访问过的相同的值,于是重复遍历了。
那么可以考虑,开始时对nums数组进行排序,排序后当第一次遍历的指针k遇到下一个和前一个指向的值重复时,就将其跳过。为了方便计算,可以在第二层循环中,可以使用对撞指针的套路:
#对撞指针的套路
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] < targetl += 1else:r -= 1
但其中要注意的是,在里层循环中仍旧需要考虑重复值的情况,因此当值相等时,再次移动指针,需要保证其指向的值和前一次指向的值不重复,因此可以:
#对撞指针套路
l, r = 0, len(nums)-1
while l < r:sum = nums[i] + nums[l] + nums[r]if sum == target:res.append([nums[i], nums[l], nums[r])l += 1r -= 1while l < r and nums[l] == nums[l-1]: l += 1while l < r and nums[r] == nums[r+1]: r -= 1elif sum < target:l += 1else:r -= 1
再调整下遍历的范围,因为设了3个索引:i,l,r。边界情况下,r索引指向len-1, l指向len-2,索引i遍历的边界为len-3,故for循环是从0到len-2。
代码实现如下:
class Solution:def threeSum(self, nums, target):res = []nums.sort()print(nums)for i in range(len(nums)-2):print('此时选择i值:',i)if nums[i] > 0: break # 因为是排序好的数组所以大于0的可以直接排除if i > 0 and nums[i] == nums[i-1]: continue #跳出本次循环,避免出现两个相同的结果l,r = i+1, len(nums)-1while l < r:sum = nums[i] + nums[l] + nums[r]print(sum, nums[i],"+",nums[l],"+",nums[r] )if sum == target:res.append([nums[i],nums[l],nums[r]])l += 1 r -= 1while l < r and nums[l] == nums[l-1]: l += 1while l < r and nums[r] == nums[r+1]: r -= 1elif sum < target:l += 1else:r -= 1return res
输出结果:
[-4, -1, -1, 0, 1, 2]
此时选择i值: 0
-3 -4 + -1 + 2
-3 -4 + -1 + 2
-2 -4 + 0 + 2
-1 -4 + 1 + 2
此时选择i值: 1
0 -1 + -1 + 2
0 -1 + 0 + 1
此时选择i值: 2
此时选择i值: 3
3 0 + 1 + 2
[[-1, -1, 2], [-1, 0, 1]]
小tips
- 采用for+while的形式来处理三索引;
- 当数组不是有序时需要注意,有序的特点在哪里,有序就可以用哪些方法解决,无序的话不方便在哪里?
- 对撞指针的套路,规定做指针和有指针,注意指针移动的边界;
- 处理重复值的套路:先转换为有序数组,再循环判断与看一只是否重复。
LeetCode18 4sum
题目描述
给出一个整形数组,寻找其中所有不同的四元组(a, b, c, d), 使得a+b+c+d等于一个给定的数字target。
与三数之后的解法类似,所不同的是需要再套一层循环,对边界情况进行改动即可。
- 原来3个是到len-2,现在外层循环是到len-3;
- 在中间层得迭代中,当第二个遍历得值在第一个遍历得值之后且后项大于前项时,认定为重复;
- 加些边界条件判断:当len小于4时,直接返回;当只有4个值且长度等于target时,直接返回本身即可。
LeetCode16 3sum closest
题目描述
给定一个整形数组,寻找其中的三个元素a,b,c, 使得a+b+c的值最接近另外一个给定的数字target。
如:给定数组nums = [-1, 2, 1, -4], 和target=1。
与target最为接近的三个数的和为2。 (-1+2+1=2)
分析实现
这道题变形的地方在于不是直接找target,而是找最近的值。
那么最开始时可以随机设定一个三个数的和为某种结果值,在每次比较中,先判断三个数的和是否和target相等,如果不等,则设定一个结果值,如果差值小于这个结果值,如果小于则进行替换,并保存和的结果值。
LeetCode454 4SumII
题目描述
给定四个整形数组A,B,C,D,寻找有多少i,j,k,l的组合,使得A[i] + B[j] + C[k] + D[i] = 0。其中A,B,C,D中均含有相同的元素个数N, 且0<= N <= 500;
输入:
A = [1,2], B=[-2, -1], C=[-1, 2], D=[0,2]
分析:1+(-2)+(-1)+2 = 0; 2+(-1)+(-1)+0 = 0
输出:2
分析实现
这歌问题同样是sum问题的变种,其将同一个数组的条件,变成了四个数组中,依然可以用查找表的思想来实现。
首先可以考虑把D数组中的元素都放入查找表,然后遍历前三个数组,判断target减去每个元素后的值是否在查找表中存在,存在的话,把结果值加一。那么查找表的数据结构用set还是用dict?考虑到数组中可能存在重复的元素,而重复的元素属于不同的情况,因此用dict存储,最后的结果值加上dict相应key值的value,代码如下:
O(n^3)代码
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
但是对于题目中给出的数据规模:N <=500,如果N为500时,时间复杂度非常高。
根据之前的思路继续往前走,如果只遍历两个数组,那么就可以得到N^ 2级别的算法,但是遍历两个数组的话,查找的就是另外两个数组,对这两个数组,我们可以通过dict来记录其和的个数,之后遍历结果在和中进行查找。
class Solution:def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:from collections import Counterrecord = Counter()for i in range(len(A)):for j in range(len(B)):record[A[i]+B[j]] += 1res = 0for 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
LeetCode 49 Group Anagrams
题目描述
给定一个字符串数组,将其中所有可以通过颠倒字符顺序产生相同结果的单词进行分组。
示例:
输入:[“eat”, “tea”, “tan”, “ate”, “nat”, “bat”].
输出:[[“ate”, “eat”, “tea”], [“nat”, “tan”], [“bat”]]
说明:所有输入均为小些字母
不考虑答案输出的顺序
根据前面的经验,异位词排序后的字符串显然都是相同的,那么可以把其当作key,把遍历的数组中的异位词当作value,对字典进行赋值,进而遍历字典的value,得到结果list。
- 需要注意的细节是,字符串和list之间的转换:
- 默认构造字典需为list的字典;
- 排序使用sorted()函数,而不用list.sort()方法,因为其不返回值;
- 通过’’.join(list),将list转换为字符串;
- 通过str.split(’,’)将字符串整个转换为list中的一项;
class Solution(self, strs):def groupAnagrams(self, strs):from collections import defaultdictstrs_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 defaultdictstrs_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()]
LeetCode 149 Max Points on a Line
题目描述
给出一个平面上的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]]
分析实现
原始思路
题目的要求是:使得i,j两点的距离等于i,k两点的距离,那么相当于是比较三个点之间距离的,那么开始的思路就是三层遍历,i从0到len,j从i+1到len,k从j+1到len,然后比较三个点的距离,相等则结果数加一。
显然这样的时间复杂度为O(n^3),对于这道题目,能否用查找表的思路进行解决优化?
查找表
之前的查找表问题,大多是通过构建一个查找表,而避免了在查找中再内层嵌套循环,从而降低了时间复杂度。那么可以考虑在这道题中,可以通过查找表进行代替哪两层循环。
当i,j两点距离等于i,k时,用查找表的思路,等价于:对距离key(i,j或i,k的距离),其值value(个数)为2。
那么就可以做一个查找表,用来查找相同距离key的个数value是多少。遍历每一个节点i,扫描得到其他点到节点i的距离,在查找表中,对应的键就是距离的值,对应的值就是距离值得个数。
在拿到对于元素i的距离查找表后,接下来就是排列选择问题了:
如果当距离为x的值有2个时,那么选择j,k的可能情况有:第一次选择有2种,第二次选择有1种,为21;
如果当距离为x的值有3个时,那么选择j,k的可能的情况有:第一次选择有3种,第二次选择有2种,为32;
那么当距离为x的值有n个时,选择j,k的可能情况有:第一次选择有n种,第二次选择有n-1种。
距离
对于距离值的求算,按照欧式距离的方法进行求算的话,容易产生浮点数,可以将根号去掉,用差的平方和来进行比较距离。
实现代码如下:
class Solution:def numberOfBoomerangs(self, points):res = 0from collections import Counterfor i in points:record = Counter()for i in points:record = Counter()for j in points:if i!= j:record[self.dis(i,j)] += 1for k,v in record.items():res += v*(v-1)return resdef dis(self, point1, point2):return (point1[0]-point2[0]) ** 2 + (point1[1]-point2[1]) ** 2
优化
对实现的代码进行优化:
将for循环遍历改为列表生成式;
对sum+=的操作,考虑使用sum函数。
对不同的函数使用闭包的方式内嵌;
class Solution:def numberOfBoomerangs(self, points: List[List[int]]) -> int:from collections import Counterdef 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)
LeetCode149 Max Points on a Line
题目描述
给定一个二维平面,平面上有 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):res = 0from collections import defaultdictfor i in range(len(points)):record = defaultdict(int)for j in range(len(points)):if i != j:record[self.get_Slope(points,i,j)] += 1for v in record.values():res = max(res, v)return res + 1def get_Slope(self,points,i,j):return (points[i][0] - points[j][0]) / (points[i][1] - points[j][1])
但是这样会出现一个问题,即斜率的求算中,有时会出现直线为垂直的情况,故需要对返回的结果进行判断,如果分母为0,则返回inf,如下:
def get_Slope(self,points,i,j):if points[i][1] - points[j][1] == 0:return float('Inf')else:return (points[i][0] - points[j][0]) / (points[i][1] - points[j][1])
再次提交,发现对于空列表的测试用例会判断错误,于是对边界情况进行判断,如果初始长度小于等于1,则直接返回len:
if len(points) <= 1:return len(points)
再次提交,对于相同元素的测试用例会出现错误,回想刚才的过程,当有相同元素时,题目的要求是算作两个不同的点,但是在程序运行时,会将其考虑为相同的点,return回了inf。但在实际运行时,需要对相同元素的情况单独考虑。
于是可以设定samepoint值,遍历时判断,如果相同时,same值++,最后取v+same的值作为结果数。
考虑到如果全是相同值,那么这时dict中的record为空,也要将same值当作结果数返回,代码实现如下:
class Solution:def maxPoints(self,points):if len(points) <= 1:return len(points)res = 0from collections import defaultdictfor i in range(len(points)):record = defaultdict(int)samepoint = 0for j in range(len(points)):if points[i][0] == points[j][0] and points[i][1] == points[j][1]:samepoint += 1else:record[self.get_Slope(points,i,j)] += 1for v in record.values():res = max(res, v+samepoint)res = max(res, samepoint)return resdef get_Slope(self,points,i,j):if points[i][1] - points[j][1] == 0:return float('Inf')else:return (points[i][0] - points[j][0]) / (points[i][1] - points[j][1])
时间复杂度为O(n^2),空间复杂度为O(n)
总结:遍历时多用索引,而不要直接用值进行遍历
Leetcode分类练习-查找(2)对撞指针相关推荐
- LeetCode 分类练习(四):查找2
LeetCode 分类练习(四):查找2 目录 LeetCode 分类练习(四):查找2 一.双指针(快慢指针.对撞指针) 1.快慢指针 2.对撞指针 3.滑动窗口法 二.滑动数组 三.实战1(对撞指 ...
- leetcode分类刷题笔记
leetcode分类刷题笔记--基于python3 写在前面 1.做题如果实在想不出时间复杂度比较优的解法,可以先写出暴力解法,尝试在其基础上优化 2.排序.双指针.二分等--经常可以优化时间复杂度 ...
- Leetcode分类解析:组合算法
Leetcode分类解析:组合算法 所谓组合算法就是指:在解决一些算法问题时,需要产生输入数据的各种组合.排列.子集.分区等等,然后逐一确认每种是不是我们要的解.从广义上来说,组合算法可以包罗万象,甚 ...
- [算法]LeetCode 专题 -- 二分查找专题 34. 在排序数组中查找元素的第一个和最后一个位置
LeetCode 专题 – 二分查找专题 34. 在排序数组中查找元素的第一个和最后一个位置 难度:中等 题目描述 给定一个按照升序排列的整数数组 nums,和一个目标值 target.找出给定目标值 ...
- mysql 家谱树查询_无限级分类之查找子孙树和家谱树
$area=array( array('id'=>'1','name'=>'河南','parent'=>0), array('id'=>'2','name'=>'吉林', ...
- 双指针--快慢指针和对撞指针
1.基础概念 两个指针有 n*n种组合,因此时间复杂度是 O(n^2) .而双指针算法就是运用单调性使得指针只能单向移动,因此总的时间复杂度只有 O(2n),也就是O(n).双指针可以分为两种类型,一 ...
- LeetCode分类训练 Task 1 分治
群内编号:2-木铎铎 博文内容参考自DataWhale开源文档: LeetCode分类训练 Task 1 分治 主要思想 分治算法的主要思想是将原问题递归地分成若干个子问题,直到子问题满足边界条件,停 ...
- [LeetCode]704.二分查找及相关题目
数组理论基础 数组理论 数组是存放在连续内存空间上的相同类型数据的集合 数组可以方便的通过下标索引的方式获取到下标下对应的数据 二维数组在内存的空间地址是连续的 二分查找 LeetCode 704.二 ...
- [Leetcode][第75题][JAVA][颜色分类][双(三)指针][计数排序]
[问题描述][中等] [解答思路] 1. 三指针 时间复杂度:O(N) 空间复杂度:O(1) class Solution {public void sortColors(int[] nums) {i ...
最新文章
- 大盘点|YOLO 系目标检测算法总览
- UVALive6428 A+B【扩展欧几里得算法+GCD】
- C++ 控制结构和函数(三)—— 函数II(Functions II)
- LeetCode 501. 二叉搜索树中的众数(中序遍历)
- 《Programming with Objective-C》第四章 Encapsulating Data
- 数据库大咖解读“新基建”,墨天轮四重好礼相送!
- 容器的进程与namespace、rootfs
- idea javafx添加maven_IntelliJ IDEA使用之JavaFX
- dev万能头文件_超级好用的C++万能头文件
- node.js入门教程(B站黑马程序员)
- 有关php外文期刊,口腔外文杂志、收录数据库、参考信息汇总
- NVIDIA TAO 工具包 (TAO Toolkit) 的部署和应用【LDR、LPR】
- 审视AI界的“SOTA成瘾”丨AI学者万字论述
- 华为机试 放苹果
- php memcache内存大小,PHP memcache 内存缓存 数据库查询 应用 高洛峰 细说PHP
- 前端通用SEO技术优化指南
- 介绍几个预览效果不错的BIM网站链接
- linux nagios监控
- ios8.1.2耗电情况严重的解决方法
- 清华计算机本科学什么,清华大学计算机本科课程表
热门文章
- PTA 7-13 列车调度 (25 分) C语言和C++实现(二分查找)
- sql语句(select,create,drop,alter,delete,insert,update,grant)
- 5G和车联网的本质联系
- 全新UI众人帮任务帮PHP源码/悬赏任务抖音快手头条点赞源码/带三级分销可封装小程序
- 对APP强制更新的思考
- 20191127上海出差总结
- CentOS6实验模板机搭建
- 学习java第6天 模仿XP画板(10%)
- Spark的坑--Spark新手必看--Python Spark必读,耗费了我近三周的时间
- 高通平台开发系列讲解(充电篇)充电管理芯片PM7250B详解