lintcode題解 · 看雲 - Google Chrome
原版文章:
題解:
尋找未排序數組的中位數,簡單粗暴的方法是先排序後輸出中位數索引處的數,但是基於比較的排序算法的時間復雜度為 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)View Codedef 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))
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)); } } }; JavaView 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