Leetcode 255. Verify Preorder Sequence in Binary Search Tree

Problem Explanation

A preorder sequence of a binary search tree (BST) is one where each node is visited before it's children. So, if we have a BST and perform a preorder traversal operation on it, we would visit the nodes in the order: root, left, right.

Therefore, in the problem, we are given an array of integers, which can be a sequence of nodes when visited using preorder traversal. We need to verify whether this given sequence is a correct preorder traversal sequence of a BST.

A binary search tree has the property that, the value of each node is greater than the values of all the nodes present in its left subtree and is less than the values of all nodes present in its right subtree.

For example, consider the following binary tree:

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

The correct preorder sequence for this binary tree is [5, 2, 1, 3, 6] because we first visit the root node (5), then we visit the left child (2), then the left child of 2 (1), then the right child of 2 (3), and finally, we visit the right child of 5 (6).

There are many sequences of the same BST, one of which is [5, 6, 2, 1, 3]. This sequence does not adhere to the rules of BSTs and so it would return false.

Solution Approach

The solution involves using a depth-first search (DFS) to traverse through the preorder sequence. It uses the concept of min and max to keep track of the range of values that a node should have in order for it to be a part of a valid BST.

We initially start with min as INT_MIN and max as INT_MAX as our initial node (root) can take any value. We then iterate through the preorder array. If at any point, we find a value that is not within the min and max range, we return because such a node cannot be a part of a valid BST.

Whenever we transition from the left subtree to the right subtree, we make sure that the nodes of the right subtree are greater than the root (reassigning min from INT_MIN to the value of the root), maintaining the property of the BST.

Every time, we successfully identify a node (which lies within min and max). We call DFS recursively for left and right subtrees, updating the min and max values.

Python Solution

3class Solution:
4    def verifyPreorder(self, preorder: List[int]) -> bool:
5        self.i = 0
6        def dfs(minval, maxval):
7            if self.i == len(preorder):
8                return True
9            if not minval < preorder[self.i] < maxval:
10                return False
11            val = preorder[self.i]
12            self.i += 1
13            return dfs(minval, val) and dfs(val, maxval)
15        return dfs(float('-inf'), float('inf'))

Java Solution

3class Solution {
4    int i = 0;
5    public boolean verifyPreorder(int[] preorder) {
6        return dfs(preorder, Long.MIN_VALUE, Long.MAX_VALUE);
7    }
9    private boolean dfs(int[] preorder, long minval, long maxval) {
10        if (i == preorder.length)
11            return true;
12        if (preorder[i] <= minval || preorder[i] >= maxval)
13            return false;
14        int val = preorder[i++];
15        return dfs(preorder, minval, val) && dfs(preorder, val, maxval);
16    }

Javascript Solution

3class Solution {
4    verifyPreorder(preorder) {
5        this.i = 0;
6        return this.dfs(preorder, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
7    }
9    dfs(preorder, minval, maxval) {
10        if (this.i === preorder.length)
11            return true;
12        if (preorder[this.i] <= minval || preorder[this.i] >= maxval)
13            return false;
14        let val = preorder[this.i++];
15        return this.dfs(preorder, minval, val) && this.dfs(preorder, val, maxval);
16    }

C++ Solution

3class Solution {
5    bool verifyPreorder(vector<int>& preorder) {
6        int i = 0;
7        return dfs(preorder, i, INT_MIN, INT_MAX);
8    }
11    bool dfs(vector<int>& preorder, int& i, int min, int max) {
12        if (i == preorder.size())
13            return true;
14        if (preorder[i] < min || preorder[i] > max)
15            return false;
17        int val = preorder[i++];
18        return dfs(preorder, i, min, val) && dfs(preorder, i, val, max);
19    }

C# Solution

3public class Solution {
4    int i = 0;
5    public bool VerifyPreorder(int[] preorder) {
6        return dfs(preorder, long.MinValue, long.MaxValue);
7    }
9    private bool dfs(int[] preorder, long min, long max) {
10        if (i == preorder.Length)
11            return true;
12        if (preorder[i] <= min || preorder[i] >= max)
13            return false;
14        int val = preorder[i++];
15        return dfs(preorder, min, val) && dfs(preorder, val, max);
16    }

These solutions manage to solve the problem with an O(n) time complexity. This is because we are visiting each node exactly once to decide whether it falls within the specified range.

Although we are using the Depth First Search approach here, we don't make use of any auxiliary space, hence the space complexity is O(1). As we go deep into one path before backtracking, recursion doesn't stack up much, so it can be considered constant space.

This solution exploits the properties of Binary Search Tree and Preorder Traversal, where in a valid BST, nodes have to follow this rule: for each node, all nodes in the left subtree must be smaller and all nodes in the right subtree must be greater. Also in Preorder Traversal, the node order is Root, Left and Right, so we can go in this order and check the rules using dfs function until the last node to determine whether the given sequence is a valid BST preorder sequence.

It's important to note that, these solutions work well for distinct values. In case of duplicates, some modifications might need to be done. This can be handled by maintaining a separate stack to hold the nodes as we traverse in a depth-first manner, or using a dictionary to store already visited nodes.

In conclusion, verifying if a given array represents the Preorder Traversal of Binary Search Tree might seem complex, but with the correct understanding of BST properties and good handling of the DFS method, it becomes a less arduous task. Implementing these solutions help in enhancing one's understanding of BSTs and traversal strategies. For robustness, handling edge cases (like empty arrays or single node cases) should be considered while implementing these solutions.

Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.

TA 👨‍🏫