Kth Smallest Element in a BST
这题考察的就是 BST 的性质: in order = sorted.
另一种应对多次查询的好办法是 augmented binary tree; 第一次用 O(n) 的时间统计记录一下每个 node 左子树节点个数,后面的每次查询就可以 O(height) 的时间搜索了。
public class Solution {
public int kthSmallest(TreeNode root, int k) {
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()){
while(cur != null){
stack.push(cur);
cur = cur.left;
}
TreeNode node = stack.pop();
if(--k == 0) return node.val;
cur = node.right;
}
return root.val;
}
}
Follow up:
What if the BST is modified (insert/delete operations) often and you need to find the kth smallest frequently? How would you optimize the kthSmallest routine?
思路一, 对node计数, 这样找左边第K个或者右边第k-left-1个
public class Solution {
public int kthSmallest(TreeNode root, int k) {
int left = nodeCount(root.left); // this value can be saved in the root node
if(left + 1 == k) {
return root.val;
} else if (left + 1 < k) {
return kthSmallest(root.right, k - left - 1);
} else {
return kthSmallest(root.left, k);
}
}
private int nodeCount(TreeNode root) {
if(root == null) {
return 0;
}
return 1 + nodeCount(root.left) + nodeCount(root.right);
}
}
维护一个大小为 k 的 max heap,一直有 insert 的时候好办;有 delete 而且删掉的 node 又在 heap 里就只好找一下 in order successor 了。
PriorityQueue<Integer> queue = new PriorityQueue<>(k, Collections.reverseOrder());
Inorder Successor in BST
递归
public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
if(root == null) return root;
if(root.val<=p.val){
return inorderSuccessor(root.right, p);
}else{
TreeNode left = inorderSuccessor(root.left, p);
return left!=null? left : root;
}
迭代
public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
TreeNode succ = null;
while(root!=null){
if(root.val>p.val){
succ = root;
root = root.left;
}
else
root = root.right;
}
return succ;
}
对binary tree通用的方式, inorder traversal时引入变量isreachedP, 第一次碰到P node时设为true, isreachedP为true时, 返回下一个node
public class Solution {
public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode cur = root;
boolean reachedP = false;
while(cur != null || !stack.isEmpty()){
while(cur != null){
stack.push(cur);
cur = cur.left;
}
TreeNode node = stack.pop();
if(reachedP) return node;
if(node == p) reachedP = true;
cur = node.right;
}
return null;
}
}
Convert Sorted Array to Binary Search Tree
利用 BST inorder = sorted 的性质,一道很有意思的递归题。
在 BST 里多用区间的思想思考问题。
public class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return helper(nums, 0, nums.length - 1);
}
private TreeNode helper(int[] nums, int start, int end){
if(start > end) return null;
if(start == end) return new TreeNode(nums[start]);
int mid = start + (end - start) / 2;
TreeNode root = new TreeNode(nums[mid]);
root.left = helper(nums, start, mid - 1);
root.right = helper(nums, mid + 1, end);
return root;
}
}
Verify Preorder Sequence in Binary Search Tree
只给定一个 preorder 顺序是无法生成唯一 binary tree 的,也可以生成多个 valid BST.
所以这题的题意需要澄清下,正确意思是,给定一个 preorder sequence,只要这个 sequence 可以生成一个 valid BST,都返回 true.
这样我们的判断条件变成了这样,给定 array 如 5(123)(678),第一个元素一定为 root,后面的比 root 小的连续序列为左子树,比 root 大的连续序列为右子树。
左右子树可以为空,但是不能违反 BST 子树性质。所以如果 > root 的连续数组后面又出现了 < root 的元素,一定是 false.
这题的写法上有点像 quicksort,要注意 index.
为了省心起见,这类 subarray 的 divide & conquer 最好把 length <= 2 的直接返回了,不然会多出许多 corner case 要考虑。传递参数时, 只需要传递区间start和end就可以了.
- O(nlogn) time and O(1) space
递归
public class Solution {
public boolean verifyPreorder(int[] preorder) {
if(preorder==null || preorder.length==0) return true;
return isValid(preorder, 0, preorder.length-1);
}
public boolean isValid(int[] preorder, int start, int end){
if(end-start<=1) return true;
int breakpoint=start+1;
int root = preorder[start];
while(breakpoint<=end&&preorder[breakpoint]<root) breakpoint++;
for(int i=breakpoint;i<=end;i++){
if(preorder[i]<root) return false;
}
return isValid(preorder, start+1, breakpoint-1) && isValid(preorder, breakpoint, end);
}
}
迭代 (Stack)
用stack来维护一个单调的递减的序列, low维护下限, 如preorder序列是[10,5,4,3, 7, 11, 16, 14, 17], 当单调递减的[10,5,4,3]压入队列, low=minvalue; 当遇到7时, 弹出[5,4,3], 栈变为[10,7], low=7, 此时后面的元素必须大于low.
时间复杂度O(n), 空间复杂度O(n)
public boolean verifyPreorder(int[] preorder) {
Stack<Integer> stack = new Stack<Integer>();
int low = Integer.MIN_VALUE;
for(int i=0;i<preorder.length;i++){
if(preorder[i]<low) return false;
//if(!stack.isEmpty()) System.out.println(preorder[i] + " " + stack.peek());
while(!stack.isEmpty() && preorder[i]>stack.peek())
low = stack.pop();//用low来存上一次popout遍历的value,后面的数字不能小于它
stack.push(preorder[i]);
}
return true;
}
Recover Binary Search Tree
Java 里一切都是 pass by value,当传入的是 object reference 的时候,实际传入的就是 object 的内存地址。
因此,带泛型的各种数据结构,Stack, List 等,即使里面放的都是 TreeNode 并且进行了各种 add/remove/pop 操作,对这些 object 的修改也会反映到原来的 Tree 上。
Hard 题,因为得 O(1) space. 这意味着做个 in order 存在 array 里面再扫的做法行不通,连 stack 也不能用了。。
先假设下我们有 inorder array,如何找那对 swapped pair ?
在中间 1234567 -> 1264537
在两端 1234567 -> 7234561
右端 1234567 -> 1237564
左端 1234567 -> 5234167
从左往右找递增序列里面第一个变小的,prev 为 swapped;
从右往左找递减序列里面第一个变大的,prev 为 swapped.
找错误元素可以简化成一次遍历,第一次找到违章元素,prev 是 swapped; 然后继续扫,第二次看到违章元素,cur 是 swapped.
两种case: 1. 交换两个相邻的元素 2. 两个元素不相邻 本质就是一个inorder traversal
public void recoverTree(TreeNode root) {
Stack<TreeNode> stack= new Stack<TreeNode>();TreeNode cur =root; TreeNode pre =null;TreeNode first =null; TreeNode mid = null; TreeNode last = null;
while(cur!=null || !stack.isEmpty()){
while(cur!=null){
stack.push(cur);
cur=cur.left;
}
cur = stack.pop();
if(pre!=null && cur.val<pre.val){
if(first==null){
first = pre;
mid = cur;
}
else
last = cur;
}
pre = cur;
cur = cur.right;
}
//case1: The swapped nodes are not adjacent in the inorder traversal of the BST.
//For example, Nodes 5 and 25 are swapped in {3 5 7 8 10 15 20 25}.
//The inorder traversal of the given tree is 3 25 7 8 10 15 20 5
if(first!=null && last==null) swapval(first, mid);
//The swapped nodes are adjacent in the inorder traversal of BST.
//For example, Nodes 7 and 8 are swapped in {3 5 7 8 10 15 20 25}.
//The inorder traversal of the given tree is 3 5 8 7 10 15 20 25
else if(first!=null && last!=null) swapval(first, last);
}
public void swapval(TreeNode mid, TreeNode last) {
// TODO Auto-generated method stub
int tmp = mid.val;
mid.val=last.val;
last.val=tmp;
}
遍历和迭代更简洁的写法在论坛这个帖子里写的非常好。
找错误元素可以简化成一次遍历,第一次找到违章元素,prev 是 swapped; 然后继续扫,第二次看到违章元素,cur 是 swapped.
递归版:
public void recoverTree(TreeNode root) {
//use inorder traversal to detect incorrect node
inOrder(root);
int temp = first.val;
first.val = second.val;
second.val = temp;
}
TreeNode prev = null;
TreeNode first = null;
TreeNode second = null;
public void inOrder(TreeNode root){
if(root == null) return;
//search left tree
inOrder(root.left);
//in inorder traversal of BST, prev should always have smaller value than current value
if(prev != null && prev.val >= root.val){
//incorrect smaller node is always found as prev node
if(first == null) first = prev;
//incorrect larger node is always found as curr(root) node
second = root;
}
//update prev node
prev = root;
//search right tree
inOrder(root.right);
}
迭代版:
public class Solution {
public void recoverTree(TreeNode root) {
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode cur = root;
TreeNode prev = null;
TreeNode p = null;
TreeNode q = null;
while(cur != null || !stack.isEmpty()){
while(cur != null){
stack.push(cur);
cur = cur.left;
}
TreeNode node = stack.pop();
if(prev != null && node.val <= prev.val){
if(p == null) p = prev;
q = node;
}
/* 上面这里如果写成这样,会在 [0,1] 的 case 上挂掉
原因是只有两个 node 的情况下 q 还没来得及被赋值
就结束了,于是 q == null 会导致无法 swap.
拿掉那个 else 可以保证不管怎样 q 指向 cur node.
if(p == null){
p = prev;
} else {
q = node;
}
*/
prev = node;
cur = node.right;
}
swap(p, q);
}
private void swap(TreeNode p, TreeNode q){
if(p == null || q == null) return;
int temp = p.val;
p.val = q.val;
q.val = temp;
}
}
Find Mode in Binary Search Tree
BST inorder traversal, 注意while loop出来后还要再判断末尾元素, 代码有点丑, 打完泰拳累晕累晕的
然而这道题要求是O(1) space的,这种情况首先想到moris traversal. 同时不能存储所有碰到的元素, 否则是O(N) space的. 所以in order遍历的时候就要handle元素.
public class Solution {
int max = 0;
int curval;
int count = 0;
ArrayList<Integer> ans = new ArrayList<Integer>();
public int[] findMode(TreeNode root) {
dfs(root);
int[] modes = new int[ans.size()];
for(int i=0;i<modes.length;i++) modes[i] = ans.get(i);
return modes;
}
public void handle(int val){
if(val!=curval){
curval = val;
count =0;
}
count++;
if(count==max) ans.add(val);
else if(count>max){
max = count;
ans.clear();ans.add(val);
}
}
public void dfs(TreeNode root){
if(root==null)
return;
dfs(root.left);
handle(root.val);
dfs(root.right);
}
}
Moris Traversal
Count of Smaller Numbers After Self
非常好的一道题,用BST直接构造。每个node count存duplicate,leftCount存遇到过的数字。[3, 2, 2, 6, 1],
1(0, 1)
\
6(3, 1)
/
2(0, 2)
\
3(0, 1)
class TreeNode{
int val;
int count = 0;
int leftCount = 0;
int rightCount = 0;
TreeNode left;
TreeNode right;
public TreeNode(int val){
this.val = val;
count=1;
}
}
public class Solution {
public int insertNode(TreeNode root, int val){
int rtn = 0;
while(root!=null){
if(root.val>val){
root.leftCount++;
if(root.left==null){
TreeNode node = new TreeNode(val);
root.left = node;
return rtn;
}
root = root.left;
}
else if (root.val==val){
root.count++;
rtn += root.leftCount;
return rtn;
}
else{
rtn += root.count + root.leftCount;
if(root.right==null){
TreeNode node = new TreeNode(val);
root.right = node;
return rtn;
}
root = root.right;
}
}
return rtn;
}
public List<Integer> countSmaller(int[] nums) {
ArrayList<Integer> result = new ArrayList<Integer>();
if(nums.length<1) return result;
TreeNode root = new TreeNode(nums[nums.length-1]);
result.add(0);
for(int i=nums.length-2;i>=0;i--){
int rtn = insertNode(root, nums[i]);
result.add(0, rtn);
}
return result;
}
}
Count of Range Sum
这道题, 看似简单,O(n*n)的方法很好想到,然而O(nlogn)并不好想到。
这道题试图用segment tree写,写到最后search的时候发现不对劲,传统search会遗漏掉一些空间。本质因为传统search传入的是lowidx和highidx,所以query的时候可以有效剪枝。而这里是用lower bound和higher bound进行的剪枝。没法有效进行。
参考了论坛的segment tree解法,非常的不直观,需要对sum数组建立segment tree,再查询。查询的方式也很奇怪,花了时间研究后放弃这个仅有17票的解法。
merge sort的思路是这样的
首先建立sum[n+1]数组。如[1,2,3],sum数组为[0,1,3,6],sum[j]-sum[i]可求出range sum。
递归调用merge(start,mid)和merge(mid,end),此时(start,mid)是“单调有序”的sum数组,(mid,end)是“单调有序”的sum数组,并且(mid,end)的sum index 一定比(start,mid)的sum index大。最重要的性质。
求解用当前左半部分(start,mid)和右半部分(mid,end)构造 range sum, 有多少合法的解决。 这里又利用到了two pointers的思路。 对左半部分每个i小于mid进行枚举,在右半部分找到对应的j和k,满足sum[j]-sum[i]<=upper, lower<=sum[k]-sum[i], 因为右半部分单调,j>=k.
最后记得将左右半部分merge sort,供下次使用。
public class Solution {
public int countRangeSum(int[] nums, int lower, int upper) {
long[] sums = new long[nums.length+1];
for(int i=0;i<nums.length;i++){
sums[i+1]=sums[i]+nums[i];
}
int count = mergeCount(sums, 0, nums.length+1, lower, upper);
return count;
}
public int mergeCount(long[] sums, int start, int end, int lower, int upper){
if(end-start<=1) return 0;
int mid = start+(end-start)/2;
int count = mergeCount(sums, start, mid, lower, upper)+mergeCount(sums, mid, end, lower, upper);
long[] cache = new long[end-start]; int c = 0;
int j = mid; int k=mid; int i = start; int l = mid;
while(i<mid){
while(k<end&&sums[k]-sums[i]<lower) k++;
while(j<end&&sums[j]-sums[i]<=upper) j++;
while(l<end&&sums[l]<=sums[i]) cache[c++]=sums[l++];
count+=j-k;
cache[c] = sums[i];
i++;c++;
}
System.arraycopy(cache, 0, sums, start, l - start);
return count;
}
}
这道题我最喜欢的最简单最直接的方式是利用和 count of smaller numbers after itself的思路,对sum数组建立BST,然后对于每一个sum[j],查询有多少sum[i]在[sum[j]-upper,sum[j]-lower]的range里。 注意查询的时候先查询再插入。
public class Solution {
class TreeNode{
int count;
int leftCount;
long val;
TreeNode left;
TreeNode right;
public TreeNode(long val){
this.val = val;
count = 1;
leftCount = 0;
}
}
public void insert(TreeNode node, long val){
while(node!=null){
if(node.val==val){
node.count++;
return;
}
else if(node.val<val){
if(node.right!=null) node = node.right;
else {node.right = new TreeNode(val); return;}
}
else if(node.val>val){
node.leftCount++;
if(node.left!=null) node = node.left;
else {node.left = new TreeNode(val);return;}
}
}
}
public int getBound(TreeNode node, long val, boolean include){
int count = 0;
while(node!=null){
if(node.val==val){
count+=node.leftCount;
count+=include?node.count:0;
return count;
}
else if(val>node.val){
count+=node.count+node.leftCount;
node = node.right;
}
else{
node = node.left;
}
}
return count;
}
public int countRangeSum(int[] nums, int lower, int upper) {
TreeNode root = new TreeNode(0); long sum = 0; int count=0;
for(int i=0;i<nums.length;i++){
sum+=nums[i];
count += getBound(root,sum-lower, true)-getBound(root, sum-upper, false);
// System.out.println("i" + i + "count: "+ count);
insert(root, sum);
//System.out.println("root");
}
return count;
}
}
Reverse Pairs
一个套路的,一样照葫芦画瓢撸了一个BST,注意2*nums[i]再转long会溢出,过了37/130test cases后TLE。复杂度是O(nlogn)没毛病。可以看出以上三个问题的抽象表述,如果要找一个数组之间的pair,count关系,利用BST可以有效进行搜索。
BST solution is no longer acceptable, because it's performance can be very bad, O(n^2) actually, for extreme cases like [1,2,3,4......49999], due to the its unbalance, but I am still providing it below just FYI.
注意worst case可以变成o(n^2).
public class Solution {
class TreeNode{
long val;
int count;
int rightCount;
TreeNode left;
TreeNode right;
public TreeNode(long val){
this.val = val;
this.count = 1;
this.rightCount = 0;
}
}
public void insert(TreeNode node, long val){
while(node!=null){
if(node.val==val){
node.count++;
return;
}
else if(node.val>val){
if(node.left!=null) {node = node.left;}
else{
node.left = new TreeNode(val);return;
}
}
else{
node.rightCount++;
if(node.right!=null) {node = node.right;}
else{
node.right = new TreeNode(val);return;
}
}
}
}
public int search(TreeNode node, long val){
int count =0; val*=2;
while(node!=null){
if(node.val==val){
return count+node.rightCount;
}
else if(node.val>val){
count+=node.count+node.rightCount;
node = node.left;
}
else{
node = node.right;
}
}
return count;
}
public int reversePairs(int[] nums) {
TreeNode root = new TreeNode(0); root.count = 0;
int count = 0;
for(int i=0;i<nums.length;i++){
count +=search(root, nums[i]);
insert(root, nums[i]);
}
return count;
}
}
再撸了一个mergesort+twopointer版本,注意mid细节的处理,左半部分是(start,mid)右半部分是(mid+1,end)为了省事,mergesort那部分用arraysort了。
public class Solution {
public int reversePairs(int[] nums) {
int count = mergeCount(nums, 0, nums.length-1);
return count;
}
public int mergeCount(int[] nums, int start, int end){
if(start>=end) return 0;
int mid = start + (end-start)/2;
int count = 0;
count = mergeCount(nums, start, mid)+mergeCount(nums, mid+1, end);
int j = mid+1;
for(int i=start;i<=mid;i++){
while(j<=end&&nums[i]/2.0>nums[j]) j++;
count+=j-(mid+1);
}
Arrays.sort(nums, start, end+1);
return count;
}
}
K Inverse Pairs Array
这道Inverse Pairs 换了一种描述方式后,就成了一道dp的题。dp[n,k]代表n个数字里可以形成k个pairs,那么我们可以得到一种递推关系,dp[n,k] = dp[n-1, k] + dp[n-1, k-1] +....+ dp[n-1, max(0, k-(n-1))]. 举个例子,dp[3,2] 可以由
dp [2,2] = 0
dp[2][2]=1 [2,1]==>[2,1,3]
dp[2,0] = 1 [1,2] ==>[3,1,2].
dp[n,k]总可以让最后加入的字符n插入到之前的某一位置,这样会在之前的基础上增加x个reverse pairs,x最多为n-1个pair。所以我们最多可以递归到dp[n-1, k-(n-1)]这个位置。当k很小时,k-(n-1)<0, 所以取max(0, k-(n-1)). 这题注意要取mod。long保存dp[][]数组。
public class Solution {
public int kInversePairs(int n, int k) {
int mod = 1000000007;
long [][] dp =new long[n+1][k+1];
for(int i=1;i<=n;i++){
dp[i][0] = 1;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++){
int t = j; int lower = Math.max(0, j-(i-1));
while(t>=lower){
dp[i][j] += dp[i-1][t];
t--;
}
dp[i][j] = (dp[i][j]+mod) % mod;
}
// for(int i=0;i<=n;i++)
// System.out.println(Arrays.toString(dp[i]));
return (int)dp[n][k];
}
}
dp[n][k] = dp[n-1][k]+dp[n-1][k-1]+dp[n-1][k-2]+...+dp[n-1][k+1-n+1]+dp[n-1][k-n+1]
dp[n][k+1] = dp[n-1][k+1]+dp[n-1][k]+dp[n-1][k-1]+dp[n-1][k-2]+...+dp[n-1][k+1-n+1]
从上两行可推出 dp[n][k+1] = dp[n][k]+dp[n-1][k+1]-dp[n-1][k+1-n]
dp[n][k] = dp[n][k-1]+dp[n-1][k] - dp[n-1][k-n]
优化time complexity
Minimum Absolute Difference in BST
撸了最简单迭代in order traversal。太久没看有点忘,还好底子好,捡起来很快。
public class Solution {
public int getMinimumDifference(TreeNode root) {
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode cur = root;
int prev = -1; int diff = Integer.MAX_VALUE;
while(cur!=null||!stack.isEmpty()){
while(cur!=null){
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
if(prev!=-1)
diff = Math.min(diff, cur.val-prev);
prev = cur.val;
cur=cur.right;
}
return diff;
}
}
递归版本,O(1) space complexity.
public class Solution {
int min = Integer.MAX_VALUE;
Integer prev = null;
public int getMinimumDifference(TreeNode root) {
if (root == null) return min;
getMinimumDifference(root.left);
if (prev != null) {
min = Math.min(min, root.val - prev);
}
prev = root.val;
getMinimumDifference(root.right);
return min;
}
}
follow up: 如果树不是BST呢?这个时候我们可以利用一个TreeSet来maintain order。
public class Solution {
TreeSet<Integer> set = new TreeSet<>();
int min = Integer.MAX_VALUE;
public int getMinimumDifference(TreeNode root) {
if (root == null) return min;
if (!set.isEmpty()) {
if (set.floor(root.val) != null) {
min = Math.min(min, root.val - set.floor(root.val));
}
if (set.ceiling(root.val) != null) {
min = Math.min(min, set.ceiling(root.val) - root.val);
}
}
set.add(root.val);
getMinimumDifference(root.left);
getMinimumDifference(root.right);
return min;
}
}