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)