1. 题目描述
给你两个有序(递增)整数数组nums1
和nums2
,请你以数组形式返回两数组的交集,M
为较长数组长度,N
为较短数组长度。例如:
给定:nums1 = [1,2,3,4,5,6]
,nums2 = [1,2,3]
输出:[1,2,3]
这道题常见且并不难,有意思的是解法非常多,在nums1
和nums2
长短不同场景下,挑选最高效的解法
2. 哈希表
这是最容易想到的解法,对较短的数组进行哈希,遍历较长的数组,就可以得到交集
function intersect(nums1, nums2){
let hash = new Set()//这里用set来代表哈希,他们本质是一样的
for (let i = 0; i < nums2.length; i++) {
hash.add(nums2[i])
}
for (let i = 0; i < nums1.length; i++) {
if (hash.has(nums1[i])) {
ans.push(nums1[i])
}
}
return ans
}
时间复杂度:O(M+N)
<br/>
空间复杂度:对于较短的数组进行了哈希,O(N)
3. 二分查找
思考一个场景,较长的数组非常长,哈希表的解法是线性的,显然没有很好的利用数组有序这个条件,这时二分查找脱颖而出,因为两者长度相差越大,二分效率越高;
function intersect(nums1, nums2){
//由于是递增,定义一个left,稍微减少下查找范围
let left = 0;
for (let i = 0; i < nums2.length; i++) {
//二分查找
let index = binarySearch(nums1, nums2[i], left)
if (index != -1) {
ans.push(nums2[i])
left = index + 1
}
}
return ans
}
function binarySearch(nums, target, left = 0, right = nums.length - 1){
//特殊处理 端点的情况 可以加速连续数组的查找
if(nums[left] == target){
return left
}
if(nums[right] == target){
return right
}
while(left <= right){
mid = Math.floor((left + right)/2)
if (nums[mid] == target) {
return mid
}else if(nums[mid] > target){
right = mid - 1
}else{
left = mid + 1
}
}
return -1
}
这里重点提一下为什么要优化二分查找的左起点,如果我们不给定左起点,那么每次二分都是从0
到len -1
二分,而由于数组是有序的,如果已经在num1
中找到了target
,那么下一个待查找target
的位置必然在上一个target
的右边,这样就避免了二分查找每次从0
开始,最理想情况下比如nums1 = [1,2,3,4,5,6]
,nums2 = [4,5,6]
,第一次查找到4
,剩余的5
,6
显然在4
的右边,实际只要O(N)
次就可以了
时间复杂度:M
为较长数组长度,N
为较短数组长度,最差/平均复杂度O(N*logM)
,而且由于我们优化了二分查找的左起点,最最理想情况下复杂度可以达到O(N)
;不是理想情况下,时间复杂度取决于交集在nums1
中的分布情况,交集在nums1
中越靠右分布,查找效率越高<br/>
空间复杂度:如果递归数组是引用的话,我们只使用了常量级变量空间O(1)
4. 双指针
如果两个数组长度相差不大,那么双指针显然效率更高;
function intersect(nums1, nums2){
let m = n = 0
while( m < nums1.length && n < nums2.length ) {
if (nums1[m] == nums2[n]) {
ans.push(nums1[m])
m++
n++
}else if (nums1[m] > nums2[n]) {
n++
}else{
m++
}
}
return ans
}
时间复杂度:M
为较长数组长度,N
为较短数组长度,最好情况是O(N)
,最坏情况是O(M)
<br/>
空间复杂度: 只使用了常量级变量空间 O(1)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。