1. 程式人生 > >lintcode題解 · 看雲 - Google Chrome

lintcode題解 · 看雲 - Google Chrome

imp 位置 relative mil swap HA 遞歸 strong median

原版文章:

題解:

尋找未排序數組的中位數,簡單粗暴的方法是先排序後輸出中位數索引處的數,但是基於比較的排序算法的時間復雜度為 O(nlogn)O(n \log n)O(nlogn), 不符合題目要求。線性時間復雜度的排序算法常見有計數排序、桶排序和基數排序,這三種排序方法的空間復雜度均較高,且依賴於輸入數據特征(數據分布在有限的區間內),用在這裏並不是比較好的解法。

由於這裏僅需要找出中位數,即找出數組中前半個長度的較大的數,不需要進行完整的排序,說到這你是不是想到了快速排序了呢?快排的核心思想就是以基準為界將原數組劃分為左小右大兩個部分,用在這十分合適。快排的實現見 Quick Sort, 由於調用一次快排後基準元素的最終位置是知道的,故遞歸的終止條件即為當基準元素的位置(索引)滿足中位數的條件時(左半部分長度為原數組長度一半)即返回最終結果。由於函數原型中左右最小索引並不總是原數組的最小最大,故需要引入相對位置(長度)也作為其中之一的參數。若左半部分長度偏大,則下一次遞歸排除右半部分,反之則排除左半部分。

Python代碼:

技術分享圖片
class Solution:
    """
    @param nums: A list of integers.
    @return: An integer denotes the middle number of the array.
    """
    def median(self, nums):
        if not nums:
            return -1
        return self.helper(nums, 0, len(nums) - 1, (1 + len(nums)) / 2)

    
def helper(self, nums, l, u, size): if l >= u: return nums[u] m = l for i in xrange(l + 1, u + 1): if nums[i] < nums[l]: m += 1 nums[m], nums[i] = nums[i], nums[m] # swap between m and l after partition, important!
nums[m], nums[l] = nums[l], nums[m] if m - l + 1 == size: return nums[m] elif m - l + 1 > size: return self.helper(nums, l, m - 1, size) else: return self.helper(nums, m + 1, u, size - (m - l + 1))
View Code

C++代碼:

技術分享圖片
class Solution {
public:
    /**
     * @param nums: A list of integers.
     * @return: An integer denotes the middle number of the array.
     */
    int median(vector<int> &nums) {
        if (nums.empty()) return 0;

        int len = nums.size();
        return helper(nums, 0, len - 1, (len + 1) / 2);
    }

private:
    int helper(vector<int> &nums, int l, int u, int size) {
        // if (l >= u) return nums[u];

        int m = l; // index m to track pivot
        for (int i = l + 1; i <= u; ++i) {
            if (nums[i] < nums[l]) {
                ++m;
                int temp = nums[i];
                nums[i] = nums[m];
                nums[m] = temp;
            }
        }

        // swap with the pivot
        int temp = nums[m];
        nums[m] = nums[l];
        nums[l] = temp;

        if (m - l + 1 == size) {
            return nums[m];
        } else if (m - l + 1 > size) {
            return helper(nums, l, m - 1, size);
        } else {
            return helper(nums, m + 1, u, size - (m - l + 1));
        }
    }
};
Java
View Code

Java代碼:

技術分享圖片
public class Solution {
    /**
     * @param nums: A list of integers.
     * @return: An integer denotes the middle number of the array.
     */
    public int median(int[] nums) {
        if (nums == null) return -1;

        return helper(nums, 0, nums.length - 1, (nums.length + 1) / 2);
    }

    // l: lower, u: upper, m: median
    private int helper(int[] nums, int l, int u, int size) {
        if (l >= u) return nums[u];

        int m = l;
        for (int i = l + 1; i <= u; i++) {
            if (nums[i] < nums[l]) {
                m++;
                int temp = nums[m];
                nums[m] = nums[i];
                nums[i] = temp;
            }
        }
        // swap between array[m] and array[l]
        // put pivot in the mid
        int temp = nums[m];
        nums[m] = nums[l];
        nums[l] = temp;

        if (m - l + 1 == size) {
            return nums[m];
        } else if (m - l + 1 > size) {
            return helper(nums, l, m - 1, size);
        } else {
            return helper(nums, m + 1, u, size - (m - l + 1));
        }
    }
}
View Code

