二,对撞指针
对撞指针:在有序数组中,将指向最左侧的索引定义为左指针(left),最右侧的定义为右指针(right),然后从两头向中间进行数组遍历。
1,两数之和
力扣01-题目: 给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
来源:力扣(LeetCode)链接:力扣
思路:由题要求知为有序数组,即可使用双指针,一个指针指向值较小的元素(left),一个指针指向值较大的元素(right)形成对撞之势。
左指针从头向尾遍历,右指针从尾向头遍历。如果两个指针指向元素的和(sum)与目标值(target)相等,那么得到要求的结果;如果 sum > target,右指针移动,使 sum变小一些;如果 sum < target,左指针移动,使 sum变大一些。
重复上述循环直到找到答案输出。
复杂度分析: 时间复杂度:O(N) , 因为 每个元素最多被访问一次,共有 n个元素。空间复杂度:O(1), 指针使用常数大小的额外空间。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
nums_dict={}
for i in range(len(nums)):
temp=target-nums[i]
if temp in nums_dict:
return [i,nums_dict[temp]]
else:
nums_dict[nums[i]] = i
2,三数之和
力扣015-题目: 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。注意:答案中不可以包含重复的三元组。
来源:力扣(LeetCode)链接:力扣
思路:本题的难点在于如何去除重复解。首先需要对数组进行排序, 并且先固定一个中心值 nums[i], 然后应用左右双指针指向nums[i]的两端, 交替向中间移动遍历排序后的数组。
如果 nums[i]>0:因为数组升序排序,所以后面不可能有三个数加和等于 0,因此直接返回结果。
对于重复元素通过双指针可加1减1跳过,避免出现重复解。左指针 Left=i+1,右指针 Right=n-1,当 L
当 nums[i]+nums[L]+nums[R]==0 执行循环,判断左界和右界是否有下一位重复值,去除重复。并同时将 L,R移到下一位置,寻找新的解若和大于 0,说明 nums[R]太大,R左移; 若和小于 0,说明 nums[L] 太小,L 右移。
题目要求返回三元组, 即循环前要定义一个空列表用于记录每个符合条件的三元组。
复杂度分析, 时间复杂度:O(N^2), 进行了一次循环; 空间复杂度 O(1):指针使用常数大小的额外空间。
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
while left
if nums[i]+nums[left]+nums[right] > 0:
right -=1
elif nums[i]+nums[left]+nums[right] < 0:
left +=1
else:
res.append([nums[i],nums[left],nums[right]])
while left
left +=1
while left
right -=1
left +=1
right -=1
return res
3,最接近的三数之和
力扣016-题目: 给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
来源:力扣(LeetCode)链接:力扣
思路:题目要求找到与目标值 target最接近的三元组,这里的「最接近」即为差值的绝对值最小
大体思路同上, 首先排序。因为返回的是和即不需要一个空列表, 首先设定初始值与最小差值的绝对值res=nums[0+nums[1]+nums[2], min=abs(res-target)
通过循环, 不断比较sum ( sum=nums[i]+nums[left]+nums[right]) 与 target 的差值的绝对值和min的大小是否比之前的更小了,如果是,更新res。
循环中 sum > target,右指针移动减1,sum < target,左指针移动加1,最后返回值。
复杂度分析: 时间复杂度: O(N^2), 进行了一次循环; 空间复杂度: O(1)
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
res=nums[0]+nums[1]+nums[2]
min=abs(res-target)
for i in range(len(nums)-2):
left=i+1
right=len(nums)-1
while left
sum=nums[i]+nums[left]+nums[right]
if abs(sum-target)
min=abs(sum-target)
res=sum
if sum>target:
right -=1
elif sum
left +=1
else:
return sum
return res
4,二分查找
是一种基于比较目标值和数组中间元素的教科书式算法。如果目标值等于中间元素,则找到目标值。如果目标值较小,继续在左侧搜索。如果目标值较大,则继续在右侧搜索。
力扣704-题目: 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
来源:力扣(LeetCode)链接:力扣
思路:初始化指针 left = 0, right = n 。
当 left <= right:目标值 target 不在 nums 中返回-1。
目标值 target 在 nums 中, 比较中间元素 nums[mid] 和目标值 target 。如果 target = nums[mid],返回 mid。如果 target < nums[mid],则在左侧继续搜索 right = mid。如果 target > nums[mid],则在右侧继续搜索 left = mid, 重复循环得出结果。
复杂度分析: 时间复杂度:O(logN); 空间复杂度:O(1)。
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)
while left <= right:
if target not in nums:
return -1
mid = (left+right)//2
if target < nums[mid]:
right = mid
elif target > nums[mid]:
left = mid
else:
return mid
三,分离指针
分离指针:是指两个指针分别在两个数组中移动,根据问题的不同,初始位置可能都在头部,或者都在尾部,或一头一尾。
1,两个数组的交集
力扣349-题目: 给定两个数组,编写一个函数来计算它们的交集。说明:输出结果中的每个元素一定是唯一的。我们可以不考虑输出结果的顺序。
来源:力扣(LeetCode)链接:力扣
思路:先将 nums1 与 nums2 排序,然后通过两个指针遍历数组,比较 nums1[i] 与 nums2[j] 谁小就往前走直到相等, 将 nums1[i] 增加到一个空的集合里, 最后返回这个集合。
复杂度分析: 时间复杂度:O(NLogN) ; 空间复杂度:O(m+n)。
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
nums1.sort()
nums2.sort()
i,j=0,0
nume_set=set()
while i
if nums1[i]>nums2[j]:
j +=1
elif nums1[i]
i +=1
elif nums1[i]==nums2[j]:
nume_set.add(nums1[i])
i +=1
j +=1
return list(nume_set)
2,合并两个有序数组
力扣088-题目: 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
来源:力扣(LeetCode)链接:力扣
思路:从后向前遍历数组改变 nums1 的值不需要额外空间。
这里的指针 k 用于追踪添加元素的位置和控制输出数组的大小。
因为 nums1 的空间都集中在后面,所以从后向前处理排序的数据会更好,节省空间,一边遍历一边将值增加进去。
设置指针 i 和 j 分别指向 nums1 和 nums2 的有数字尾部,从尾部值开始比较遍历,同时设置指针 k 指向 nums1 的最末尾,每次遍历比较值大小之后,则进行赋值。
当 k <0 时遍历结束,此时 nums1 或 nums2 中还有一个头部数据未拷贝完全,再进行一次判断, 将其直接赋值到 nums1 的前面,最后得到结果数组。
复杂度分析: 时间复杂度 : O(n + m); 空间复杂度 : O(1)。
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
i = m-1
j = n-1
k = m+n-1
while i>=0 and j>=0:
if nums1[i] >= nums2[j]:
nums1[k] = nums1[i]
i -= 1
elif nums1[i] < nums2[j]:
nums1[k] = nums2[j]
j -= 1
k -= 1
while i>=0:
nums1[k]=nums1[i]
i -=1
k -=1
while j>=0:
nums1[k]=nums2[j]
j -= 1
k -= 1