每日一道 LeetCode (47):尋找兩個正序數組的中位數

每天 3 分鐘,走上算法的逆襲之路。

前文合集

每日一道 LeetCode 前文合集

代碼倉庫

GitHub: https://github.com/meteor1993/LeetCode

Gitee: https://gitee.com/inwsy/LeetCode

題目:尋找兩個正序數組的中位數

難度:困難

題目來源:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/

給定兩個大小爲 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

解題方案一:暴力法

果然是困難難度的題,這道題最簡單的暴力法我都調了半天程序才調好。

暴力法的思路很簡單,兩個有序數組,直接拼起來組合成一個大的有序數組,然後在這個有序數組裏面取中位數。

思路很簡單,不過代碼還是稍微有點不是那麼好寫的,還好給的是有序數組,如果是無序數組,合併的難度更大。

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int n1 = nums1.length;
    int n2 = nums2.length;
    int[] nums = new int[n1 + n2];
    // 先處理極限情況, nums1 爲空
    if (n1 == 0) {
        // 如果 nums2 長度是偶數
        if (n2 % 2 == 0) {
            return (nums2[n2 / 2 - 1] + nums2[n2 / 2]) / 2.0;
        } else {
            return nums2[n2 / 2];
        }
    }
    // 同上,如果 nums2 爲空
    if (n2 == 0) {
        if (n1 % 2 == 0) {
            return (nums1[n1 / 2 - 1] + nums1[n1 / 2]) / 2.0;
        } else {
            return nums1[n1 / 2];
        }
    }

    // 定義新數組的指針
    int count = 0;
    int i = 0, j = 0;

    while (count != (n1 + n2)) {
        // 當 nums1 循環結束而 nums2 還有的時候
        if (i == n1) {
            while (j != n2){
                nums[count++] = nums2[j++];
            }
            break;
        }
        // 當 nums2 循環結束而 nums1 還有的時候
        if (j == n2) {
            while (i != n1) {
                nums[count++] = nums1[i++];
            }
            break;
        }
        if (nums1[i] < nums2[j]) {
            nums[count++] = nums1[i++];
        } else {
            nums[count++] = nums2[j++];
        }
    }

    if (count % 2 == 0) {
        return (nums[count / 2 - 1] + nums[count / 2]) / 2.0;
    } else {
        return nums[count / 2];
    }
}

好了,我這個智商基本上對一道困難難度的題能想到這裏應該已經算不錯的了,剩下的就開始翻答案了。

解題方案二:二分法

感覺答案上給這個方案叫二分法並不是很合適。

這個方案的思路是遮掩的,兩個數組 nums1 和 nums2 的長度已經確定了,那麼中位數的的位置肯定是 (nums1.length + nums2.length) / 2 ,假設長度正好是奇數哈,如果定義剛纔那個位置是 k 的話,這個問題就可以成功的轉化成另一個問題,如何在兩個有序數組中尋找第 k 小的數字。

可以定義分別爲兩個數組定義兩個指針,分別指向兩個數組開頭的位置,然後移動兩個指針,直到第 k 次移動,那麼我們就找到了第 k 小的數字。這種方案相當於還是要循環兩個數組,接着想辦法減少循環次數。

那麼就不能是使用循環了,這裏就用到了 「二分法」 。

  • 兩個數組 nums1 和 nums2 ,先取 nums1[k/2 - 1] 記爲 pivot1 和 nums2[k/2 - 1] 記爲 pivot2 。
  • 接着取 pivot = min(pivot1, pivot2) ,那麼這時兩個數組中小於等於 pivot 的最多不會超過 k - 2 個。
  • 這時 pivot 最大也可能是第 k-1 小的元素。
  • 如果 pivot = pivot1 ,那麼在 nums1 中,從 0 到 k / 2 - 1 都不會是第 k 個元素,把這些元素全部 "刪除",剩下的作爲新的 nums1 數組。
  • 如果 pivot = pivot2 ,那麼在 nums2 中,從 0 到 k / 2 - 1 都不會是第 k 個元素,把這些元素全部 "刪除",剩下的作爲新的 nums2 數組。
  • 由於前面刪除了一些元素,這時我們需要修改 k 的值,減去我們刪掉的元素。
  • 重複上面的過程,就可以找到第 k 個元素了。
public double findMedianSortedArrays_1(int[] nums1, int[] nums2) {
    int l1 = nums1.length, l2 = nums2.length;
    int totalLength = l1 + l2;
    if (totalLength % 2 == 1) {
        int midIndex = totalLength / 2;
        return getKthElement(nums1, nums2, midIndex + 1);
    } else {
        int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
        return (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
    }
}

// 獲取第 k 小的數字
private int getKthElement(int[] nums1, int[] nums2, int k) {
    int length1 = nums1.length, length2 = nums2.length;
    int index1 = 0, index2 = 0;

    while (true) {
        // 邊界情況
        if (index1 == length1) {
            return nums2[index2 + k - 1];
        }
        if (index2 == length2) {
            return nums1[index1 + k - 1];
        }
        if (k == 1) {
            return Math.min(nums1[index1], nums2[index2]);
        }

        // 正常情況
        int half = k / 2;
        int newIndex1 = Math.min(index1 + half, length1) - 1;
        int newIndex2 = Math.min(index2 + half, length2) - 1;
        int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
        if (pivot1 <= pivot2) {
            k -= (newIndex1 - index1 + 1);
            index1 = newIndex1 + 1;
        } else {
            k -= (newIndex2 - index2 + 1);
            index2 = newIndex2 + 1;
        }
    }
}

果然不愧是困難模式,上面這段操作我看了好幾遍,還自己徒手推導了好幾次,才理解答案中的含義,建議各位看不大懂的小夥伴自己在紙上畫兩個數組,徒手推導一次,絕對事半功倍。

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