解决面试题的思路(下)
包含min函数的栈
题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
分析
增加一个辅助栈:
辅助栈的size保持和数据栈的size相同,但是辅助栈最top的元素是当前数据栈中的最小值min。
当获取数据栈最小元素min时,只需要获取辅助栈栈顶元素的大小即可。
当向数据栈中push一个新的元素X时,将X和最小值min比较,
- X>min:将min值push入辅助栈;
- X<min:将X值push入辅助栈;
当从数据栈中pop出一个元素Y时,从辅助栈中同时pop出一个元素。
Java代码
import java.util.Stack;
public class Solution {
Stack<Integer> stack = new Stack<Integer>();
Stack<Integer> stackHelp = new Stack<Integer>();
public void push(int node) {
stack.push(node);
int min = node;
if(!stackHelp.isEmpty() && node>stackHelp.peek())
min = stackHelp.peek();
stackHelp.push(min);
}
public void pop() {
stack.pop();
stackHelp.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return stackHelp.peek();
}
}
栈的压入、弹出序列
题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
分析
借助一个辅助栈,例如题目中序列:
由于上图中保存带判断的弹出序列只有pop操作,没有push操作,所以可以有一个序列来表示,利用一个index来指示当前比较的元素。
Java代码
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
if(pushA.length != popA.length)
return false;
//创建一个辅助栈
Stack<Integer> stackPushA = new Stack<Integer>();
// 用来指示popA中元素pop的位置;
int idxPopA = 0;
for(int i=0; i<pushA.length; i++){
stackPushA.push(pushA[i]);
// 先将该元素压到栈中
// 如果栈不为空,且栈顶的元素和popA指示的元素一样,
// 就将栈顶元素pop出,序列idxPopA向后移
while(!stackPushA.empty() && stackPushA.peek()==popA[idxPopA]){
stackPushA.pop();
idxPopA++;
}
}
// pushA中的所有元素都遍历过之后,如果辅助栈为空,则说明是正确的弹出序列
return stackPushA.empty();
}
}
从上往下打印二叉树
题目描述
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
例如:
输出的结果是8、6、1、5、7、9、2
树的结点定义:
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
分析
利用一个队列Queue(参考https://www.runoob.com/java/data-queue.html)存放元素:
首先将root放到Queue中,
然后在将root读入到List中的时候,将其左右孩子存放到Queue中。
由此反复,每读取Queue中一个结点,就看这个结点有没有孩子,如果就将左右孩子结点存入Queue中。
直到Queue为空。
Queue也可以采用Java中的双端队列Deque,参考https://blog.csdn.net/top_code/article/details/8650729
Deque最大的特点就是两端都可以增加和删除元素。
Java代码
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> res = new ArrayList<Integer>();
if(root == null)
return res;
Queue<TreeNode> q = new LinkedList<TreeNode>();
q.offer(root);
while(!q.isEmpty()){
TreeNode node = q.poll();
res.add(node.val);
if(node.left != null)
q.offer(node.left);
if(node.right != null)
q.offer(node.right);
}
return res;
}
}
二叉搜索树的后续遍历序列
题目描述
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
分析
已知搜索二叉树的特点是左孩子小于根节点,右孩子大于根节点。
如题所示的一棵二叉树,后续遍历序列的最后一个元素是根节点(深绿色)。
在序列的前面一定存在一个分界线,
- 分界线的左边(黄色)是左子树的元素,所有值都比根节点小,而最后一个元素就是左子树的根节点。
- 在分界线的右边(绿色)是右子树的元素,这些元素都比根节点大,同理,最后一个元素是右子树的根节点。
每一个子树也符合这样的规律。
再举一个例子。如果一个序列是{7,4,6,5}
- 根节点是5;
- 第一个元素是7,7>5,所以这棵搜索二叉树没有左子树。
- 检查右子树的所有结点时候都是大于5的。发现小于5的元素4。所以这棵树不是二叉搜索树。
Java代码
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence==null ||sequence.length==0)
return false;
return Verify(sequence, 0, sequence.length - 1);
}
public boolean Verify(int[] seq, int start, int root){
if(start >= root)
return true;
int i = start;
while(seq[i] < seq[root]) // 左子树
i++;
int idx = i; //左右子树的分界线,idx是右子树的第一个结点
while(seq[i] > seq[root]) // 右子树
i++;
if(i != root)
return false;
return (Verify(seq, start, idx-1) && Verify(seq, idx, root-1));
}
}
终止条件:
- 左(右)子树只有一个元素,则一定是搜索二叉树,此时Verify()中,START= ROOT;
- 左子树没有元素,右子树有元素:Verify(seq, start, idx-1)。START(start) > ROOT(idx-1)
- 左子树有元素,右子树没有元素:Verify(seq, idx, root-1)。START(idx) > ROOT(root-1)
二叉树中和为某一值的路径
题目描述
输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
二叉树结点定义:
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
分析
例如下图的二叉树,目标整数和为22,有两条路径
上述路径的寻找过程为:
步骤 | 操作 | 是否为叶结点 | 路径 | 路径结点的和 |
---|---|---|---|---|
1 | 访问结点10 | 否 | 10 | 10 |
2 | 访问结点12 | 是 | 10、12 | 22 |
3 | 返回结点10 | 否 | 10 | 10 |
4 | 访问结点5 | 否 | 10,5 | 15 |
5 | 访问结点4 | 是 | 10、5、4 | 19 |
6 | 返回结点5 | 否 | 10、5 | 15 |
7 | 访问结点7 | 是 | 10、5、7 | 22 |
访问结点的顺序就是先序遍历的顺序。
按照题意需要按照长度从大到小排序,所以最终结果为:[[10,5,7], [10,12]]
ArrayList的排序依靠Collections.sort()
Java代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Solution {
ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> path = new ArrayList<Integer>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root == null)
return res;
find(root, target);
// 对结果进行排序,按照长度从大到小。
Collections.sort(res, new Comparator<ArrayList<Integer>>() {
@Override
public int compare(ArrayList<Integer> list1, ArrayList<Integer> list2) {
if (list1.size() < list2.size())
return 1;
else
return -1;
}
});
return res;
}
public void find(TreeNode root, int target){
path.add(root.val);
target -= root.val;
// 符合要求的叶子结点
if(root.left==null && root.right==null && target==0)
res.add(new ArrayList<Integer>(path));
else{
// 左子树
if(root.left != null)
find(root.left, target);
// 右子树
if(root.right != null)
find(root.right, target);
}
// 返回上一结点
path.remove(path.size() - 1);
}
}