多種解法破中等題

  • 2019 年 10 月 5 日
  • 筆記

多種解法破中等題

0.說在前面1.數組中的第K個最大元素1.0 問題1.1 降序方法1.2 遞歸快排1.3 非遞歸快排1.4 最大堆排序1.5 最小堆排序2.二叉搜索樹中第K小的元素2.0 問題2.1 遞歸中序遍歷2.2 非遞歸中序遍歷

0.說在前面

本周還差一次,今天刷兩道,分別為數組中的第k個最大元素與二叉搜索樹中第k小的元素!

1.數組中的第K個最大元素

1.0 問題

在未排序的數組中找到第 k 個最大的元素。請注意,你需要找的是數組排序後的第 k 個最大的元素,而不是第 k 個不同的元素。

示例 1:

輸入: [3,2,1,5,6,4] 和 k = 2  輸出: 5  

示例 2:

輸入: [3,2,3,1,2,4,5,5,6] 和 k = 4  輸出: 4  

1.1 降序方法

思路

直接將數組排序即可,然後取出第k-1個元素就是結果!

實現

def findKthLargest(self, nums, k):      if not nums:          return None      nums.sort(reverse=True)      return nums[k-1]  

分析

實踐複雜度為O(nlogn),空間複雜度為O(1)

1.2 遞歸快排

思想

既然是尋找第k個最大元素,我們想到排序中的一些演算法,發現快排是最合適的,怎麼說呢?

快排是以樞椎privot為界限,分左邊與右邊,我們假設是降序快排,那麼樞椎左邊的元素都比樞椎值本身大,右邊的元素都比樞椎值本身小,這就引出了一個思想:當某次快排結束後,剛好這個樞椎放在了第k個位置上,那麼我們可以確定,這個元素就是第k個最大!

上面思想轉化為具體的思路為:樞椎值最後填充的位置假設為i,那麼i+1就是真實的位置,因為index從0開始,而當i+1與k相等,就是第k個最大;如果i+1小於k,那麼我們就需要從i的後面元素尋找第k個最大;如果i+1大於k,那麼我們就需要從i的前面元素尋找第k個最大!

實現

def findKthLargest(self, nums, k):      return self.quicksort(nums,0,len(nums)-1,k)  def quicksort(self,nums,low,high,k):      if low>high:          return      privot = nums[low]      i = low      j = high      while i<j:          while i<j and nums[j]<=privot:              j-=1          if i<j:              nums[i]=nums[j]              i+=1          while i<j and nums[i]>privot:              i+=1          if i<j:              nums[j]=nums[i]              j-=1      nums[i] = privot      # 核心思路!下面則是上面寫的思路的程式碼實現,而上面的程式碼就是快排的核心思想!      if i+1 == k:          return privot      if i+1 > k:          return self.quicksort(nums,low, i-1,k)      else:          return self.quicksort(nums,i+1,high,k)  

分析

時間複雜度為O(nlogn),空間複雜度為O(1)

1.3 非遞歸快排

非遞歸與遞歸程式碼變動不大,主要是對於外層加了個循環條件,將內層的遞歸返回處改變上下界即可!

def findKthLargest(self, nums, k):      return self.quicksort(nums,0,len(nums)-1,k)  def quicksort(self,nums,low,high,k):      # 這裡一定得加等於號!      while low<=high:          if low>high:              return          privot = nums[low]          i = low          j = high          while i<j:              while i<j and nums[j]<=privot:                  j-=1              if i<j:                  nums[i]=nums[j]                  i+=1              while i<j and nums[i]>privot:                  i+=1              if i<j:                  nums[j]=nums[i]                  j-=1          nums[i] = privot          if i+1 == k:              return privot          if i+1 > k:              high=i-1          else:              low=i+1  

1.4 最大堆排序

思路

這裡使用了heapq來構建堆,調用其中的方法來實現,由於默認採用的是最小堆,那麼怎麼樣變為最大堆呢?

原數組中的數據變為負數即可!第k最大轉化為最大堆第k次pop出去的值,也就是最小堆pop出去的負數!

