面试题27:
- 注意: 该题让求一棵二叉树的镜像,而不是判断两棵树是否为镜像对称。
- 分析:
public TreeNode invertTree(TreeNode root) {
prevOrder(root);
return root;
}
public void prevOrder(TreeNode root) {
if (root == null) {
return;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
prevOrder(root.left);
prevOrder(root.right);
}
面试题28:
- 如果一颗二叉树关于自身对称,则以根节点为中心,左边的等于右边的,右边等于左边的。
① 比较左右子树
- 除去根节点,其他节点以根节点为轴对称:左孩子等于右孩子,右孩子等于左孩子。
- 相当于判断二叉树的左右子树是否镜像对称,在进行子树是否相同的基础上,只需要更改判断的对象就可以实现。
public boolean isSymmetric(TreeNode root) {
if (root==null){
return true;
}
return isDuichen(root.left, root.right);
}
public boolean isDuichen(TreeNode node1, TreeNode node2) {
if (node1 == null && node2 == null) {
return true;
}
if (node1 == null || node2 == null) {
return false;
}
return (node1.val == node2.val) && isDuichen(node1.left, node2.right) &&
isDuichen(node1.right, node2.left);
}
② 两个自己进行比较
- 如果一棵二叉树关于自身对称,两个它关于中间成镜像对称。因此,可以在判断是否对称时,直接传入两个自己。
- 代码如下:
public boolean isSymmetric(TreeNode root) {
return isDuichen(root,root);
}
③ 迭代实现
- 利用两个stack,实现对左右子树的节点的存储。
- 压入左右子树的孩子节点时,按照左孩子/右孩子、右孩子/左孩子的顺序压入。
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
Stack<TreeNode> s1 = new Stack<>();
Stack<TreeNode> s2 = new Stack<>();
s1.push(root.left);
s2.push(root.right);
while (!s1.isEmpty() && !s2.isEmpty()) {
TreeNode left = s1.pop();
TreeNode right = s2.pop();
if (left == null && right == null) {
continue;
}
if (left == null || right == null) {
return false;
}
if (left.val != right.val) {
return false;
}
s1.push(left.left);
s2.push(right.right);
s1.push(left.right);
s2.push(right.left);
}
return true;
}
面试题29:
- 矩阵每次打印都是由上方的
r1
,下方的r2
,左边的c1
,右边的c2
去共同控制的。
- 注意: 当从右到左、从下到上的打印之前,需要先判断是否可以停止打印。
- 代码如下:
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> list = new ArrayList<>();
if (matrix.length == 0 || matrix[0].length == 0) {
return list;
}
int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1;
while (r1 <= r2 && c1 <= c2) {
for (int i = c1; i <= c2; i++) {
list.add(matrix[r1][i]);
}
for (int i = r1 + 1; i <= r2; i++) {
list.add(matrix[i][c2]);
}
if (r1 != r2) {
for (int i = c2 - 1; i >= c1; i--) {
list.add(matrix[r2][i]);
}
}
if (c1 != c2) {
for (int i = r2 - 1; i > r1; i--) {
list.add(matrix[i][c1]);
}
}
r1++;
r2--;
c1++;
c2--;
}
return list;
}
最大公约数和最小公倍数
①
- 以
(319,377)
为例,交换二者的位置,让较大数做被除数。 - 58 = 377 % 319,则有
(319,58)
- 29 = 319 % 58,则有
(58,29)
- 0 = 58 % 29,则最大公约数为29
public int GCD(int a, int b) {
int max = Math.max(a, b);
int min = Math.min(a, b);
while (min != 0) {
int temp = max % min;
max = min;
min = temp;
}
return max;
}
②
- 求到最大公约数后,将两个数的乘积除以最大公约数,就是最小公倍数:
a* b/ GCD(a, b)
。 - 代码如下:
public static int LCM(int a, int b) {
int max = Math.max(a, b);
int min = Math.min(a, b);
while (min != 0) {
int temp = max % min;
max = min;
min = temp;
}
return a * b / max;
}
面试题30:
- 要想调用
min
函数时,时间复杂度为
O
(
1
)
O(1)
O(1),直接使用一个min
变量存储栈中的最小值。 - 比如压入3,2,1,最小值为1,如果弹出了1,那最小值应该是栈中的2还是3呢?
- 这时,如果查找栈中
min
,时间复杂度就不是
O
(
1
)
O(1)
O(1)了。
- 创建一个存储当前栈中最小元素的辅助栈,每次获取
min
时,直接获取辅助栈栈顶元素即可。 - 数据栈弹出一个元素,辅助栈也需要弹出对应的元素。
- 辅助栈压入的元素为:
min(数据栈待压入元素,辅助栈的栈顶元素)
。
class MinStack {
private Stack<Integer> data;
private Stack<Integer> helper;
public MinStack() {
data = new Stack<>();
helper = new Stack<>();
}
public void push(int x) {
data.push(x);
int min = (helper.isEmpty()) ? x : Math.min(x, helper.peek());
helper.push(min);
}
public void pop() {
helper.pop();
data.pop();
}
public int top() {
return data.peek();
}
public int getMin() {
return helper.peek();
}
}
面试题31:栈的压入、弹出序列
- 对于压入序列,如果与弹出序列不相同,则直接压入栈;如果待压入的数字是下一个弹出的数字,则不压入数字,直接逃过。
- 编程思路:
- 先压入数字,在将匹配的栈顶元素弹出栈。
- 如果最后栈不为空,说明不是弹出序列;否则,是弹出序列。
- 无需进行空数组判断,如果数组为空不会进行for循环。
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack = new Stack<>();
int cur = 0;
for (int i = 0; i < pushed.length; i++) {
stack.push(pushed[i]);
while (!stack.isEmpty()) {
if (stack.peek() == popped[cur]) {
stack.pop();
cur++;
} else {
break;
}
}
}
return stack.isEmpty();
}