源碼分析:

以相對距離(長度)進行理解,遞歸終止步的條件一直保持不變(比較左半部分的長度)。

以題目中給出的樣例進行分析,size 傳入的值可為(len(nums) + 1) / 2, 終止條件為m - l + 1 == size, 含義為基準元素到索引為l的元素之間(左半部分)的長度(含)與(len(nums) + 1) / 2相等。若m - l + 1 > size, 即左半部分長度偏大,此時遞歸終止條件並未變化,因為l的值在下一次遞歸調用時並未改變,所以仍保持為size; 若m - l + 1 < size, 左半部分長度偏小,下一次遞歸調用右半部分,由於此時左半部分的索引值已變化,故size應改為下一次在右半部分數組中的終止條件size - (m - l + 1), 含義為原長度size減去左半部分數組的長度m - l + 1.

復雜度分析:

和快排類似,這裏也有最好情況與最壞情況,平均情況下,索引m每次都處於中央位置,即每次遞歸後需要遍歷的數組元素個數減半,故總的時間復雜度為 O(n(1+1/2+1/4+...))=O(2n)O(n (1 + 1/2 + 1/4 + ...)) = O(2n)O(n(1+1/2+1/4+...))=O(2n), 最壞情況下為平方。使用了臨時變量,空間復雜度為 O(1)O(1)O(1), 滿足題目要求。

個人思緒很混亂, 建議直接觀看原文鏈接

個人理解如下:

代碼加註釋

技術分享圖片
import random


class Solution:
    """此方法只考慮列表長度為基數的情況"""
    def median(self, nums):
        if not nums:
            return -1
        return self.helper(nums, 0, len(nums) - 1, (1 + len(nums)) / 2)
    """
    1. 如果列表為空, 直接返回-1
    2. 反之, 將helper()函數返回 
    """

    def helper(self, nums, l, u, size):
        if l >= u:
            return nums[u]
        """helper()函數的參數
            nums: 列表, l: 首元素下標, u: 尾元素下標, size: 中值的下標
            中值: 有序列表中間位置處的元素
        """

        m = l # 設定指針m, 起始為首元素下標: 0
        for i in range(l + 1, u + 1):
            # i指針為 從第二個元素到尾元素的下標
            if nums[i] < nums[l]:
                # nums[l]: 設定首元素為基準值, 遍歷列表, 讓每一個元素與之比較
                m += 1
                # 如果當前元素小於基準值, m指針後移一位
                nums[m], nums[i] = nums[i], nums[m]
                # 讓i指針處的元素與m指針處的元素, 進行交換
        # 註: m指針始終指向比基準值小的元素,

        # swap between m and l after partition, important!
        # 循環結束, m指針左側元素都比基準值小, 右側元素都比基準值大
        # 將基準值插入到m指針處
        nums[m], nums[l] = nums[l], nums[m]



        if m - l + 1 == size:
            # 元素下標加一為: 當前元素到首元素的長度[包含首元素]
            # 如果左列表的長度, 等於中值的下標, 那麽m指向的元素就是列表的中值
            return nums[m]
        elif m - l + 1 > size:
            # 如果左列表的長度, 大於中值的下標
            # 將左列表進行helper()的調用, 因為是左側列表, 所以l的值不用變, m-1: m不為中值元素, 所以列表不用包含m
            return self.helper(nums, l, m - 1, size)
        else:
            # 如果左列表的長度, 小於中值的下標
            # 將右列表返回, 設定m+1為起始元素的下標, 因為是右列表所以u的值不用變, size要變為, 中值下標減去左列表的長度
            return self.helper(nums, m + 1, u, size - (m - l + 1))



if __name__ == "__main__":
    l = list(i for i in range(1, 12))
    print("洗牌之前的列表:" + str(l))
    random.shuffle(l)
    print("洗牌之後的列表:" + str(l))
    a = Solution()
    print(a.median(l))
View Code

lintcode題解 · 看雲 - Google Chrome