剑指offer(三)
概述
继续刷题。。。
第十七题
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
解题思路
这道题,一开始看可能会懵,这道题的关键就是先在A中找到B的根节点,当找到B的根节点之后,事情就简单了,就从这个根节点开始以相同的方式遍历A和B,然后比较,如果一致,就说明是,如果不一致,说明A中的这个节点不行,再继续寻找和B的根节点相同的节点。
代码
public class Solution { public boolean HasSubtree(TreeNode root1,TreeNode root2) { boolean result = false; if(root1 != null && root2 != null){ if(root1.val == root2.val){ result = compare(root1,root2); } //这里一开始我想直接使用else,如果使用else的话要求树中没有重复元素才可以 //比如如果有重复元素,即便第一个元素相同,他们的左右子路也不同 if(!result){ result = HasSubtree(root1.left,root2); } if(!result){ result = HasSubtree(root1.right,root2); } } return result; } public boolean compare(TreeNode node1,TreeNode node2){ //这种情况说明A中的元在相应的子树上没有B中元素多,不一致 if(node1 == null && node2 != null){ return false; } //当B的节点寻找完了,都一致,就说明是 if(node2 == null){ return true; } if(node1.val != node2.val){ return false; } //只有左右子树都相同,才成立 return compare(node1.left,node2.left) && compare(node1.right,node2.right); } }
总结
对于有返回的递归,要特别注意返回值的处理,比如这个结果会不会作为上一步的条件。
第十八题
操作给定的二叉树,将其变换为源二叉树的镜像。 输入描述: 二叉树的镜像定义:源二叉树 8 / \ 6 10 / \ / \ 5 7 9 11 镜像二叉树 8 / \ 10 6 / \ / \ 11 9 7 5
解题思路
这道题其实就是反转二叉树,只要找到一个遍历二叉树的方法就可以实现,就是每遍历到一个节点就交换左右节点就可以了。
代码
/** public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { public void Mirror(TreeNode root) { if(root == null){ return; } TreeNode temp = root.left; root.left = root.right; root.right = temp; Mirror(root.left); Mirror(root.right); } }
第十九题
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
解题思路
这道题其实就是顺时针打印矩阵中的值,反正我是没有想出来怎么做,在牛客网上看到两种实现思路
- 第一种方法,四条边单独控制,按照顺时针打印,我就是采用这种思路实现的
- 第二种方法,非常的秀,每次只打印矩阵的最上面一行,打印完之后就把这行数据删除,之后逆时针旋转矩阵,这样本来应该打印的那条边就跑到最上面了,但是矩阵的旋转开销太高,所以这里采用第一种。
代码
import java.util.ArrayList; public class Solution { public ArrayList<Integer> printMatrix(int [][] matrix) { if(matrix.length == 0 && matrix[0].length == 0){ return null; } int up = 0; int left = 0; int down = matrix.length - 1; int right = matrix[0].length - 1; ArrayList<Integer> arrayList = new ArrayList(); //表示不停的转,不把最后一个打印出来就不停止 while(true){ //先干最上面一行 for(int i=left;i<=right;i++){ arrayList.add(matrix[up][i]); } up++; if(up > down){ break; } //在干最右边一列 for(int i=up;i<=down;i++){ arrayList.add(matrix[i][right]); } right--; if(left > right){ break; } //在干最下面一行 for(int i=right;i>=left;i--){ arrayList.add(matrix[down][i]); } down--; if(up > down){ break; } //在干最左边一列 for(int i=down;i>=up;i--){ arrayList.add(matrix[i][left]); } left++; if(left > right){ break; } } return arrayList; } }
第二十题
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
解题思路
这道题我一开看的时候觉得题意不明,因为他说定义栈数据结构,我还以为他让我先实现一个栈呢,看了别的解答,都是使用java自带的Stack,没劲,这道题有很多种实现方式,个人感觉下面的方式应该是最优的。
代码
import java.util.Stack; public class Solution { //第一栈就是正常存放数据 private Stack<Integer> stack = new Stack(); //第二个栈用来存放最小值,其中这个站栈中栈顶元素是其中最小的 private Stack<Integer> stack1 = new Stack(); public void push(int node) { stack.push(node); if(stack1.isEmpty()){ stack1.push(node); }else{ if(node < stack1.peek()){ stack1.push(node); } } } public void pop() { if(stack.peek() == stack1.peek()){ stack1.pop(); } stack.pop(); } public int top() { return stack.peek(); } public int min() { return stack1.peek(); } }
第二十一题
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。 例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
解题思路
这个就是一个正常的判断,很简单
代码
import java.util.ArrayList; import java.util.Stack; public class Solution { Stack<Integer> stack = new Stack(); public boolean IsPopOrder(int [] pushA,int [] popA) { if(pushA == null || popA == null || pushA.length != popA.length){ return false; } if(pushA.length == 0 && popA.length==0){ return true; } //控制第二个序列的 int j = 0; //控制第一个序列 int i = 0; //栈的作用就是模拟正常的入栈和出栈过程 stack.push(pushA[i]); i++; while(i <= pushA.length && j < popA.length){ //如果栈中存在,就弹出来 if(stack.peek() == popA[j]){ stack.pop(); j++; }else{ if(i == pushA.length){ break; } //栈中不存在,把第一个序列新的元素放进来 stack.push(pushA[i]); i++; } } //如果顺序正常,栈中的数据应该全部都弹出来 if(stack.isEmpty()){ return true; }else{ return false; } } }
第二十二题
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
解题思路
这个就是二叉树的层序遍历,使用队列就可以实现,之前有写过
代码
import java.util.ArrayList; import java.util.LinkedList; /** public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { LinkedList<TreeNode> queue = new LinkedList(); ArrayList<Integer> list = new ArrayList(); public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) { if(root == null){ return list; } queue.add(root); TreeNode root1; while(!queue.isEmpty()){ root1 = (TreeNode)queue.pop(); list.add(root1.val); if(root1.left != null){ queue.add(root1.left); } if(root1.right != null){ queue.add(root1.right); } } return list; } }
第二十三题
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
解题思路
这个题有点难,思路不容易想到,其实是利用了二叉搜索树的特点,就是根节点左边的数全部小于根节点,根节点右边的数全部大于根节点,而根节点就是数组的最后一个值,利用数组中最后一个值把二叉树分为左右子树,然后再利用上面的性质对左右子树进行分割,最后就可以判断。
代码
public class Solution { public boolean VerifySquenceOfBST(int [] sequence) { if(sequence == null || sequence.length == 0){ return false; } return isBST(sequence,0,sequence.length-1); } public boolean isBST(int[] sequence,int start,int end){ //如果左子树或者右子树只有一个节点或者两个节点,直接返回 if(end == start || end -start == 1){ return true; } int rootValue = sequence[end]; int i = start; //记录左右子树的分隔点 int j = end + 1; //这里i<end,为了把最后一个元素排除在外 boolean result = true; while(i < end){ if(sequence[i] > rootValue){ if(j == (end+1)){ j = i; } }else{ if(i > j){ return result; } } i++; } //如果只有右子树,或者只有左子树 if(j == start || j == (end+1)){ result = isBST(sequence,start,end-1); }else{ //处理左子树 result = isBST(sequence,start,j-1); //处理右子树 if(result == true){ result = isBST(sequence,j,end-1); } } return result; } }
第二十四题
输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
解题思路
这道题,题意有点难以理解,第一个就是什么是按字典序打印,第二个就是路径,我看牛客网别人实现的代码按字典序排序就是最后的满足条件的路径数组,按照数组长度从大到小存放,这个题的答案很优秀,可惜不是我想出来的。
代码
import java.util.ArrayList; /** public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; } } */ public class Solution { ArrayList<ArrayList<Integer>> resultList = new ArrayList(); //存放一个完整的路径 ArrayList<Integer> pathList = new ArrayList(); public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) { if(root == null){ return resultList; } //存放所有的路径 ArrayList<ArrayList<Integer>> result = find(root,target); result.sort((o1,o2)->o2.size() - o1.size()); return result; } public ArrayList<ArrayList<Integer>> find(TreeNode root,int target){ //到底了,但是这条路径不满住条件 if(root == null){ return resultList; } pathList.add(root.val); target = target - root.val; //到底了,而且target也减小到0,这时就说明是一个正确的路径 if(target == 0 && root.left == null && root.right == null){ resultList.add(new ArrayList(pathList)); } //还没有到底,继续寻找 find(root.left,target); find(root.right,target); //如果上面两个递归find都执行了,把放入的pathList的末尾元素移除,用于去寻找其他路径 pathList.remove(pathList.size()-1); return resultList; } }
总结
这个题倒数第二行那个remove方法可能有点不好理解,仔细想想也没有那么难,其实就是某个路径不满足,就把这个路径的最后一个元素干掉,然后回到这个元素的父节点,从父节点在去找别的子节点,如果这个父节点所有的子节点都不满足,就把父节点也干掉,在找父节点的父节点,从这个节点开始再去找别的子节点,,,如此循环。
写在最后的话
发现进大厂拿高工资是有原因的,这些题如果都掌握,真的是非常非常费时费力,不知道为什么一定要求会这些,可能只有这些东西才没有那么容易学,所以可以筛选人吧。