實現

def findKthLargest(self, nums, k):      import heapq      nums = [-i for i in nums]      heapq.heapify(nums)      for i in range(k):          res=heapq.heappop(nums)      return -res  

分析

時間複雜度為O(n+klogn),遍歷了一遍list,將每個數變為負數O(n),循環k次,每次pop複雜度O(logn),則為O(klogn),最終實踐複雜度為O(n+logn)!

空間複雜度為O(n)。

1.5 最小堆排序

思路

這裡採用每次維護含k個元素的堆,我們知道最小堆堆頂為所有元素中最小的,也就是說,我每次維護的含k個元素的最小堆,堆pop出來就是我們需要的第k個最大元素!

那麼怎麼維護呢?

就是只要nums中的元素比堆頂元素大,那麼替換調這個堆頂,重新調堆,直到所有的元素循環完畢!heappush方法實現了push數據同時調整堆!

實現

def findKthLargest(self, nums, k):      import heapq      # 初始化堆,包含k個元素      # 元素值為負窮大      min_heap=[-float('inf')]*k      heapq.heapify(min_heap)      for i in nums:          if i>min_heap[0]:              heapq.heappop(min_heap)              heapq.heappush(min_heap,i)      return min_heap[0]  

分析

時間複雜度為O(k+(n-k) logk),空間複雜度為O(k)。

參考資料: https://leetcode.com/problems/kth-largest-element-in-an-array/discuss/167837/Python-or-tm

2.二叉搜索樹中第K小的元素

2.0 問題

給定一個二叉搜索樹,編寫一個函數 kthSmallest 來查找其中第 k 個最小的元素。

說明: 你可以假設 k 總是有效的,1 ≤ k ≤ 二叉搜索樹元素個數。

示例 1:

輸入: root = [3,1,4,null,2], k = 1     3    /    1   4         2  輸出: 1  

示例 2:

輸入: root = [5,3,6,2,4,null,null,1], k = 3         5        /        3   6      /      2   4    /   1  輸出: 3  

2.1 遞歸中序遍歷

思路就是採用中序遍歷有序性來解決,中序遍歷得到的結果是有序的,那麼取第k-1個元素就是第k個最小元素!

class Solution:      def __init__(self):          self.stack=[]        def kthSmallest(self, root, k):          self.inOrder(root)          return self.stack[k-1]      def inOrder(self,root):          if root is None:              return          self.inOrder(root.left)          self.stack.append(root.val)          self.inOrder(root.right)  

2.2 非遞歸中序遍歷

這裡思路以一個例子來模擬:

輸入: root = [5,3,6,2,4,null,null,1], k = 3         5        /        3   6      /      2   4    /   1  輸出: 3  

如上這個例子,首先我們來從根節點開始循環遍歷左孩子,並添加到棧中,此時棧元素有[5,3,2,1],然後pop掉最後一個元素,得到stack=[5,3,2],而k不為1,所以彈出來的不是第一個最小的,此時由於彈出了一個元素,那麼我們也將k減去1,也就是從剩下的元素找第K-1個最小,後面依此類推。繼續模擬:k=2,1無有節點,直接pop掉2,k減去1後為1,2無右結點,然後stack中為[5,3],pop掉3,此時k剛好為1,那麼就是最後的結果3!

實質就是將左孩子入棧,然後判斷當前結點有無有孩子,如果有有孩子,則出棧,否則繼續添加右孩子的左孩子,重複這個操作即可!你會發現,每次pop的元素就是按照從小到大的順序pop的,所以不斷讓k減去1,每次pop掉,k減少,當k為1的時候,pop的元素就是最終解!

def kthSmallest(self, root, k):      stack = []      while stack or root:          while root:              stack.append(root)              root = root.left          root = stack.pop()          if k==1:              return root.val          else:              k-=1          root=root.right  

參考資料: https://leetcode.com/problems/kth-smallest-element-in-a-bst/discuss/131184/Python-3-iterative-and-recursive-solution