下面这段时间带来的是对于剑指Offer(第二版)一书中的算法题目进行阅读并分享。原书中一共66道题目,我们就一天11道,用六天的时间来进行讲解,最后一天来个总结,争取在一周的时间内介绍完这66道经典题目。要是喜欢的欢迎关注公众号《Java冢狐》来追更!
今天是剑指Offer的第三期,
另外由于原书是C++代码编写而成,这边我们用Java来实现一遍,顺便说一下相关的面试知识点,一起进行面试前的复习。希望大家能够喜欢。
另外有些地方的讲解可能并不是十分到位,在此更推荐更大家去看原书。
那么话不多少,让我们开始今天的解题之路吧!
二十三、链表中环的入口节点
- 问题
如果一个链表中包含环,如何找到环的入口节点?
这个在快慢指针中也有提到,也不在过多赘述:
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
二十四、翻转链表
- 问题
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
这个属于链表入门题目。
public ListNode reverseList(ListNode head) {
ListNode ans =null;
while(head!=null){
ListNode temp =head.next;
head.next=ans;
ans=head;
head=temp;
}
return ans;
}
二十五、合并两个排序的链表
- 问题
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
因为我们无法确定l1和l2中那个头节点小,所以我们预设的时候先引入一个没有意义的头结点,输出的时候就直接输出其下一个节点即可:
这三道题目都是属于链表中最基础,也是最常考察的题目。
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode ans = new ListNode(0);
ListNode temp = ans;
while (l1 != null && l2 != null) {
if (l1.val > l2.val) {
temp.next = l2;
l2 = l2.next;
} else {
temp.next = l1;
l1 = l1.next;
}
temp = temp.next;
}
temp.next = l1 != null ? l1 : l2;
return ans.next;
}
二十六、树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
要想判断B是否是A的子结构,那么首先就要找到B的根节点在A中的位置,然后判断其是不是他的一个子结构。所以说一共分为两步:
找到B的根节点
-
- A为根节点的子树包含B
- B是A左子树的子结构
- B是A右子树的子结构
判断是否包含B
-
- B为空。匹配完成
- A为空。匹配失败
- AB的值不相等。匹配失败
看起来貌似很复杂,其实用递归调用来处理起来,相当的简单。后续题目中有很多都会用到这个思路,一点不要因为看起来复杂而放弃,其实真正写起来很舒服的。
public boolean isSubStructure(TreeNode A, TreeNode B) {
if (A == null || B == null)
return false;
return recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
}
// 判断是否包含
boolean recur(TreeNode A, TreeNode B) {
if (B == null)
return true;
if (A == null)
return false;
if (A.val != B.val)
return false;
return recur(A.left, B.left) && recur(A.right, B.right);
}
二十七、二叉树的镜像
- 问题
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
直接使用递归法,来解决,还是那句话,只要是捋清楚了,就很简单。
public TreeNode mirrorTree(TreeNode root) {
if (root == null)
return null;
TreeNode temp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(temp);
return root;
}
二十八、对称的二叉树
- 问题
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
还是一样的思路,都快找到规律了,直接递归调用即可。
public boolean isSymmetric(TreeNode root) {
return root == null ? true : recur(root.left, root.right);
}
boolean recur(TreeNode L, TreeNode R) {
if(L == null && R == null) return true;
if(L == null || R == null || L.val != R.val) return false;
return recur(L.left, R.right) && recur(L.right, R.left);
}
二十九、顺时针打印矩阵
- 问题
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
这个没啥好说的就是打印就完事了,按照题目顺序打印即可。
public int[] spiralOrder(int[][] matrix) {
if (matrix.length == 0)
return new int[0];
int[] res = new int[matrix.length * matrix[0].length];
int x = 0;
int row = matrix.length - 1;
int y = 0;
int column = matrix[0].length - 1;
int idx = 0;
while (true) {
for (int i = y; i <= column; i++) {
res[idx++] = matrix[x][i];
}
if (++x > row) {
break;
}
for (int i = x; i <= row; i++) {
res[idx++] = matrix[i][column];
}
if (--column < y) {
break;
}
for (int i = column; i >= y; i--) {
res[idx++] = matrix[row][i];
}
if (--row < x) {
break;
}
for (int i = row; i >= x; i--) {
res[idx++] = matrix[i][y];
}
if (++y > column) {
break;
}
}
return res;
}
三十、包含min函数的栈
- 问题
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
由于需要min、push和pop都是常数复杂度,所以我们需要一个辅助栈使得其栈顶永远是最小的元素。
当添加一个新元素比栈中最小元素还小时,就需要更新这个辅助栈,要是比这个大的话就不需要,因为栈的先进后出的原则,这个新加的这个比最小值大的元素肯定是要比这个最小值先出去的,所以无论如何都不会影响到最小值。
而出栈操作的时候,让发现出栈元素是最小元素的时候,就要把辅助栈中的元素一起出栈
class MinStack {
Stack<Integer> A, B;
public MinStack() {
A = new Stack<>();
B = new Stack<>();
}
public void push(int x) {
A.add(x);
if(B.empty() || B.peek() >= x)
B.add(x);
}
public void pop() {
if(A.pop().equals(B.peek()))
B.pop();
}
public int top() {
return A.peek();
}
public int min() {
return B.peek();
}
}
三十一、栈的压入、弹出序列
- 问题
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
这个题目需要一个栈来进行模拟操作,按照压栈顺序进行入栈,在入栈的过程中判断栈顶元素是否等于弹出序列的当前元素是否成立,将符合的弹出。
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack = new Stack<>();
int i = 0;
for (int num : pushed) {
// 入栈
stack.push(num);
while (!stack.isEmpty() && stack.peek() == popped[i]) {
stack.pop();
i++;
}
}
return stack.isEmpty();
}
三十二、从上到下打印二叉树
- 问题
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
从上到下打印二叉树,即层次遍历,即广度优先搜索(BFS),通常借助队列的先进先出的特性来实现。即:
public int[] levelOrder(TreeNode root) {
if (root == null)
return new int[0];
Queue<TreeNode> queue = new LinkedList<>() {{
add(root);
}};
ArrayList<Integer> ans = new ArrayList<>();
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
ans.add(node.val);
if (node.left != null)
queue.add(node.left);
if (node.right != null)
queue.add(node.right);
}
int[] res = new int[ans.size()];
for (int i = 0; i < ans.size(); i++)
res[i] = ans.get(i);
return res;
}
三十三、二叉搜索树的后续遍历序列
- 问题
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true
,否则返回 false
。假设输入的数组的任意两个数字都互不相同。
因为是后续遍历,所以最后一个节点就是根节点,根据二叉搜索树的性质可以判断其右子树和左子树分别的位置,然后依次判断即可
public boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1);
}
// ij分别为树的后续遍历的左边和右边即j表示根节点
boolean recur(int[] postorder, int i, int j) {
if (i >= j)
return true;
int p = i;
// 确定左子树的范围i-p
while (postorder[p] < postorder[j])
p++;
int m = p;
// 确定右子树的范围p-m
while (postorder[m] > postorder[j])
m++;
return m == j && recur(postorder, i, p - 1) && recur(postorder, p, j - 1);
}
最后
- 如果觉得看完有收获,希望能关注一下,顺便给我点个赞,这将会是我更新的最大动力,感谢各位的支持
- 欢迎各位关注我的公众号【java冢狐】,专注于java和计算机基础知识,保证让你看完有所收获,不信你打我
- 求一键三连:点赞、转发、在看。
- 如果看完有不同的意见或者建议,欢迎多多评论一起交流。感谢各位的支持以及厚爱。
——我是冢狐,和你一样热爱编程。
欢迎关注公众号“ Java冢狐”,获取最新消息