剑指offer_面试题27:二叉树镜像,面试题28:对称的二叉树,面试题29:顺时针打印矩阵,最大公约数和最小公倍数,面试题30:包含min函数的栈,面试题31:栈的压入、弹出序列
·
面试题27:二叉树镜像

- 注意: 该题让求一棵二叉树的镜像,而不是判断两棵树是否为镜像对称。
- 分析:
- 先画出一个二叉树的镜像:

- 发现与镜像二叉树相比,根节点不变,左孩子成了右孩子,右孩子成了左孩子。
- 于是,可以考虑通在先序遍历的过程中,加入交换左右孩子节点的操作。

- 代码如下:
public TreeNode invertTree(TreeNode root) {
// root为null的特殊情况无需处理
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) {
// 要判断左右子树,需要处理root为null的情况
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) {
// 直接传入两个自己,不用判断根节点为null的特殊情况
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) {
// 从左向右打印,对应行为r1,起始点为c1,一直到c2
for (int i = c1; i <= c2; i++) {
list.add(matrix[r1][i]);
}
// 从上到下打印,对应列为c2,起始点为r1+1,一直到r2
for (int i = r1 + 1; i <= r2; i++) {
list.add(matrix[i][c2]);
}
// 没打印完,从右到左打印,对应行为r2,从c2-1开始,一直到c1
if (r1 != r2) {
for (int i = c2 - 1; i >= c1; i--) {
list.add(matrix[r2][i]);
}
}
// 没打印完,从下到上打印,对应列为c1,从r2-1开始,一直到r1-1
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;
}
// 在while循环中已经更新了max为除数,因此要返回max
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函数的栈

- 自己的
native想法:
- 要想调用
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();
}
更多推荐
所有评论(0)