分享一個簡單但挺有意思的演算法題-雜湊-二分-雙指標

tfzh發表於2022-04-08

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
}
這裡重點提一下為什麼要優化二分查詢的左起點,如果我們不給定左起點,那麼每次二分都是從0len -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)

相關文章