我正在编写代码,通过先序遍历检查它是否是有效的二叉搜索树。例如,先序遍历1,2,4,3,5
是有效的BST,而1,3,4,2
不是。
我从先序遍历序列构建整个树,然后检查该树是否是有效的BST。这是一个时间和空间复杂度均为O(N)
的解决方案。
有人有比这更好的方法吗?我有一种直觉,认为我们可以在O(1)的额外空间内完成。
我正在编写代码,通过先序遍历检查它是否是有效的二叉搜索树。例如,先序遍历1,2,4,3,5
是有效的BST,而1,3,4,2
不是。
我从先序遍历序列构建整个树,然后检查该树是否是有效的BST。这是一个时间和空间复杂度均为O(N)
的解决方案。
有人有比这更好的方法吗?我有一种直觉,认为我们可以在O(1)的额外空间内完成。
检查1..n的排列是否是一个有效BST的前序遍历(如果存在BST,那么它是唯一的;imreal的答案不是反例,因为第二个重构没有排序)等价于测试该排列是否可以通过堆栈排序,即避免231模式。我似乎找不到任何线性时间常量空间算法在这些名称下,这可能表明鉴于此问题所吸引的关注量,没有人知道一个常量额外空间解决方案。
如果您愿意假设可读/写的输入可以被破坏,则存在一种使用恒定额外空间的线性时间算法。没必要单独重构树然后测试它。以下是相关的Python代码。
def isbstpreorder(iterable):
lowerbound = None
stack = []
for x in iterable:
if lowerbound is not None and x < lowerbound: return False
while stack and stack[-1] < x: lowerbound = stack.pop()
stack.append(x)
return True
为了消除堆栈的单独存储,将其存储在输入列表的前缀中。
这个想法是检查输入数组是否可以分成两个子数组,代表左子树和右子树。显然,这必须递归地完成。
以下是一些经过测试的Java代码,以说明相同的内容:
import java.util.ArrayList;
import java.util.List;
public class PreOrderBSTCheck {
/**
* @param args
*/
public static void main(String[] args) {
String ipStr = "1 4 2 3";
String[] ipArr = ipStr.split(" ");
int[] intArr = new int[ipArr.length];
List<Integer> listArr = new ArrayList<Integer>();
for(int i = 0 ; i < ipArr.length ; i++)
{
intArr[i] = Integer.parseInt(ipArr[i]);
listArr.add(intArr[i]);
}
//System.out.println("List size: "+listArr.size());
if(checkTree(listArr))
System.out.println("Valid");
else
System.out.println("Invalid");
}
public static boolean checkTree(List<Integer> arr)
{
if(arr.size() == 1)
{
return true;
}
List<Integer> left = new ArrayList<Integer>();
List<Integer> right = new ArrayList<Integer>();
Integer root = arr.get(0);
int idx = 1;
//adding left subtree nodes
while(arr.get(idx) < root)
{
left.add(arr.get(idx++));
if(idx >= arr.size())
break;
}
//adding right subtree nodes
if(! (idx >= arr.size()))
while(arr.get(idx) > root)
{
right.add(arr.get(idx++));
if(idx >= arr.size())
break;
}
if(left.size() + right.size() == arr.size() - 1)
{
if(left.size()==0)
{
return true && checkTree(right);
}
else if(right.size() == 0)
{
return true && checkTree(left);
}
else
{
return checkTree(left) && checkTree(right);
}
}
else
{
return false;
}
}
}
isBST(vector<int> &A) {
int a=0,b=1,c=2;
if(A.size() <= 2)
return 1;
while(c < A.size())
{
if(A[a] < A[b] && A[b] > A[c] && A[c] < A[a])
return 0;
a++; b++; c++;
}
return 1;
}
8
7 9 output preorder 8 7 9
7
8
9 output preorder 7 8 9
7
9
8 output preorder 7 9 8
9
7
8 output preorder 9 7 8
9
8
7 out put preorder 9 8 7
int lower=-1;
for(long int i=0;i<size;i++)
{
if(lower>-1 && pre[i] < lower)
{
lower=-2;
break;
}
while(!isempty(s) && top(s)<pre[i])
{
lower =top(s);
pop(s);
}
push(s,pre[i]);
}
if(lower==-2)
{
cout<<"Invalid"<<endl;
}
else
{
cout<<"Valid BST"<<endl;
}
这个算法适用于任何类型的排序输入,而不仅仅是1到n。它只需要O(1)的额外空间,即只有变量“lower”。如果您尝试使用笔和纸解决此问题,您将不得不跟踪一个变量,即用于检查任何新出现的值都不应小于该值的变量。Lower充当该标记,以便您检查没有新值低于'lower'。并且一旦您获得更高的值,就会立即更新'lower'。
在这里,我们可以考虑的第一件事是对于前序遍历数组,如果在数组右侧的任何点上找到比当前元素更大的元素,并且在此之后的任何元素都是较小的元素,则应该考虑从给定的前序遍历中不可能构建BST。
例如:arr[] = {40,30,35,20,80,100} 无法构建有效的BST。
解释:
在这里,当我们开始构建树时,40成为BST的根,现在随着我们的继续,30进入左子树,现在我们找到了30的下一个更大的数,即35,因为我们知道任何低于或小于30的元素都必须在30和其下一个更大的元素之间,即35。因此,当我们向前遍历时,我们发现20不能适合30的左侧,因为它在下一个更大的元素之后,肯定不是35的子节点,因为它将违反BST属性(即任何右子树元素始终比根具有更大的值)。因此,无法构建有效的BST。
现在,为了解决这个问题,我们可以使用栈,就像我们在数组中查找下一个更大的元素一样下一个更大元素的代码。在这里,我们只需要确保在找到下一个更大的元素后,没有比它更小的元素出现。
在代码中,我们最初将根节点设置为INT_MIN,即可能的最低值, 如果根节点任何时候都比当前元素低,我们返回false。 当当前元素大于栈顶元素时,我们弹出栈顶元素并将根节点设置为弹出的元素。 然后我们将当前元素推入栈中进行进一步比较。
bool check(int *arr, int n)
{
stack<int> s;
int root=INT_MIN;
for(int i=0;i<n;++i)
{
if(arr[i]<root)
return false;
while(!s.empty() && arr[i]>s.top())
{
root=s.top();
s.pop();
}
s.push(arr[i]);
}
return true;
}
public static bool IsBST(List<int> array)
{
var s = new Stack<int>();
int root = 0;
foreach(var el in array)
{
if(el < root)
return false;
while(s.Count != 0 && el > s.Peek())
{
root = s.Peek();
s.Pop();
}
s.Push(el);
}
return true;
}
不使用额外的空间或容器(如stack
):
将输入数组分成这种形式[root] - [left-subtree] - [right-subtree]
,然后递归验证每个子树。
public boolean isValidPreorder(int[] A) {
return util(A, 0, A.length-1);
}
public boolean isValidPreorderUtil(int[] A, int x, int y){
if (x >= y) return true;
for (int i = x+1; i <= y; i++){
if (A[x] < A[i]) {
for (int k = i+1; k <=y; k++){
if (A[k] < A[x]) return 0;
}
return util(A, x+1, i-1) & util(A, i, y); // both left and right subtree
}
}
return util(A, x+1, y); // left subtree only
}
int Solution::solve(vector<int> &A) {
set<int> s;
for(auto&I: A)
s.insert(I);
if(s.size() != A.size())
return 0;
int minVal = INT_MIN;
for(int i = 0; i < A.size(); i++){
if(A[i] <= minVal)
return 0;
if(i > 0 && A[i] > A[i-1])
minVal = A[i-1];
}
return 1;
}
这可以在O(n)时间和O(n)辅助空间复杂度(递归栈)内完成。整个树的根节点将位于前序数组的索引0处。在前序遍历中,每个节点后面都是其左子树元素的集合,然后是右子树元素的集合。对于每个节点,右子树将以该节点的下一个更高的数组值为根。因此,基本思想是递归地将前序数组划分为具有节点值有效范围的左右子树。
public class IsBstPossibleFromPreorder {
public static void main(String args[]){
int[] preorder = {40, 30, 35, 20, 80};//{6,3,0,5,4,8,7,9};//{3,2,5,1,7};
System.out.println(isBstPossible(preorder, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, preorder.length-1));
}
/*
li is root of current subtree
ri is the index of the last element in the current subtree
min, max range for the elements of current subtree
*/
private static boolean isBstPossible(int[] preorder, int min, int max, int li, int ri){
if (li==ri && preorder[li]>min && preorder[li]<max){ // if single node subtree...
return true;
}
if (preorder[li]<=min || preorder[li]>=max ){ // if node value out of range
return false;
}
int lsi = preorder[li+1]<preorder[li]?li+1:-1; // left subtree root index (-1 if no left subtree exits for node li)
int rsi = findNextHigherElementIndex(preorder, li, ri); // right subtree root index (-1 if no right subtree exists for node li)
boolean isLeftValid = (lsi==-1 || isBstPossible(preorder, min, preorder[li], lsi, rsi-1>lsi?rsi-1:lsi));
boolean isRightValid = (rsi==-1 || isBstPossible(preorder, preorder[li], max, rsi, ri));
return isLeftValid && isRightValid;
}
private static int findNextHigherElementIndex(int[] preorder, int li, int ri){
int x = -1; // -1 if no higher element on right side of li
for (int i = li+1; i <= ri ; i++) {
if (preorder[i]>preorder[li]){
x = i;
break;
}
}
return x;
}
}
仅凭先序遍历无法唯一构建二叉树,因此无法验证它是否为BST。
例如,4、2、3、6、5可以构成:
4
/ \
2 6
\ /
3 5
这是一棵二叉搜索树,同时也是:
4
\
2
\
3
\
6
\
5
这不是。