1. 程式人生 > >[LeetCode] Delete Node in a BST 刪除二叉搜尋樹中的節點

[LeetCode] Delete Node in a BST 刪除二叉搜尋樹中的節點

Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return the root node reference (possibly updated) of the BST.

Basically, the deletion can be divided into two stages:

  1. Search for a node to remove.
  2. If the node is found, delete the node.

Note: Time complexity should be O(height of tree).

Example:

root = [5,3,6,2,4,null,7]
key = 3

    5
   / \
  3   6
 / \   \
2   4   7

Given key to delete is 3. So we find the node with value 3 and delete it.

One valid answer is [5,4,6,2,null,null,7], shown in the following BST.

    5
   / \
  4   6
 /     \
2       7

Another valid answer is [5,2,6,null,4,null,7].

    5
   / \
  2   6
   \   \
    4   7

這道題讓我們刪除二叉搜尋樹中的一個節點,這道題的難點在於刪除完節點並補上那個節點的位置後還應該是一棵二叉搜尋樹。被刪除掉的節點位置,不一定是由其的左右子節點補上,比如下面這棵樹:

         7
        / \
       4   8
     /   \   
    2     6
     \   /
      3 5

如果我們要刪除節點4,那麼應該將節點5補到4的位置,這樣才能保證還是BST,那麼結果是如下這棵樹:

         7
        / \
       5   8
     /   \   
    2     6
     \   
      3

我們先來看一種遞迴的解法,首先判斷根節點是否為空。由於BST的左<根<右的性質,使得我們可以快速定位到要刪除的節點,我們對於當前節點值不等於key的情況,根據大小關係對其左右子節點分別呼叫遞迴函式。若當前節點就是要刪除的節點,我們首先判斷是否有一個子節點不存在,那麼我們就將root指向另一個節點,如果左右子節點都不存在,那麼root就賦值為空了,也正確。難點就在於處理左右子節點都存在的情況,我們需要在右子樹找到最小值,即右子樹中最左下方的節點,然後將該最小值賦值給root,然後再在右子樹中呼叫遞迴函式來刪除這個值最小的節點,參見程式碼如下:

解法一:

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (!root) return NULL;
        if (root->val > key) {
            root->left = deleteNode(root->left, key);
        } else if (root->val < key) {
            root->right = deleteNode(root->right, key);
        } else {
            if (!root->left || !root->right) {
                root = (root->left) ? root->left : root->right;
            } else {
                TreeNode *cur = root->right;
                while (cur->left) cur = cur->left;
                root->val = cur->val;
                root->right = deleteNode(root->right, cur->val);
            }
        }
        return root;
    }
};

下面我們來看迭代的寫法,還是通過BST的性質來快速定位要刪除的節點,如果沒找到直接返回空。遍歷的過程要記錄上一個位置的節點pre,如果pre不存在,說明要刪除的是根節點,如果要刪除的節點在pre的左子樹中,那麼pre的左子節點連上刪除後的節點,反之pre的右子節點連上刪除後的節點。在刪除函式中,如果左右子節點都不存在,那麼返回空;如果有一個不存在,那麼我們返回那個存在的;難點還是在於處理左右子節點都存在的情況,還是要找到需要刪除節點的右子樹中的最小值,然後把最小值賦值給要刪除節點,然後就是要處理最小值可能存在的右子樹的連線問題,如果要刪除節點的右子節點沒有左子節點了的話,那麼最小值的右子樹直接連到要刪除節點的右子節點上即可(因為此時原本要刪除的節點的值已經被最小值替換了,所以現在其實是要刪掉最小值節點)。否則我們就把最小值節點的右子樹連到其父節點的左子節點上。文字表述確實比較繞,請大家自行帶例子一步一步觀察就會很清晰明瞭了,參見程式碼如下:

解法二:

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        TreeNode *cur = root, *pre = NULL;
        while (cur) {
            if (cur->val == key) break;
            pre = cur;
            if (cur->val > key) cur = cur->left;
            else cur = cur->right;
        }
        if (!cur) return root;
        if (!pre) return del(cur);
        if (pre->left && pre->left->val == key) pre->left = del(cur);
        else pre->right = del(cur);
        return root;
    }
    TreeNode* del(TreeNode* node) {
        if (!node->left && !node->right) return NULL;
        if (!node->left || !node->right) {
            return (node->left) ? node->left : node->right;
        }
        TreeNode *pre = node, *cur = node->right;
        while (cur->left) {
            pre = cur;
            cur = cur->left;
        }
        node->val = cur->val;
        (pre == node ? node->right : pre->left) = cur->right;
        return node;
    }
};

下面來看一種對於二叉樹通用的解法,適用於所有二叉樹,所以並沒有利用BST的性質,而是遍歷了所有的節點,然後刪掉和key值相同的節點,參見程式碼如下:

解法三:

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (!root) return NULL;
        if (root->val == key) {
            if (!root->right) return root->left;
            else {
                TreeNode *cur = root->right;
                while (cur->left) cur = cur->left;
                swap(root->val, cur->val);
            }
        }
        root->left = deleteNode(root->left, key);
        root->right = deleteNode(root->right, key);
        return root;
    }
};

參考資料: