leetCode解題記錄4 - 尋找兩個有序數組的中位數(JS, TS, PY版)

  • 作者:陳大魚頭
  • 項目地址:ying-leetcode
  • 碎碎念:Mmmmm,不定期刷leetcode,會以JS TS PY的形式輸出出來

題目描述:

給定兩個大小爲 m 和 n 的有序數組 nums1 和 nums2。

請你找出這兩個有序數組的中位數,並且要求算法的時間複雜度爲 O(log(m + n))。

你可以假設 nums1 和 nums2 不會同時爲空。

示例 1:

nums1 = [1, 3]
nums2 = [2]

// 則中位數是 2.0

示例 2:

nums1 = [1, 2]
nums2 = [3, 4]

// 則中位數是 (2 + 3)/2 = 2.5

解題思路

這條題目本身其實不難,主要麻煩的地方是限制了時間複雜度爲 O(log(m + n)),所以我們可以參考歸併排序的思路去實現,那麼既然是歸併排序,就有兩種方案,一種是自上而下的遞歸,另一種是 自下而上的迭代

其實就是對排好序的兩個數組的掃描,判斷,一直往右移,每移一次判斷一次當前位置,如果兩個數組總長度是奇數,事實上這個中位數就是在兩個數組合並之後的最中間的值,否則則是中間位置前後的平均值,因爲在這裏我們不能合併數組,所以不能判斷出結果。但是我們可以記錄每次遍歷兩個數組的位置,從而不斷縮小這個中位數的區間。

那麼關鍵步驟就是:

  1. 二分兩個數組得出第一個數組的left right與第二個數組的left right
  2. 進行二分的迭代或者遞歸
  3. 通過中位數的概念我們可以有當兩個數組總長度爲奇數時,則中位數是:Max(left1, left2)
  4. 當兩個數組總長度爲偶數時,則中位數是:(Max(left1, left2) + Max(right1, right2)) / 2

JS版

/**
 * @func
 * @name findMedian
 * @desc 使用二分法來獲取中位數
 * @param {number} start1 第一個數組需要查找的起始位置
 * @param {number[]} nums1 傳入的第一個數組
 * @param {number} start2 第二個數組需要查找的起始位置
 * @param {number[]} nums2 傳入的第二個數組
 * @param {number} half nums1與nums2的長度中間值
 * @return {number} nums1與nums2的中位數
 */
const findMedian = (start1, nums1, start2, nums2, half) => {
    const [{ length: len1 }, { length: len2 }] = [nums1, nums2]
    // 下面兩個if主要是對空數組的判斷,如果是有一個是空數組,則輸出另一個數組的中位數
    if (start1 >= len1) {
        return nums2[start2 + half - 1]
    }
    if (start2 >= len2) {
        return nums1[start1 + half - 1]
    }
    if (half === 1) { // 此時兩個數組總長度的一半就是等於1,說明兩個數組就只有一個值,那麼就哪個小輸出哪個
        return Math.min(nums1[start1], nums2[start2])
    }
    // 下面的不變量定義主要是爲了查找是否有中間值,如果沒有就賦值爲Infinity,否則則賦值爲這個中間值
    const halfVal1 = (start1 + parseInt(half / 2) - 1 < len1
                        ? nums1[start1 + parseInt(half / 2) - 1]
                        : Infinity)
    const halfVal2 = (start2 + parseInt(half / 2) - 1 < len2
                        ? nums2[start2 + parseInt(half / 2) - 1] 
                        : Infinity)
    // 這裏是二分法的核心,判斷兩個數組中間值的大小,如果nums1的(half / 2)比較小的話,就說明我們找的數字不在nums1的前(half / 2)數字之中,所以我們nums1就往後移動(half / 2)。反之就是nums2往後移動(half / 2)。一直用二分法遞歸對比沒有判斷過的數,從而得到結果值。
    return (halfVal1 < halfVal2
                ? findMedian(start1 + parseInt(half / 2), nums1, start2, nums2, half - parseInt(half / 2))
                : findMedian(start1, nums1, start2 + parseInt(half / 2), nums2, half - parseInt(half / 2)));
}

/**
 * @func
 * @name findMedianSortedArrays
 * @desc 尋找兩個有序數組的中位數
 * @param {number[]} nums1 傳入的第一個數組
 * @param {number[]} nums2 傳入的第二個數組
 * @return {number} 中位數
 */
const findMedianSortedArrays = (nums1, nums2) => {
    const [{ length: len1 }, { length: len2 }] = [nums1, nums2]
    const totalLen = len1 + len2 // 兩個數組的總長度
    return (totalLen % 2 === 1
                ? findMedian(0, nums1, 0, nums2, (totalLen + 1) / 2) // 總長度爲奇數時
                : (findMedian(0, nums1, 0, nums2, totalLen / 2) + findMedian(0, nums1, 0, nums2, totalLen / 2 + 1)) / 2) // 總長度爲偶數時
}

/**
 * @func
 * @name findMedianSortedArrays2
 * @desc 尋找兩個有序數組的中位數
 * @param {number[]} nums1 傳入的第一個數組
 * @param {number[]} nums2 傳入的第二個數組
 * @return {number} 中位數
 */
