1. 程式人生 > >[LeetCode] Closest Binary Search Tree Value II 最近的二分搜尋樹的值之二

[LeetCode] Closest Binary Search Tree Value II 最近的二分搜尋樹的值之二

Given a non-empty binary search tree and a target value, find k values in the BST that are closest to the target.

Note:

  • Given target value is a floating point.
  • You may assume k is always valid, that is: k ≤ total nodes.
  • You are guaranteed to have only one unique set of k values in the BST that are closest to the target.

Follow up:
Assume that the BST is balanced, could you solve it in less than O(n) runtime (where n = total nodes)?

Hint:

1. Consider implement these two helper functions:
  i. getPredecessor(N), which returns the next smaller node to N.
  ii. getSuccessor(N), which returns the next larger node to N.
2. Try to assume that each node has a parent pointer, it makes the problem much easier.
3. Without parent pointer we just need to keep track of the path from the root to the current node using a stack.
4. You would need two stacks to track the path in finding predecessor and successor node separately.

這道題是之前那道Closest Binary Search Tree Value的拓展,那道題只讓我們找出離目標值最近的一個節點值,而這道題讓我們找出離目標值最近的k個節點值,難度瞬間增加了不少,我最先想到的方法是用中序遍歷將所有節點值存入到一個一維陣列中,由於二分搜尋樹的性質,這個一維陣列是有序的,然後我們再在有序陣列中需要和目標值最近的k個值就簡單的多,參見程式碼如下:

解法一:

class Solution {
public:
    vector<int> closestKValues(TreeNode* root, double target, int
k) { vector<int> res, v; inorder(root, v); int idx = 0; double diff = numeric_limits<double>::max(); for (int i = 0; i < v.size(); ++i) { if (diff >= abs(target - v[i])) { diff = abs(target - v[i]); idx = i; } } int left = idx - 1, right = idx + 1; for (int i = 0; i < k; ++i) { res.push_back(v[idx]); if (left >= 0 && right < v.size()) { if (abs(v[left] - target) > abs(v[right] - target)) { idx = right; ++right; } else { idx = left; --left; } } else if (left >= 0) { idx = left; --left; } else if (right < v.size()) { idx = right; ++right; } } return res; } void inorder(TreeNode *root, vector<int> &v) { if (!root) return; inorder(root->left, v); v.push_back(root->val); inorder(root->right, v); } };

還有一種解法是直接在中序遍歷的過程中完成比較,當遍歷到一個節點時,如果此時結果陣列不到k個,我們直接將此節點值加入res中,如果該節點值和目標值的差值的絕對值小於res的首元素和目標值差值的絕對值,說明當前值更靠近目標值,則將首元素刪除,末尾加上當前節點值,反之的話說明當前值比res中所有的值都更偏離目標值,由於中序遍歷的特性,之後的值會更加的遍歷,所以此時直接返回最終結果即可,參見程式碼如下:

解法二:

class Solution {
public:
    vector<int> closestKValues(TreeNode* root, double target, int k) {
        vector<int> res;
        inorder(root, target, k, res);
        return res;
    }
    void inorder(TreeNode *root, double target, int k, vector<int> &res) {
        if (!root) return;
        inorder(root->left, target, k, res);
        if (res.size() < k) res.push_back(root->val);
        else if (abs(root->val - target) < abs(res[0] - target)) {
            res.erase(res.begin());
            res.push_back(root->val);
        } else return;
        inorder(root->right, target, k, res);
    }
};

下面這種方法是上面那種方法的迭代寫法,原理一模一樣,參見程式碼如下:

解法三:

class Solution {
public:
    vector<int> closestKValues(TreeNode* root, double target, int k) {
        vector<int> res;
        stack<TreeNode*> s;
        TreeNode *p = root;
        while (p || !s.empty()) {
            while (p) {
                s.push(p);
                p = p->left;
            }
            p = s.top(); s.pop();
            if (res.size() < k) res.push_back(p->val);
            else if (abs(p->val - target) < abs(res[0] - target)) {
                res.erase(res.begin());
                res.push_back(p->val);
            } else break;
            p = p->right;
        }
        return res;
    }
};

在來看一種利用最大堆來解題的方法,堆裡儲存的一個差值diff和節點值的pair,我們中序遍歷二叉樹(也可以用其他遍歷方法),然後對於每個節點值都計算一下和目標值之差的絕對值,由於最大堆的性質,diff大的自動拍到最前面,我們維護k個pair,如果超過了k個,就把堆前面大的pair刪掉,最後留下的k個pair,我們將pair中的節點值取出存入res中返回即可,參見程式碼如下: 

解法四:

class Solution {
public:
    vector<int> closestKValues(TreeNode* root, double target, int k) {
        vector<int> res;
        priority_queue<pair<double, int>> q;
        inorder(root, target, k, q);
        while (!q.empty()) {
            res.push_back(q.top().second);
            q.pop();
        }
        return res;
    }
    void inorder(TreeNode *root, double target, int k, priority_queue<pair<double, int>> &q) {
        if (!root) return;
        inorder(root->left, target, k, q);
        q.push({abs(root->val - target), root->val});
        if (q.size() > k) q.pop();
        inorder(root->right, target, k, q);
    }
};

下面的這種方法用了兩個棧,pre和suc,其中pre存小於目標值的數,suc存大於目標值的數,開始初始化pre和suc的時候,要分別將最接近目標值的稍小值和稍大值壓入pre和suc,然後我們迴圈k次,每次比較pre和suc的棧頂元素,看誰更接近目標值,將其存入結果res中,然後更新取出元素的棧,依次類推直至取完k個數返回即可,參見程式碼如下:

解法五:

class Solution {
public:
    vector<int> closestKValues(TreeNode* root, double target, int k) {
        vector<int> res;
        stack<TreeNode*> pre, suc;
        while (root) {
            if (root->val <= target) {
                pre.push(root);
                root = root->right;
            } else {
                suc.push(root);
                root = root->left;
            }
        }
        while (k-- > 0) {
            if (suc.empty() || !pre.empty() && target - pre.top()->val < suc.top()->val - target) {
                res.push_back(pre.top()->val);
                getPredecessor(pre);
            } else {
                res.push_back(suc.top()->val);
                getSuccessor(suc);
            }
        }
        return res;
    }
    void getPredecessor(stack<TreeNode*> &pre) {
        TreeNode *t = pre.top(); pre.pop();
        if (t->left) {
            pre.push(t->left);
            while (pre.top()->right) pre.push(pre.top()->right);
        }
    }
    void getSuccessor(stack<TreeNode*> &suc) {
        TreeNode *t = suc.top(); suc.pop();
        if (t->right) {
            suc.push(t->right);
            while (suc.top()->left) suc.push(suc.top()->left);
        }
    }
};

類似題目:

參考資料: