出处
出镜率很高的一道题 https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/
77
统计右子树结点
首先要读清楚题目,搞清楚第k大到底是指什么
然后就可以开始做了
首先输入是一颗二叉搜索树,满足左小右大的定义,输入数据k不会越界
观察规律发现,如果k=1并且当前节点没有右孩子时,当前根节点就是要找的结点 —— 边界情况
继续观察,设lnum为根节点左子树结点个数,rnum为根节点右子树结点个数,那么当k-rnum==1时,当前结点就是要找的结点 —— 一般情况
根据一般情况推导,我们发现k-rnum还有另外两种可能
k - rnum < 1 —— 意味着我们要往右子树寻找
k - rnum > 1 —— 意味着我们要往左子树寻找
回到实际意义
k - rnum小于1的时候,表示右子树结点个数足够多,我们要找的结点存在于右子树
k - rnum大于1的时候,表示右子树结点个数不够多,我们要找的结点存在于左子树,例如k=8,rnum=5的时候,明显此时右子树没有符合的结点,应该往左子树走
目前为止我们有了边界情况、一般情况下的三种状态,现在我们还需要完善另外两种状态的处理
- 当我们往右子树走的时候,k不需要改变,因为右子树此时必然存在目标结点,并且在递归过程中总会满足 k-rnum==1 这个状态,所以能找到目标结点
- 当我们往左子树走的时候,k需要改变大小,因为我们往左子树走的过程中,相当于构造一颗新树,这颗新树是当前树舍去根结点和右子树形成的,此时我们相当于递归找第k-rnum-1个结点
分析完了,我们发现解题并不需要用到lnum
下面是伪代码
void func(Tree T,int k){
// 1 边界情况
如果k==1并且T->right==null,返回T->val
否则 // 2 一般情况
获得右子树的结点数rnum
val = k - rnum
if val == 1:
返回T->val // 2.1
else if val>1:
return func(T->left,val-1); // 2.2
else:
return func(T->right,k); // 2.3
}
写出伪码后,具体实现就很简单了
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
# 边界情况
if k==1 and not root.right:
return root.val
# lnum = self.getNum(root.left)
rnum = self.getNum(root.right)
val = k-rnum
if val == 1:
return root.val
elif val > 1:
return self.kthLargest(root.left,val-1)
else:
return self.kthLargest(root.right,k)
def getNum(self,root):
if not root:
return 0
if not root.left and not root.right:
return 1
return self.getNum(root.left)+self.getNum(root.right)+1
以上就是这道题的一种做法,也是比较容易想到的解法
中序逆序遍历
接下来介绍官方题解的做法:
思想很巧妙,大意就是,二叉搜索树按照左-中-右的顺序遍历时,得到的结果是升序序列,按照右-中-左的顺序遍历时,得到的结果是降序序列,那么寻找第k大个数,相当于寻找降序序列中第k个结点
我们按照右-中-左的顺序遍历,遍历过程中,如果k==0,则表示当前节点就是答案,否则k-=1,继续递归
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
def dfs(root):
if not root:
return
dfs(root.right)
if self.k == 0:
return
self.k -= 1
if self.k == 0:
self.res = root.val
dfs(root.left)
self.k = k
dfs(root)
return self.res
扩展
事实上这样右中左的变换,在迭代遍历二叉树的时候也有应用,例如我们可以用栈来实现先序遍历的迭代做法,使用栈来实现后序遍历的迭代做法,具体可以看之前的文章
https://blog.csdn.net/hhmy77/article/details/103841102