const findMedianSortedArrays2 = (nums1, nums2) => {
    let len1 = nums1.length
    let len2 = nums2.length

    // 下面交換法主要是爲了統一一個方向,方便迭代
    if (len1 > len2) {
        let lenTemp = len1
        let numsTemp = nums1
        len1 = len2
        len2 = lenTemp
        nums1 = nums2
        nums2 = numsTemp
    }

    let left = 0 // 左邊開始的位置
    let right = len1 // 右邊開始的位置
    let mid = Math.floor((len1 + len2 + 1) / 2) // 中間位置

    // 二分法 迭代模式
    while (left <= right) {
        let i = Math.floor((left + right) / 2) // 左邊遍歷到的位置
        let j = Math.floor(mid - i) // 右邊遍歷到的位置
        if (i < len1 && nums2[j - 1] > nums1[i]) { // 如果當前左邊遍歷的位置還沒到數組1的長度,並且數組二遍歷到的元素是比數組已的元素大的,那麼這該時候就說明i還太小,需要+1
            left = i + 1
        } else if (i > 0 && nums1[i - 1] > nums2[j]) { // 如上,相反的邏輯 - 1
            right = i - 1
        } else {
            let maxLeft
            let maxRight
            // 下面一層二層的判斷是爲了處理當有一個數組長度爲0時的情況
            if (i === 0) {
                maxLeft = nums2[j - 1]
            } else if (j === 0) {
                maxLeft = nums1[i - 1]
            } else { // 此時則是獲取當前遍歷到的兩個數組對比的最大值
                maxLeft = Math.max(nums1[i - 1], nums2[j - 1])
            }
            if ((len1 + len2) % 2 === 1) { // 如果兩個數組的長度爲奇數,可以直接輸出最大值
                return maxLeft
            }
            // 下面則是判斷數組長度加起來爲偶數時的情況
            if (i === len1) { // 當i已經遍歷完時,則右邊中間值爲第二個數組當前遍歷的值
                maxRight = nums2[j]
            } else if (j === len2) { // 與上相反
                maxRight = nums1[i]
            } else { // 獲取當前遍歷到的值中的最大值
                maxRight = Math.min(nums1[i], nums2[j])
            }
            // 最終的值爲left right兩邊中間值的一半
            return (maxLeft + maxRight) / 2
        }
    }
}

TS版

/**
 * @func
 * @name findMedian
 * @desc 使用二分法來獲取中位數
 * @param {number} start1 第一個數組需要查找的起始位置
 * @param {number[]} nums1 傳入的第一個數組
 * @param {number} start2 第二個數組需要查找的起始位置
 * @param {number[]} nums2 傳入的第二個數組
 * @param {number} half nums1與nums2的長度中間值
 * @return {number} nums1與nums2的中位數
 */
const findMedian = (start1: number, nums1: number[], start2: number, nums2: number[], half: number): number => {
    const [{ length: len1 }, { length: len2 }] = [nums1, nums2]
    // 下面兩個if主要是對空數組的判斷,如果是有一個是空數組,則輸出另一個數組的中位數
    if (start1 >= len1) {
        return nums2[start2 + half - 1]
    }
    if (start2 >= len2) {
        return nums1[start1 + half - 1]
    }
    if (half === 1) { // 此時兩個數組總長度的一半就是等於1,說明兩個數組就只有一個值,那麼就哪個小輸出哪個
        return Math.min(nums1[start1], nums2[start2])
    }
    // 下面的不變量定義主要是爲了查找是否有中間值,如果沒有就賦值爲Infinity,否則則賦值爲這個中間值
    const halfVal1 = (start1 + parseInt(half / 2) - 1 < len1
                        ? nums1[start1 + parseInt(half / 2) - 1]
                        : Infinity)
    const halfVal2 = (start2 + parseInt(half / 2) - 1 < len2
                        ? nums2[start2 + parseInt(half / 2) - 1] 
                        : Infinity)
    // 這裏是二分法的核心,判斷兩個數組中間值的大小,如果nums1的(half / 2)比較小的話,就說明我們找的數字不在nums1的前(half / 2)數字之中,所以我們nums1就往後移動(half / 2)。反之就是nums2往後移動(half / 2)。一直用二分法遞歸對比沒有判斷過的數,從而得到結果值。
    return (halfVal1 < halfVal2
                ? findMedian(start1 + parseInt(half / 2), nums1, start2, nums2, half - parseInt(half / 2))
                : findMedian(start1, nums1, start2 + parseInt(half / 2), nums2, half - parseInt(half / 2)));
}

/**
 * @func
 * @name findMedianSortedArrays
 * @desc 尋找兩個有序數組的中位數
 * @param {number[]} nums1 傳入的第一個數組
 * @param {number[]} nums2 傳入的第二個數組
 * @return {number} 中位數
 */
