19. Predecessor and Successor

The problem can be found at the following link: ๐Ÿ”— Question Link

๐Ÿงฉ Problem Description

Given the root of a Binary Search Tree (BST) and an integer key, find the inorder predecessor and successor of the key in the BST.

  • The predecessor is the largest value in the BST smaller than the key.

  • The successor is the smallest value in the BST larger than the key.

  • If predecessor or successor does not exist, return NULL.

Example 1:

Input: root = [8, 1, 9, N, 4, N, 10, 3, N, N, N], key = 8

Output: 4 9

Explanation: The inorder predecessor of 8 is 4, and the inorder successor is 9.

Example 2:

Input: root = [10, 2, 11, 1, 5, N, N, N, N, 3, 6, N, 4, N, N], key = 11

Output: 10 -1

Explanation: The inorder predecessor of 11 is 10, and successor does not exist.

Example 3:

Input: root = [2, 1, 3], key = 3

Output: 2 -1

Explanation: The inorder predecessor of 3 is 2, and successor does not exist.

๐Ÿ”’ Constraints

  • $1 \leq \text{Number of Nodes} \leq 10^4$

  • $1 <= node->data <= 10^6$

  • $1 \leq \text{Key Value} \leq 10^5$

โœ… My Approach

Iterative BST Search Using BST Properties

The BST property allows us to find predecessor and successor without full traversal:

  • Predecessor: The last node encountered with value less than key during a search down the tree.

  • Successor: The last node encountered with value greater than key during a search down the tree.

Algorithm Steps:

  1. Initialize two pointers: pre and suc to nullptr.

  2. To find predecessor:

    • Start from root.

    • Move right if current node's value < key, update pre to current node.

    • Else move left.

  3. To find successor:

    • Start from root.

    • Move left if current node's value > key, update suc to current node.

    • Else move right.

  4. Return the pair {pre, suc}.

This approach leverages BST ordering and runs in time proportional to the height of the tree, typically O(log N) for balanced BSTs.

๐Ÿงฎ Time and Auxiliary Space Complexity

  • Expected Time Complexity: O(h), where h is the height of the BST, as each search for predecessor and successor traverses at most from root to leaf.

  • Expected Auxiliary Space Complexity: O(1), as we use only a fixed number of pointers and no extra data structures.

๐Ÿง  Code (C++)

class Solution {
  public:
    vector<Node*> findPreSuc(Node* root, int key) {
        Node *pre = nullptr, *suc = nullptr;
        Node* curr = root;
        while (curr) {
            if (curr->data < key) {
                pre = curr;
                curr = curr->right;
            } else curr = curr->left;
        }
        curr = root;
        while (curr) {
            if (curr->data > key) {
                suc = curr;
                curr = curr->left;
            } else curr = curr->right;
        }
        return {pre, suc};
    }
};
โšก Alternative Approaches

๐Ÿ“Š 2๏ธโƒฃ Recursive Traversal Method

Algorithm Steps:

  1. Initialize pre and suc to nullptr.

  2. Recursively traverse the BST:

    • If node->data == key:

      • Go to the rightmost node in left subtree for predecessor.

      • Go to the leftmost node in right subtree for successor.

    • If node->data < key: update pre and move right.

    • If node->data > key: update suc and move left.

class Solution {
  void find(Node* root, Node*& pre, Node*& suc, int key) {
    if (!root) return;
    if (root->data == key) {
        Node* l = root->left;
        while (l) pre = l, l = l->right;
        Node* r = root->right;
        while (r) suc = r, r = r->left;
    } else if (root->data > key) {
        suc = root;
        find(root->left, pre, suc, key);
    } else {
        pre = root;
        find(root->right, pre, suc, key);
    }
  }

  public:
    vector<Node*> findPreSuc(Node* root, int key) {
        Node *pre = nullptr, *suc = nullptr;
        find(root, pre, suc, key);
        return {pre, suc};
    }
};

โœ… Why This Approach?

  • Easy to implement and reason about.

  • Handles edge cases (node with given key exists) clearly.

๐Ÿ“ Complexity Analysis:

  • Time: ๐Ÿ”ธ O(h)

  • Auxiliary Space: ๐Ÿ”ธ O(h) (recursion stack)

๐Ÿ“Š 3๏ธโƒฃ Inorder Traversal + Linear Scan

Algorithm Steps:

  1. Perform inorder traversal and store nodes in a vector.

  2. Scan through vector:

    • Last node with value < key โ†’ predecessor.

    • First node with value > key โ†’ successor.

class Solution {
  void inorder(Node* root, vector<Node*>& nodes) {
    if (!root) return;
    inorder(root->left, nodes);
    nodes.push_back(root);
    inorder(root->right, nodes);
  }

