题目21;定义一个具有min函数(返回栈内最小值)的栈数据结构。
首先这个栈要具有普通栈所具有的push()和pop()方法,那么内部一定包含一个Stack。至于还要能实现min函数,而且还是在O(1)时间复杂度内,我们不得不考虑用额外的空间。
如果直接使用一个int变量存储当前的最小值,我们的确可以获得最小值,但是当栈pop()了以后,我们无法获得次小值。我们需要一个数据结构来动态保存每个时刻的最小值,每当push()和pop()的时候,同样更新这个数据结构,而且时间复杂度也是O(1),那么看来需要额外O(n)的空间了。可以选择栈或者一个链表(实质上当作栈使用)。我们使用一个辅助栈来实现,该栈和主栈的大小相同,栈顶存放的是当前栈内的最小值。
书中方法:
public class StackContainingMinFunc{
Stack<Integer> origin = new Stack<Integer>();
Stack<Integer> sup = new Stack<Integer>();
public void push(int item){
origin.push(item);
if(sup.isEmpty()){
sup.push(item);
}else sup.push(sup.peek()<item ? sup.peek() : item);
}
public int pop(){
assert(origin.size()>0 && sup.size()>0);
sup.pop();
return origin.pop();
}
public int min(){
assert(origin.size()>0 && sup.size()>0);
return sup.peek();
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
StackContainingMinFunc s = new StackContainingMinFunc();
s.push(5);
System.out.println(s.min());
s.push(4);
System.out.println(s.min());
s.push(3);
System.out.println(s.min());
s.push(6);
System.out.println(s.min());
s.pop();
System.out.println(s.min());
s.pop();
System.out.println(s.min());
s.pop();
System.out.println(s.min());
}
}
题目22:栈的压入、弹出序列。
输入两个整数序列,第一个序列是一串数字的压入顺序,判断第二个序列是否是该栈数字的弹出顺序。
我的方法:压入序列是给定的,每一次弹出操作形成一个弹出序列的值,我们从前往后遍历弹出序列,每一次访问弹出序列都代表了一次弹出操作,弹出的值必然是当前栈顶的元素或者是在剩余没有压入的元素序列中。我们要做的是去检查当前弹出的这个元素是否合理,即是否在栈顶或者剩下未压入栈的元素中。
public boolean check(int[] inSerial, int[] outSerial){
int N = inSerial.length;
//存储已经压入未被弹出的值
Stack<Integer> left = new Stack<Integer>();
int toPush = 0;
int toPop = 0;
//如果还有未被弹出的值或者还有没有被压入的元素,则继续检查弹出序列
while(!left.isEmpty() || toPush < N){
//如果当前弹出元素 并不在栈中,也不是下一个压入元素,则继续检查后一个压入元素。
if(toPush < N && toPop < N && inSerial[toPush] != outSerial[toPop]
&& (left.isEmpty() || left.peek() != outSerial[toPop])){
left.push(inSerial[toPush]);
toPush++;
}
//如果当前弹出元素在栈顶,弹出这个元素,并继续检查下一个弹出元素。
else if(!left.isEmpty() && left.peek() == outSerial[toPop]){
left.pop();
toPop++;
}
//如果当前弹出元素是下一个压入元素,则继续检查下一个弹出元素,同时也将压入元素指针后移。
else if(toPush < N && inSerial[toPush] == outSerial[toPop]){
toPush++;
toPop++;
}
//如果出现 当前弹出元素既不在栈顶,也不在压入元素序列内,则表示这个弹出序列是错误的。
if (toPush == N && !left.isEmpty() && left.peek() != outSerial[toPop]) {
return false;
}
}
return true;
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
CheckInAndOutSerialForStack c = new CheckInAndOutSerialForStack();
int[] a = new int[]{1,2,3,4,5};
int[] b = new int[]{4,3,2,1,5};
System.out.print(c.check(a, b));
}
题目23:二叉树层序遍历
注意层序遍历的时候对每一层的处理方式可能不同,这里把每一层的元素保存进一个List中了,那么就需要记录每一层的数量。
public List<List<Integer>> search(TreeNode root){
List<List<Integer>> result = new ArrayList<List<Integer>>();
Queue<TreeNode> queue = new LinkedList<TreeNode>();
if (root == null) {
return result;
}
queue.offer(root);
while(!queue.isEmpty()){
List<Integer> row = new ArrayList<Integer>();
int size = queue.size();
for(int i=0; i<size; i++){
TreeNode now = queue.poll();
row.add(now.val);
if(now.left != null)queue.offer(now.left);
if(now.right != null)queue.offer(now.right);
}
result.add(row);
}
return result;
}
题目24:判断一个序列是否为BST的LRD(元素均不相同)。
书中方法:首先对于二叉搜索树,左子树中的所有元素小于根节点小于右子树中的所有元素,然后后序遍历序列最后一个元素是根节点,这是我们已知的条件。这道题不禁让人想起用一个普通二叉树的前序(或后序)遍历序列加上中序遍历序列就可以还原一棵二叉树,在那道题中,我们知道前序或后序序列就能开始组织根节点,但是因为我们无法确定子树的个数,所以还需要一个辅助的序列来确定子树范围。如果改成BST的前序或后序遍历序列,我们就可以直接组织二叉树,BST的特性帮我们确定了子树的范围。对于这道题,我们要判断一个序列是不是BST的后序遍历序列,同样可以用确定根节点的思路,但是这里不需要重建二叉树,我们只需要在当前递归中判断两个子序列是不是有效的就行。我们先找到根节点(最后一个元素),然后用它找到左右子树的分割点,判断分割点右侧是不是都大于根节点,如果是就去递归检查分割点左边和分割点右边的两个子序列。
public boolean judge(int[] a){
if(a == null)return false;
if(a.length == 0)return true;
return myJudge(a, 0, a.length-1);
}
private boolean myJudge(int[] a, int start, int end){
//加上等号也可以
if(start > end)return true;
int leftEnd = start-1;
for(int i=start; i<=end-1; i++){
if(a[i]<a[end]){
leftEnd++;
}
}
for(int i=leftEnd+1; i<=end-1; i++){
if(a[i] < a[end]){
return false;
}
}
return myJudge(a, start, leftEnd) && myJudge(a, leftEnd+1, end-1);
}
题目25:二叉树中和为某一值的路径
判断是否有从根到叶子节点的路径,其和为给定值。记录这些路径。
我的方法:这道题我是按照回溯的思路去做的,我们需要一个数据结构来保存和删除当前递归函数中添加的值。这里要打印一条路径,我们可以选择List、栈等,它们都可以很方便的删除掉末尾的元素从而保护现场,也可以选择String,只需要在进入递归的时候创建一个和参数一样的值,也能做到在子函数退出的时候保护现场的效果。递归的时候先判断是否满足条件,然后添加本节点的值往下递归。
public void findPath(TreeNode root, int target){
if (root == null) {
return;
}
Stack<TreeNode> path = new Stack<TreeNode>();
int sum = 0;
find2(path, root, sum, target);
}
private void find(Stack<TreeNode> path, TreeNode root, int sum, int target){
if(sum > target)return;
if(root == null && sum < target)return;
if(root == null && sum == target){
for(TreeNode treeNode : path){
System.out.print(treeNode.val+" ");
}
System.out.println();
return;
}
if(root != null){
if(root.left != null || root.right != null){//如果左右子树有不空的,向下寻找
if(root.left != null){
path.push(root);
sum += root.val;
find(path, root.left, sum, target);
sum -= root.val;
path.pop();
}
if(root.right != null){
path.push(root);
sum += root.val;
find(path, root.right, sum, target);
sum -= root.val;
path.pop();
}
}else{//如果左右子树都空了,说明到了叶子节点
path.push(root);
sum += root.val;
//发送结束信息
find(path, null, sum, target);
sum -= root.val;
path.pop();
}
}
}
书中方法:先添加本节点值,然后判断是否满足条件,接着往下递归。
public void findPath(TreeNode root, int target){
if (root == null) {
return;
}
Stack<TreeNode> path = new Stack<TreeNode>();
int sum = 0;
find2(path, root, sum, target);
}
private void find2(Stack<TreeNode> path, TreeNode root, int sum, int target){
path.push(root);
sum += root.val;
if(sum > target){
path.pop();
return;
}
if(sum == target && root.left == null && root.right == null){
//从栈底到栈顶打印
for(TreeNode treeNode : path){
System.out.print(treeNode.val+" ");
}
System.out.println();
}
if(root.left != null)find2(path, root.left, sum, target);
if(root.right != null)find2(path, root.right, sum, target);
//保护现场
path.pop();
}
这道题如果是任意路径,而不要求从根节点到叶子节点。那么思路应该是分为两层递归,外层递归就是遍历二叉树,内层寻找路径。