1. 程式人生 > 實用技巧 >每日一題 - 劍指 Offer 54. 二叉搜尋樹的第k大節點

每日一題 - 劍指 Offer 54. 二叉搜尋樹的第k大節點

題目資訊

  • 時間: 2019-07-04

  • 題目連結:Leetcode

  • tag:二叉搜尋樹 中序遍歷 遞迴

  • 難易程度:中等

  • 題目描述:

    給定一棵二叉搜尋樹,請找出其中第k大的節點。

示例1:

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

示例2:

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

注意

1. 1 ≤ k ≤ 二叉搜尋樹元素個數

解題思路

本題難點

二叉排序樹:根節點的值大於左子樹的值,小於右子樹的值。查詢第K大節點。

具體思路

二叉搜尋樹的中序遍歷為 遞增序列 ,易得二叉搜尋樹的 中序遍歷倒序遞減序列

求 “二叉搜尋樹第 k大的節點” 可轉化為求 “此樹的中序遍歷倒序的第 k個節點”。

  • 中序遍歷

    // 列印中序遍歷
    void dfs(TreeNode root) {
    if(root == null) return;
    dfs(root.left); // 左
    System.out.println(root.val); // 根
    dfs(root.right); // 右
    }
  • 中序遍歷的倒序

    // 列印中序遍歷倒序
    void dfs(TreeNode root) {
    if(root == null) return;
    dfs(root.right); // 右
    System.out.println(root.val); // 根
    dfs(root.left); // 左
    }

求第 k大節點,需要實現以下 三項工作

  • 遞迴遍歷時計數,統計當前節點的序號;
  • 遞迴到第 k個節點時,應記錄結果 res ;
  • 記錄結果後,後續的遍歷即失去意義,應提前終止(即返回)。

提示:在獲得res結果時,增加一個return語句可以避免之後的無效迭代dfs(root.left);

程式碼

class Solution {
////形參k不能隨著dfs的迭代而不斷變化,為了記錄迭代程式和結果,引入類變數count和res
int count=0, res=0;
public int kthLargest(TreeNode root, int k) {
this.count = k;
dfs(root);
return res;
} public void dfs(TreeNode root){
// 若 k=0 ,代表已找到目標節點,無需繼續遍歷,因此直接返回;
if(root == null || count == 0){
return;
}
//遞迴右子樹
dfs(root.right);
//統計序號: 執行 k=k−1 (即從 k 減至 0 );
//記錄結果: 若 k=0 ,代表當前節點為第 k 大的節點,因此記錄 res=root.val ;
if(--count == 0){
res = root.val;
return;
}
//遞迴左子樹
dfs(root.left);
}
}

複雜度分析:

  • 時間複雜度 O(N) :當樹退化為連結串列時(全部為右子節點),無論 k的值大小,遞迴深度都為 N,佔用 O(N) 時間。
  • 空間複雜度 O(N) :當樹退化為連結串列時(全部為右子節點),系統使用 O(N) 大小的棧空間。

其他優秀解答

解題思路

中序遍歷倒序的非遞迴演演算法。

程式碼

class Solution {
public int kthLargest(TreeNode root, int k) {
int count = 1;
Stack<TreeNode> stack = new Stack<>();
while (Objects.nonNull(root) || !stack.empty()) {
while (Objects.nonNull(root)) {
stack.push(root);
root = root.right;
}
TreeNode pop = stack.pop();
if (count == k) {
return pop.val;
}
count++;
root = pop.left;
}
return 0;
}
}