  public:
    vector<Node*> findPreSuc(Node* root, int key) {
        vector<Node*> nodes;
        inorder(root, nodes);
        Node *pre = nullptr, *suc = nullptr;
        for (Node* node : nodes) {
            if (node->data < key) pre = node;
            else if (node->data > key) {
                suc = node;
                break;
            }
        }
        return {pre, suc};
    }
};

โœ… Why This Approach?

  • Very intuitive.

  • Allows easy post-processing or reuse of full traversal data.

๐Ÿ“ Complexity Analysis:

  • Time: ๐Ÿ”ธ O(N)

  • Auxiliary Space: ๐Ÿ”ธ O(N)

๐Ÿ“Š 4๏ธโƒฃ Morris Inorder Traversal (O(1) Space)

Algorithm Steps:

  1. Use Morris traversal to visit nodes in-order without recursion or stack.

  2. While visiting:

    • Keep track of last node < key as predecessor.

    • First node > key becomes successor.

class Solution {
  public:
    vector<Node*> findPreSuc(Node* root, int key) {
        Node *pre = nullptr, *suc = nullptr, *curr = root;

        while (curr) {
            if (!curr->left) {
                if (curr->data < key) pre = curr;
                else if (curr->data > key && !suc) suc = curr;
                curr = curr->right;
            } else {
                Node* pred = curr->left;
                while (pred->right && pred->right != curr)
                    pred = pred->right;

                if (!pred->right) {
                    pred->right = curr;
                    curr = curr->left;
                } else {
                    pred->right = nullptr;
                    if (curr->data < key) pre = curr;
                    else if (curr->data > key && !suc) suc = curr;
                    curr = curr->right;
                }
            }
        }

        return {pre, suc};
    }
};

โœ… Why This Approach?

  • Best when minimizing auxiliary space is required.

  • No stack or recursionโ€”constant space.

๐Ÿ“ Complexity Analysis:

  • Time: ๐Ÿ”ธ O(N)

  • Auxiliary Space: ๐ŸŸข O(1)

๐Ÿ†š Comparison

Approach

โฑ๏ธ Time

๐Ÿ—‚๏ธ Space

โœ… Pros

โš ๏ธ Cons

Iterative BST Walk

๐ŸŸข O(h)

๐ŸŸข O(1)

Fastest and clean

Doesnโ€™t handle exact-key node logic

Recursive Traversal

๐ŸŸข O(h)

๐Ÿ”ธ O(h)

Handles key match subtree logic

Extra space from recursion

Inorder + Linear Scan

๐Ÿ”ธ O(N)

๐Ÿ”ธ O(N)

Simple to code

Inefficient for large balanced trees

Morris Traversal

๐Ÿ”ธ O(N)

๐ŸŸข O(1)

True constant-space

Trickier to implement

โœ… Best Choice by Scenario

Scenario

Recommended Approach

๐Ÿš€ Time-efficient & minimal memory

๐Ÿฅ‡ Iterative BST Walk

๐ŸŽฏ Need to handle exact key logic

๐Ÿฅˆ Recursive Traversal

๐Ÿ“Š Want to reuse full inorder list

๐Ÿฅ‰ Inorder + Linear Scan

๐Ÿ’ก Memory-constrained environment

๐Ÿ… Morris Traversal

๐Ÿง‘โ€๐Ÿ’ป Code (Java)

class Solution {
    public ArrayList<Node> findPreSuc(Node root, int key) {
        Node pre = null, suc = null, curr = root;
        while (curr != null) {
            if (curr.data < key) {
                pre = curr;
                curr = curr.right;
            } else {
                curr = curr.left;
            }
        }
        curr = root;
        while (curr != null) {
            if (curr.data > key) {
                suc = curr;
                curr = curr.left;
            } else {
                curr = curr.right;
            }
        }
        ArrayList<Node> res = new ArrayList<>();
        res.add(pre);
        res.add(suc);
        return res;
    }
}

๐Ÿ Code (Python)

class Solution:
    def findPreSuc(self, root, key):
        pre = suc = None
        curr = root
        while curr:
            if curr.data < key:
                pre = curr
                curr = curr.right
            else:
                curr = curr.left
        curr = root
        while curr:
            if curr.data > key:
                suc = curr
                curr = curr.left
            else:
                curr = curr.right
        return [pre, suc]

๐Ÿง  Contribution and Support

For discussions, questions, or doubts related to this solution, feel free to connect on LinkedIn: ๐Ÿ“ฌ Any Questions?. Letโ€™s make this learning journey more collaborative!

โญ If you find this helpful, please give this repository a star! โญ


๐Ÿ“Visitor Count

Last updated