const findMedianSortedArrays = (nums1: number, nums2: number): number => {
    const [{ length: len1 }, { length: len2 }] = [nums1, nums2]
    const totalLen = len1 + len2 // 兩個數組的總長度
    return (totalLen % 2 === 1
                ? findMedian(0, nums1, 0, nums2, (totalLen + 1) / 2) // 總長度爲奇數時
                : (findMedian(0, nums1, 0, nums2, totalLen / 2) + findMedian(0, nums1, 0, nums2, totalLen / 2 + 1)) / 2) // 總長度爲偶數時
}

/**
 * @func
 * @name findMedianSortedArrays2
 * @desc 尋找兩個有序數組的中位數
 * @param {number[]} nums1 傳入的第一個數組
 * @param {number[]} nums2 傳入的第二個數組
 * @return {number} 中位數
 */
const findMedianSortedArrays2 = (nums1: number[], nums2: number[]): number => {
    let len1: number = nums1.length
    let len2: number = nums2.length

    // 下面交換法主要是爲了統一一個方向,方便迭代
    if (len1 > len2) {
        let lenTemp: number = len1
        let numsTemp: number[] = nums1
        len1 = len2
        len2 = lenTemp
        nums1 = nums2
        nums2 = numsTemp
    }

    let left: number = 0 // 左邊開始的位置
    let right: number = len1 // 右邊開始的位置
    let mid: number = Math.floor((len1 + len2 + 1) / 2) // 中間位置

    // 二分法 迭代模式
    while (left <= right) {
        let i: number = Math.floor((left + right) / 2) // 左邊遍歷到的位置
        let j: number = Math.floor(mid - i) // 右邊遍歷到的位置
        if (i < len1 && nums2[j - 1] > nums1[i]) { // 如果當前左邊遍歷的位置還沒到數組1的長度,並且數組二遍歷到的元素是比數組已的元素大的,那麼這該時候就說明i還太小,需要+1
            left = i + 1
        } else if (i > 0 && nums1[i - 1] > nums2[j]) { // 如上,相反的邏輯 - 1
            right = i - 1
        } else {
            let maxLeft: number
            let maxRight: number
            // 下面一層二層的判斷是爲了處理當有一個數組長度爲0時的情況
            if (i === 0) {
                maxLeft = nums2[j - 1]
            } else if (j === 0) {
                maxLeft = nums1[i - 1]
            } else { // 此時則是獲取當前遍歷到的兩個數組對比的最大值
                maxLeft = Math.max(nums1[i - 1], nums2[j - 1])
            }
            if ((len1 + len2) % 2 === 1) { // 如果兩個數組的長度爲奇數,可以直接輸出最大值
                return maxLeft
            }
            // 下面則是判斷數組長度加起來爲偶數時的情況
            if (i === len1) { // 當i已經遍歷完時,則右邊中間值爲第二個數組當前遍歷的值
                maxRight = nums2[j]
            } else if (j === len2) { // 與上相反
                maxRight = nums1[i]
            } else { // 獲取當前遍歷到的值中的最大值
                maxRight = Math.min(nums1[i], nums2[j])
            }
            // 最終的值爲left right兩邊中間值的一半
            return (maxLeft + maxRight) / 2
        }
    }
}

PY版

from typing import List
def findMedian(start1: int, nums1: List[int], start2: int, nums2: List[int], half: int) -> float:
    """
    :type start1: int
    :type nums1: List[int]
    :type start2: int
    :type nums2: List[int]
    :type half: int
    :rtype: float
    """
    len1 = len(nums1)
    len2 = len(nums2)

    if start1 >= len1:
        return nums2[int(start2 + half - 1)]

    if start2 >= len2:
        return nums1[int(start1 + half - 1)]

    if half == 1:
        return min(nums1[start1], nums2[start2])

    halfVal1 = float('Infinity')
    halfVal2 = float('Infinity')

    if (start1 + int(half / 2) - 1) < len1:
        halfVal1 = nums1[start1 + int(half / 2) - 1]

    if (start2 + int(half / 2) - 1) < len2:
        halfVal2 = nums2[start2 + int(half / 2) - 1]


    if halfVal1 < halfVal2:
        return findMedian(start1 + int(half / 2), nums1, start2, nums2, half - int(half / 2))
    else:
        return findMedian(start1, nums1, start2 + int(half / 2), nums2, half - int(half / 2))

def findMedianSortedArrays(nums1: List[int], nums2: List[int]) -> float:
    """
    :type nums1: List[int]
    :type nums2: List[int]
    :rtype: float
    """
    len1 = len(nums1)
    len2 = len(nums2)
    totalLen = len1 + len2
    if totalLen % 2 == 1:
        return findMedian(0, nums1, 0, nums2, (totalLen + 1) / 2)
    else:
        return (findMedian(0, nums1, 0, nums2, totalLen / 2) + findMedian(0, nums1, 0, nums2, totalLen / 2 + 1)) / 2

如果你喜歡探討技術,或者對本文有任何的意見或建議,非常歡迎加魚頭微信好友一起探討,當然,魚頭也非常希望能跟你一起聊生活,聊愛好,談天說地。
魚頭的微信號是:krisChans95
也可以掃碼添加好友,備註“csdn”就行
https://fish-pond-1253945200.cos.ap-guangzhou.myqcloud.com/img/base/wx-qrcode1.jpg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章