多种解法破中等题
- 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