标签6二叉树与数组
二叉树
二叉树的前序后序都是根节点入栈,层次遍历是根节点先入队
145. 二叉树的后序遍历
详见书数据结构高分笔记P147,比如下面的求二叉树的公共祖先或者二叉搜索树的公共祖先的题目就是使用的二叉树的后序遍历改进而来。剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
解法1:递归的解法
class Solution {
List<Integer> list;
public List<Integer> postorderTraversal(TreeNode root) {
list = new ArrayList<>();
if(root == null) {
return list;
}else {
postOrder(root);
return list;
}
}
//java里可以另外定义一个实例变量来存储递归函数的返回值,这样递归函数就可以没有返回值了。
public void postOrder(TreeNode node) {
if(node == null) {
return;
}else {
// 后序,左右根
postOrder(node.left);
postOrder(node.right);
list.add(node.val);
}
}
}
解法2:非递归的解法
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null) {
return list;
}else {
Stack<TreeNode> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
TreeNode tem = null;
//根节点先入栈
stack1.push(root);
//先按根右左遍历,那么入栈顺序就应该是左子树右子树
//把根右左遍历的结果放在另外一个栈里,出栈即可
while(! stack1.isEmpty()) {
tem = stack1.pop();
stack2.push(tem.val);
if(tem.left != null) {
stack1.push(tem.left);
}
if(tem.right != null) {
stack1.push(tem.right);
}
}
while(! stack2.isEmpty()) {
list.add(stack2.pop());
}
return list;
}
}
}
144. 二叉树的前序遍历
树的深度优先搜索遍历就是前序遍历,与树有关的回溯也是前序遍历,其实回溯就是深度优先搜索遍历(DFS),因此比较重要
解法:递归
class Solution {
List<Integer> list;
public List<Integer> preorderTraversal(TreeNode root) {
list = new ArrayList<>();
if(root == null) {
return list;
}else {
preOrder(root);
return list;
}
}
public void preOrder(TreeNode node) {
//递归终止条件
if(node == null) {
return;
}else {
//递归一般条件,前序,根左右
list.add(node.val);
preOrder(node.left);
preOrder(node.right);
}
}
}
解法2:非递归
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null) {
return list;
}else {
Stack<TreeNode> stack = new Stack<>();
//根节点先入栈
stack.push(root);
TreeNode tem = root;
//根据栈的特点,后入先出,因此进栈的顺序应该是右子树,左子树
while(! stack.isEmpty()) {
tem = stack.pop();
list.add(tem.val);
if(tem.right != null) {
stack.push(tem.right);
}
if(tem.left != null) {
stack.push(tem.left);
}
}
return list;
}
}
}
94. 二叉树的中序遍历
中序遍历的用处是二叉搜索树,因为二叉搜索树的特点:1.节点的值不重复2.根节点的值大于左孩子小于右孩子,并且在根节点的左右子树也成立,因此中序遍历二叉搜索树有序。就是因为中序遍历二叉搜索树有序,因此与二叉搜索树有关的题目都是中序遍历。非递归解法不好记忆,不用掌握。
解法:递归
class Solution {
List<Integer> list;
public List<Integer> inorderTraversal(TreeNode root) {
list = new ArrayList<>();
if(root == null) {
return list;
}else {
inOrder(root);
return list;
}
}
public void inOrder(TreeNode node) {
//递归终止条件
if(node == null) {
return;
}else {
//递归一般条件
inOrder(node.left);
list.add(node.val);
inOrder(node.right);
}
}
}
解法2:非递归
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null) {
return list;
}else {
TreeNode current = root;
Stack<TreeNode> stack = new Stack<>();
//中序遍历左根右
while(current != null || ! stack.isEmpty()) {
//从根节点到最左的节点依次进栈
while(current != null) {
stack.push(current);
current = current.left;
}
//最左的节点赋给current
current = stack.pop();
list.add(current.val);
//准备遍历current的右子树
current = current.right;
}
return list;
}
}
}
102. 二叉树的层次遍历
解法:基本的层次遍历改进一下。以下与本题无关,如有题目:求二叉树的最大宽度,可以使用改进的层次遍历(while循环里增加一个for循环),求二叉树的最大宽度其实就是求层次遍历的过程中queue.size()的最大值
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
//用ArrayList或者LinkedList都可以
List<List<Integer>> answer = new ArrayList<>();
if(root == null) {
return answer;
}else {
//LinkedList实现了Queue接口,当然ArrayDeque也实现了Queue接口,是基于数组的双端队列,因此是ArrayDeque不是ArrayQueue,LinkedList与ArrayDeque的方法基本一样
//对于下面一句,虽然LinkedList实现了Queue接口,但是由于Queue中没有addLast()等方法,会报错,这个与多态有关可以问我
//Queue<TreeNode> queue = new LinkedList<>();
LinkedList<TreeNode> queue = new LinkedList<>();
//addLast()removeFirst()一起使用,可以实现队列
//addFirst()removeLast()一起使用也可以实现队列
//根节点入栈
queue.addLast(root);
TreeNode tem = null;
while(! queue.isEmpty()) {
/*
//以下是最基本的层次遍历
//不用判断tem是否为空,后面会说原因
tem = queue.removeFirst();
//遍历,这里假设answer = new ArrayyList<Integer>();
answer.add(tem.value);
//下面两个是ifif的关系,不是ifelse
//也正是由于树节点不空才加入queue,因此前面无需判断tem是否为空
if(tem.left != null) {
queue.addLast(tem.left);
}
if(tem.right != null) {
queue.addLast(tem.right);
}
*/
//因为题目要求是将每一层的节点值,放在一个list中,因此需要用一个循环,一次出队一层的节点,出队的同时再把出队节点的孩子节点(不空)加入
//对于每一层节点都新new一个list
List<Integer> list = new ArrayList<>();
int size = queue.size();
//一次出size个节点,这些节点在一层
//for(int i = 0;i < queue.size();i ++) {...}不对,因此queue是边出边进,因此queue.size()是变化的
for(int i = 0;i < size;i ++) {
tem = queue.removeFirst();
//无需判断tem是否为空下面会说
list.add(tem.val);
//因为不空才加入queue,因此queue中的树节点都不空
if(tem.left != null) {
queue.addLast(tem.left);
}
if(tem.right != null) {
queue.addLast(tem.right);
}
}
//将list加入answer
answer.add(list);
}
return answer;
}
}
}
199. 二叉树的右视图
解法:二叉树的层次遍历的改进,如有题目:求二叉树的最大宽度,可以使用改进的层次遍历(while循环里增加一个for循环),求二叉树的最大宽度其实就是求层次遍历的过程中queue.size()的最大值,也就是求题目中sum的最大值
class Solution {
//因为每次都是要的每一层的最右的节点,因此要层次遍历
public List<Integer> rightSideView(TreeNode root) {
List<Integer> answer = new ArrayList<>();
if(root == null) {
return answer;
}else {
//LinkedList可以模拟栈也可以模拟队列也可以模拟双端队列
//详见LinkedList的方法addFirst()addLast()removeFirst()
//removeLast()
LinkedList<TreeNode> list = new LinkedList<>();
TreeNode tem = null;
int sum = 0;
list.add(root);
//层次遍历,与普通的层次遍历不同的是,一次出完队列所有的节点
while(! list.isEmpty()) {
//list存储的一层从左到右所有的节点
//每次加入list的最后一个节点的val到answer
answer.add(list.peekLast().val);
//sum记录出队之前的长度,因为list是边出边进,要有一个变量记录出队的个数
sum = list.size();
//一次性出队完,因为每次向answer加入的是一层的最右节点,不能用传统的层次遍历
for(int i = 0;i < sum;i ++) {
tem = list.removeFirst();
if(tem.left != null) {
list.add(tem.left);
}
if(tem.right != null) {
list.add(tem.right);
}
}
}
return answer;
}
}
}
958. 二叉树的完全性检验
解法:和上题一样,需要使用到层次遍历序列中当前节点的上一个节点,不要忘记更新当前节点的上一个节点。
另外,和普通层次遍历不同的是需要存储空节点,因为使用的方法是,如果上一个节点为空,这个节点不为空那么一定不是完全二叉树
class Solution {
//思路:层次遍历的时候,记录前一个节点,前一个节点为空,当前节点不为空,那么肯定不是完全二叉树
//1.与普通的二叉树的层次遍历相比,这种需要缓存空节点
public boolean isCompleteTree(TreeNode root) {
if(root == null) {
return true;
}else {
LinkedList<TreeNode> list = new LinkedList<>();
list.addLast(root);
TreeNode current = root;
//2.pre的初始化不要为空,可以是不为空的任何节点,因为如果为空的话,以下while循环进行一次就结束返回false
TreeNode pre = root;
//层次遍历
while(list.isEmpty()) {
current = list.removeFirst();
if(pre == null && current != null) {
return false;
}
//3.更新pre
pre = current;
if(current != null) {
//4.就算current的left,right是空也添加进去
list.addLast(current.left);
list.add(current.right);
}
}
return true;
}
}
}
103. 二叉树的锯齿形层次遍历
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
//关于<>的问题
//下面可以
//List<List<Integer>> answer = new LinkedList<List<Integer>>();
//下面不可以
//List<List<Integer>> answer = new LinkedList<ArrayList<Integer>>();
//List<List<Integer>> answer = new LinkedList<>();
if(root == null){
return answer;
}else{
//ArrayDeque与LinkeddList都可以使用
Deque<TreeNode> queue = new ArrayDeque<>();
//根节点入队
queue.addLast(root);
//level记录层数,也可以用flag来标识奇数层与偶数层
int level = 1;
while( ! queue.isEmpty()){
//list记录一层的节点值
ArrayList<Integer> list = new ArrayList<>();
//num记录当前队列的长度,一次性for循环出双端队列
//然后循环中,节点出队,节点的子树入双端队列
//while(! queue.isEmpty()){
int num = queue.size();
//不能直接用i < queue.size(),应该是i < num,因为queue的size在for循环中变化
for(int i = 0;i < num;i ++){
TreeNode tem = null;
//以下的队指的是双端队列
//奇数层,队头出队,队尾入队(先左子树后右子树)
if(level % 2 == 1){
tem = queue.pollFirst();
list.add(tem.val);
if(tem.left != null){
queue.addLast(tem.left);
}
if(tem.right != null){
queue.addLast(tem.right);
}
}else{
//偶数层,双端队列的队尾出队
//那么肯定要队头入队,为了保证层次遍历的从左到右遍历
//右子树先从队头入队,然后左子树
tem = queue.pollLast();
list.add(tem.val);
if(tem.right != null){
queue.addFirst(tem.right);
}
if(tem.left != null){
queue.addFirst(tem.left);
}
}
}
//for循环之后,进入下一层遍历,level++
level ++;
answer.add(list);
}
return answer;
}
}
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
以下是根据二叉树的
236. 二叉树的最近公共祖先
解法:思路见代码注释,理解lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)函数的意思:在根节点是root的树中,寻找p和q的最近公共祖先,然后递归调用的时候更加方便理解。其实是基于树的后序遍历改的
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//递归终止条件
if(p == null) {
return q;
}
if(q == null) {
return p;
}
if(root == null || p == root || q == root){
return root;
}
//在root的左右子树分别递归调用lowestCommonAncestor方法,寻找p和q的最近公共祖先
//得到结果leftanswer和rightanswer
//举例,root表示的树高度是2,root的左右子树分别是p,q
TreeNode leftanswer = lowestCommonAncestor(root.left, p, q);
TreeNode rightanswer = lowestCommonAncestor(root.right, p, q);
//leftanswer和rightanswer都不空,返回root
if(leftanswer != null && rightanswer != null){
return root;
}
return leftanswer == null ? rightanswer : leftanswer;
}
}
剑指 Offer 68 - II. 二叉树的最近公共祖先
解法:同上,举例,root表示的树高度是2,root的左右子树分别是p,q。
一般情况下:最后的left与right无非是:
//1.left为空,right为空;2.left不为空,right不为空;
//3.left不为空,right不为空;4.left为空,right不为空
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//递归终止条件
if(p == null) {
return q;
}
if(q == null) {
return p;
}
if(root == null || p == root || q == root) {
return root;
}else {
//举例,root表示的树高度是2,root的左右子树分别是p,q
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left == null) {
return right;
}
if(right == null) {
return left;
}
return root;
}
}
}
面试题68 - I. 二叉搜索树的最近公共祖先
个人认为这个题相对于上面的是进阶版本
解法:这个是最优的解法。注意二叉搜索树的性质:节点的值不重复,对于一个树节点如果左右儿子都存在,它的值大于左儿子的值小于右儿子的值
class Solution {
//root是二叉搜索树,没有重复的元素
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//边界条件
if(p == null) {
return q;
}else if(q == null) {
return p;
}else if(root == null || p == root || q == root) {
return root;
}else {
//根据二叉搜索树的性质,以下三个分别对应pq都在root的左子树、pq都在root的右侧、pq在root的两侧
if(q.val < root.val && p.val < root.val) {
return lowestCommonAncestor(root.left,p,q);
}else if(q.val > root.val && p.val > root.val) {
return lowestCommonAncestor(root.right,p,q);
}else {
//由于qp在root的两侧,直接返回root
return root;
}
}
}
}
解法:大概思路和上面一样,递归终止条件增加一个只是当q的val和p的val在root的两端的时候,直接返回root即可。下面的没有用到,如果pq位于root的一个子树中,直接递归调用返回就行。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q){
if(p == null){
return q;
}
if(q == null){
return p;
}
if(root == null || root == p || root == q){
return root;
}
if((p.val > root.val && q.val < root.val)
|| (p.val < root.val && q.val > root.val)){
return root;
}
TreeNode leftanswer = lowestCommonAncestor(root.left,p,q);
TreeNode rightanswer = lowestCommonAncestor(root.right,p,q);
if(leftanswer != null && rightanswer != null){
return root;
}
return leftanswer == null ? rightanswer : leftanswer;
/*
if(root == null || root == p || root == q){
return root;
}
if((p != null && q != null && p.val > root.val && q.val < root.val)
|| (p != null && q != null && p.val < root.val && q.val > root.val)){
return root;
}
TreeNode leftanswer = lowestCommonAncestor(root.left,p,q);
TreeNode rightanswer = lowestCommonAncestor(root.right,p,q);
if(leftanswer != null && rightanswer != null){
return root;
}
return leftanswer == null ? rightanswer : leftanswer;
*/
}
剑指 Offer 07. 重建二叉树
解法1:
class Solution {
//树中各个节点的值都不相等
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length != inorder.length || (preorder.length == 0)) {
return null;
}else {
return rebuild(preorder,0,preorder.length - 1,inorder,0,inorder.length - 1);
}
}
//这个函数的意思是:根据preorder的[preleft,preright]的部分和inorder的[inleft,inright]的部分构建一个二叉树,其中preorder的[preleft,preright]的部分是这个二叉树的前序遍历,inorder的[inleft,inright]的部分是这个二叉树的后序遍历
public TreeNode rebuild(int[] preorder,int preleft,int preright,int[] inorder,int inleft,int inright){
//以下三个if都是递归的边界条件
//与下标有关的,不管是在一维数组中,还是在二维数组中,需要考虑下标越界的问题。
//任何下标越界,肯定返回null
if(inleft < 0 || inright > inorder.length - 1 || preleft < 0 || preright > preorder.length - 1){
return null;
}
//区间[preleft,preright]和[inleft,inright]是合法的
if(preleft > preright || inleft > inright){
return null;
}
//只有一个数,直接返回TreeNode
if(preleft == preright && inleft == inright){
return new TreeNode(preorder[preleft]);
}
//以下是递归的一般条件
//div其实是根节点的值,div可以把中序遍历划分为两个部分:左子树部分和右子树部分
int rootval = preorder[preleft];
TreeNode root = new TreeNode(rootval);
int indexroot = getIndex(inorder, rootval);
//len是左子树的长度
int len = indexroot - inleft;
//只定位中序的位置,用长度来表示前序数组该怎么切割
//可以用在preorder中preright参数减去preleft参数等于inorder中inright-inleft的规律
root.left = rebuild(preorder, preleft + 1, preleft + len, inorder, inleft, indexroot - 1);
root.right = rebuild(preorder, preleft + 1 + len, preright, inorder, indexroot + 1, inright);
return root;
}
//根据题意,默认getIndex函数中的参数,value一定会出现在array之中
//其实这个函数的意思是,根据preorder中的根节点的值(对应value参数)找到inorder中这个值对应在inorder中的下标
public int getIndex(int[] array,int value){
int ans = 0;
for(int i = 0;i < array.length;i ++){
if(array[i] == value){
ans = i;
return ans;
}
}
return ans;
}
}
解法2:去掉上面出现的getIndex函数,由于题目中说的,树的节点的val都不相等,也就是preorder和inorder中的数都独一无二,因此可以使用HashMap来存储,key是inorder中的数,value是数对应的下标。具体详见getIndex函数的注释,因为默认preorder中的根一定在inorder中出现,因此使用HashMap的时候不需要考虑key不存在的情况。缺点是空间复杂度变为O(n)
class Solution {
//树中各个节点的值都不相等
//将map作为Solution类的实例变量,那么Solution类的实例方法rebuild(...)是可以访问map的,但是静态方法不能访问到map,因此如果在牛客网笔试编程或者面试的时候写静态方法的时候,要使用静态变量
HashMap<Integer,Integer> map;
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length != inorder.length || (preorder.length == 0)) {
return null;
}else {
map = new HashMap<>();
//求长度:数组对象.length length是数组对象的实例属性
//String对象.length() length()是String对象的一个方法
//与list等有关的(ArrayList,LinkedList,ArrayDeque,Stack等)对象.size()方法
for(int i = 0;i < inorder.length;i ++) {
map.put(inorder[i],i);
}
return rebuild(preorder,0,preorder.length - 1,inorder,0,inorder.length - 1);
}
}
//这个函数的意思是:根据preorder的[preleft,preright]的部分和inorder的[inleft,inright]的部分构建一个二叉树,其中preorder的[preleft,preright]的部分是这个二叉树的前序遍历,inorder的[inleft,inright]的部分是这个二叉树的后序遍历
public TreeNode rebuild(int[] preorder,int preleft,int preright,int[] inorder,int inleft,int inright){
//以下三个if都是递归的边界条件
//与下标有关的,不管是在一维数组中,还是在二维数组中,需要考虑下标越界的问题。
//任何下标越界,肯定返回null
if(inleft < 0 || inright > inorder.length - 1 || preleft < 0 || preright > preorder.length - 1){
return null;
}
//区间[preleft,preright]和[inleft,inright]是合法的
if(preleft > preright || inleft > inright){
return null;
}
//只有一个数,直接返回TreeNode
if(preleft == preright && inleft == inright){
return new TreeNode(preorder[preleft]);
}
//以下是递归的一般条件
//div其实是根节点的值,div可以把中序遍历划分为两个部分:左子树部分和右子树部分
int rootval = preorder[preleft];
TreeNode root = new TreeNode(rootval);
//int indexs1 = getIndex(inorder, rootval);
//默认map中一定包含
int indexroot = map.get(rootval);
//len是左子树的长度
int len = indexroot - inleft;
//只定位中序的位置,用长度来表示前序数组该怎么切割
//可以用在preorder中preright参数减去preleft参数等于inorder中inright-inleft的规律
root.left = rebuild(preorder, preleft + 1, preleft + len, inorder, inleft, indexroot - 1);
root.right = rebuild(preorder, preleft + 1 + len, preright, inorder, indexroot + 1, inright);
return root;
}
/*
//根据题意,默认getIndex函数中的参数,value一定会出现在array之中
//其实这个函数的意思是,根据preorder中的根节点的值(对应value参数)找到inorder中这个值对应在inorder中的下标
public int getIndex(int[] array,int value){
int ans = 0;
for(int i = 0;i < array.length;i ++){
if(array[i] == value){
ans = i;
return ans;
}
}
return ans;
}
*/
}
106. 从中序与后序遍历序列构造二叉树
类似
注意,rebuild函数中的,leftlength用来标识左子树的长度,不可以因为感觉每次都得新申请一个int,把int leftlength设置成实例变量,因为rebuild函数中代码:answer.left = ......;会把leftlength改变!!,导致answer.right = ......;中的leftlength变化。105的注释讲解原因。
//题目中假设数组中没有重复元素
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder.length != postorder.length) {
throw new IllegalArgumentException();
}else if(inorder.length == 0) {
return null;
}else if(inorder.length == 1){
return new TreeNode(inorder[0]);
}else {
return rebuild(inorder,0,inorder.length - 1,postorder,0,postorder.length - 1);
}
}
//这个函数中,inright - inleft === postleft - postright
//这也导致了,如果inright > inleft 那么postright > postleft
//inright < inleft 那么postright < postleft
public TreeNode rebuild(int[] inorder,int inleft,int inright,int[] postorder,int postleft,int postright) {
//if(inleft == inright)
if(inleft == inright || postleft == postright) {
//由上面的注释可以知道,||之后的可以写也可以不写
return new TreeNode(inorder[inleft]);
}else if(inleft > inright || postleft > postright) {
//由上面的注释可以知道,||之后的可以写也可以不写
//if(inleft == inright)
//不能抛异常,如果树没有左子树,那么就会触发这个条件
//用没有左子树或者右子树举个例子
//throw new IllegalArgumentException();
return null;
}else {
TreeNode answer = new TreeNode(postorder[postright]);
//标识左子树的长度
int leftlength = getIndex(inorder,postorder[postright]) - inleft;
//下面举个例子就可以知道坐标的关系
//核心思路还是inright - inleft === postleft - postright
//需要注意的是postorder参与构建左右子树的起止处
answer.left = rebuild(inorder,inleft,inleft + leftlength - 1
,postorder,postleft,postleft + leftlength - 1);
answer.right = rebuild(inorder,inleft + leftlength + 1,inright
,postorder,postleft + leftlength,postright - 1);
return answer;
}
}
public int getIndex(int[] nums,int target) {
for(int i = 0;i < nums.length;i ++) {
if(nums[i] == target) {
return i;
}
}
return -1;
}
}
改进:由于数组中的数字不重复,可以用HashMap存储中序数组数值对应的下标
//题目中假设数组中没有重复元素
class Solution {
HashMap<Integer,Integer> map;
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder.length != postorder.length) {
throw new IllegalArgumentException();
}else if(inorder.length == 0) {
return null;
}else if(inorder.length == 1){
return new TreeNode(inorder[0]);
}else {
map = new HashMap<>();
for(int i = 0; i < inorder.length;i ++) {
map.put(inorder[i],i);
}
return rebuild(inorder,0,inorder.length - 1,postorder,0,postorder.length - 1);
}
}
//这个函数中,inright - inleft === postleft - postright
//这也导致了,如果inright > inleft 那么postright > postleft
//inright < inleft 那么postright < postleft
public TreeNode rebuild(int[] inorder,int inleft,int inright,int[] postorder,int postleft,int postright) {
//if(inleft == inright)
if(inleft == inright || postleft == postright) {
//由上面的注释可以知道,||之后的可以写也可以不写
return new TreeNode(inorder[inleft]);
}else if(inleft > inright || postleft > postright) {
//由上面的注释可以知道,||之后的可以写也可以不写
//if(inleft == inright)
//不能抛异常,如果树没有左子树,那么就会触发这个条件
//用没有左子树或者右子树举个例子
//throw new IllegalArgumentException();
return null;
}else {
TreeNode answer = new TreeNode(postorder[postright]);
//标识左子树的长度
//int leftlength = getIndex(inorder,postorder[postright]) - inleft;
int leftlength = map.get(postorder[postright]) - inleft;
answer.left = rebuild(inorder,inleft,inleft + leftlength - 1
,postorder,postleft,postleft + leftlength - 1);
answer.right = rebuild(inorder,inleft + leftlength + 1,inright
,postorder,postleft + leftlength,postright - 1);
return answer;
}
}
/*
public int getIndex(int[] nums,int target) {
for(int i = 0;i < nums.length;i ++) {
if(nums[i] == target) {
return i;
}
}
return -1;
}
*/
}
889. 根据前序和后序遍历构造二叉树
解法:已知前序和后序,对应的二叉树不止一种,构造出来一个就可以了。https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal/solution/gen-ju-qian-xu-he-hou-xu-bian-li-gou-zao-er-cha-sh/
//返回一种即可
class Solution {
HashMap<Integer,Integer> map;
public TreeNode constructFromPrePost(int[] pre, int[] post) {
if(pre.length == 0) {
return null;
}else if(pre.length == 1) {
return new TreeNode(pre[0]);
}else {
map = new HashMap<>();
for(int i = 0;i < post.length;i ++) {
map.put(post[i],i);
}
return get(pre,0,pre.length - 1,post,0,post.length - 1);
}
}
public TreeNode get(int[] pre,int preleft,int preright,int[] post,int postleft,int postright) {
//if(preleft > preright)
//上下效果都一样
if(preleft > preright || postleft > postright) {
return null;
}
else if(preleft == preright) {
return new TreeNode(pre[preleft]);
}else {
TreeNode answer = new TreeNode(pre[preleft]);
//TreeNode answer = new TreeNode(post[right]);
//post的postleft到pre[preleft + 1]在post的出现位置可以看作左子树
int leftlength = map.get(pre[preleft + 1]) - postleft + 1;
answer.left = get(pre,preleft + 1,preleft + leftlength,post,postleft,postleft + leftlength - 1);
answer.right = get(pre,preleft + leftlength + 1,preright,post,postleft + leftlength,postright - 1);
return answer;
}
}
}
105. 从前序与中序遍历序列构造二叉树
解法:
class Solution {
HashMap<Integer,Integer> map;
//int leftlength;
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length == 0) {
return null;
}else if(preorder.length == 1) {
return new TreeNode(preorder[0]);
}else {
map = new HashMap<>();
for(int i = 0;i < inorder.length;i ++) {
map.put(inorder[i],i);
}
//leftlength = 0;
return get(preorder,0,preorder.length - 1,inorder,0,inorder.length - 1);
}
}
public TreeNode get(int[] preorder,int preleft,int preright,int[] inorder,int inleft,int inright) {
if(preleft == preright) {
return new TreeNode(preorder[preleft]);
}else if(preleft > preright || inleft > inright) {
return null;
}else {
TreeNode answer = new TreeNode(preorder[preleft]);
//leftlength表示左子树的长度
//下面这句不对,应该减去inleft
//int leftlength = map.get(preorder[preleft]) - preleft;
//下面这句就是把leftlength设置成实例变量,如果设置成实例变量由于answer.left = get(...)会改变leftlength的值因此不对
//leftlength = map.get(preorder[preleft]) - inleft;
int leftlength = map.get(preorder[preleft]) - inleft;
answer.left = get(preorder,preleft + 1,preleft + leftlength,inorder,inleft,inleft + leftlength - 1);
//下面这句不对,inorder参数后面不对,举例
//answer.right = get(preorder,preleft + leftlength + 1,preright,inorder,inleft + leftlength,inright);
answer.right = get(preorder,preleft + leftlength + 1,preright,inorder,inleft + leftlength + 1,inright);
return answer;
}
}
}
---------------------------------------------------------------------------------------------------------------------------------------------
114. 二叉树展开为链表
解法:按照题意思路,看注释。其实这种采用哪种遍历(前,中,后)可以根据题目的意思,模拟一下伪代码的流程可以判断出来采用哪种遍历的递归形式
class Solution {
//本题空间复杂度并没有要求,并不是所谓的原地排序
public void flatten(TreeNode root) {
root = pre(root);
}
public TreeNode pre(TreeNode root){
if(root == null || (root != null && root.left == null && root.right == null)){
return root;
}else{
//先保存root的right节点
TreeNode right = root.right;
root.right = pre(root.left);
//得把root的left斩断
root.left = null;
TreeNode next = root;
//并入root的left之后,得找到最右的节点
while(next.right != null){
next = next.right;
}
next.right = pre(right);
return root;
}
}
}
538. 把二叉搜索树转换为累加树
解法:见注释其实就是基于二叉搜索树的中序遍历改进而来的
//二叉搜索树没有重复的关键字,左子树节点的值严格小于根节点的值,根节点的值严格小于右子树节点的值
class Solution {
//中序遍历二叉搜索树(BST),左根右,升序
//很明显本题需要,右根左,降序
//value表示右根左遍历二叉搜索树的时候,累计的值,也就是能表示遍历当前节点之前,大于当前节点val的和。为什么不把函数设计成有返回值的order函数?java的返回值,可以用实例变量代替,个人喜欢递归函数没有返回值,把需要返回的结果存储在实例变量之中即可。
int value;
public TreeNode convertBST(TreeNode root) {
if(root == null) {
return root;
}else {
//赋值与否都可以,因为int型实例变量的默认值是0
//牛客网上使用静态变量的话,得初始化为0,因为静态变量类的实例之间共享,程序测试通过率的时候,可能会new出来多个实例。
value = 0;
order(root);
return root;
}
}
//右根左递归访问BST
public void order(TreeNode node) {
if(node == null) {
return;
}else {
order(node.right);
//value存储的是大于node节点的val的和
node.val += value;
//更新value
value = node.val;
order(node.left);
}
}
}
剑指 Offer 55 - I. 二叉树的深度
解法:简单的DFS,回溯
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) {
return 0;
}else {
return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;
}
}
}
下面
class Solution {
public int maxDepth(TreeNode root) {
return getHeight(root);
}
//getHeight函数的参数是树根节点,返回值是树根节点表示的二叉树的高度
public int getHeight(TreeNode current) {
//递归的边界条件
if(current == null) {
return 0;
}else {
//以下是递归的一般条件
//left和right分别是current左右子树的高度
int left = getHeight(current.left);
int right = getHeight(current.right);
//根据题意,非空二叉树的高度等于左子树右子树高度的最大值加一
return Math.max(left,right) + 1;
}
}
}
这是基于回溯法改的,回溯类似于深度优先搜索遍历,可以在写回溯题目的时候看看
class Solution {
int answer;
public int maxDepth(TreeNode root) {
if(root == null) {
return answer;
}else {
int length = 0;
backtrack(length,root);
return answer;
}
}
public void backtrack(int length,TreeNode current) {
if(current == null) {
}else {
length ++;
//树的回溯是加入之后,立刻判断
//下面判断current是否是叶子节点
if(current.left == null && current.right == null) {
answer = Math.max(length,answer);
}
backtrack(length,current.left);
backtrack(length,current.right);
length --;
}
}
}
543. 二叉树的直径
解法:是剑指 Offer 55 - I. 二叉树的深度的升级版。如有题目:求二叉树的最大宽度,可以使用改进的层次遍历(while循环里增加一个for循环),求二叉树的最大宽度其实就是求层次遍历的过程中queue.size()的最大值
class Solution {
//length保存的是题目中描述的二叉树的根节点的左子树的高度加上右子树的高度加1
int length = 0;
public int diameterOfBinaryTree(TreeNode root) {
if(root == null) {
return 0;
}else {
//oneside函数有返回值,但是不接收是可以的
getHeight(root);
//返回length不对,因为求的直径其实是边数
//比如给定的例子,运行之后length=4
//return length;
return length - 1;
}
}
//getHeight函数的参数是树的根节点,root节点代表的树的高度
//这个递归函数是根据判断二叉树的最大深度改进而来
public int getHeight(TreeNode root) {
if(root == null) {
return 0;
}else {
int left = getHeight(root.left);
int right = getHeight(root.right);
//要是下面这样结果是0
//length = Math.max(length,left + right);
//return Math.max(left,right);
//由于length是root节点左子树的高度加上右子树的高度加一,root相对于它的左右子树在更高的一层,这也是为什么下面两行都得加1的原因
length = Math.max(length,left + right + 1);
//由于getHeight函数返回的是树的高度,因此返回的是下面的
return Math.max(left,right) + 1;
}
}
}
剑指 Offer 55 - II. 平衡二叉树
解法:是剑指 Offer 55 - I. 二叉树的深度的升级版本
class Solution {
public boolean isBalanced(TreeNode root) {
if(root == null) {
return true;
}else {
//return oneside(root) == -1;
return getHeight(root) != -1;
}
}
//getHeight函数返回的不是-1的话是current的高度,如果返回-1证明current不是平衡二叉树
public int getHeight(TreeNode current) {
if(current == null) {
return 0;
}else {
int left = getHeight(current.left);
int right = getHeight(current.right);
//如果leftright有一个等于-1证明current节点的左右子树失衡
if(left == -1 || right == -1) {
return -1;
}
//current的左右子树高度差的绝对值大于1,返回-1证明失衡
if(Math.abs(left - right) > 1) {
return -1;
}
//返回current的树高,注意要加一
return Math.max(left,right) + 1;
//map.put(current,height);
}
}
}
另外一个思路:用HashMap存储树的高度,key就是一个树的节点,value是以该节点为根的树的高度,缺点是空间复杂度比较高,时间复杂度变大,没有任何优点,但是是一个思路。主要原因是存储在HashMap中的信息存储之后,不会再使用(因为是遍历一次二叉树),也就是遍历两边二叉树题目的题需要用到HashMap,后面会出现。
class Solution {
boolean answer;
HashMap<TreeNode,Integer> map;
public boolean isBalanced(TreeNode root) {
if(root == null) {
return true;
}else {
answer = true;
map = new HashMap<>();
oneside(root);
return answer;
}
}
public int oneside(TreeNode current) {
if(! answer) {
return -1;
}else if(current == null) {
return 0;
}else if(map.containsKey(current)) {
return map.get(current);
}else {
int left = oneside(current.left);
int right = oneside(current.right);
if(Math.abs(left - right) > 1) {
answer = false;
}
int height = Math.max(left,right) + 1;
map.put(current,height);
return height;
}
}
}
解法:反而更快一点,其实还是之前getHeight函数的改进。剑指 Offer 55 - I. 二叉树的深度 剑指 Offer 55 - II. 平衡二叉树
class Solution {
//flag表示题目所给的树是不是平衡二叉树,其值为false就表示不是平衡二叉树
boolean flag = true;
public boolean isBalanced(TreeNode root) {
if(root == null) {
return true;
}else {
//最终目的不是为了获得dp函数的返回值,因此直接调用即可
dp(root);
return flag;
}
}
//dp函数返回的数组,第一个元素表示左子树的高
//第二个元素表示右子树的高
public int[] dp(TreeNode root) {
int[] answer = new int[2];
//root为空直接返回
//flag为false,表示题目中的树的子树已经失衡,直接返回即可
if(root == null || flag == false) {
return answer;
}else {
int[] left = dp(root.left);
int[] right = dp(root.right);
if(get(left[0],left[1]) > 1 || get(right[0],right[1]) > 1){
flag = false;
}
answer[0] = Math.max(left[0],left[1]) + 1;
answer[1] = Math.max(right[0],right[1]) + 1;
if(get(answer[0],answer[1]) > 1){
flag = false;
}
return answer;
}
}
public int get(int a,int b){
return Math.abs(a - b);
}
}
剑指 Offer 36. 二叉搜索树与双向链表
解法1:中序遍历二叉搜索树的时候,记录当前遍历节点的前一个节点。
java里函数的参数可以用实例变量代替,返回结果也可以放到实例变量中去,这里使用的first,pre都放在实例变量中的。
class Solution {
//first节点指向二叉搜索树中序遍历的第一个节点
Node first;
//int counter = 0;
//pre指向BST(二叉搜索树)的中序遍历当前节点的前一个节点
//中序遍历结束之后,pre指向二叉树中序遍历最后一个节点
Node pre;
public Node treeToDoublyList(Node root) {
if(root == null) {
return null;
}
inorder(root);
first.left = pre;
pre.right = first;
//之前写成reurn root这是不对的,因为要返回的是一个升序链表的头节点,应该返回first节点
return first;
}
public void inorder(Node root) {
if(root == null) {
return;
}else {
//中序遍历
inorder(root.left);
if(pre != null) {
pre.right = root;
}else {
//如果pre为空,那么中序遍历的第一个节点必然是root,只有第一个节点的pre为空
first = root;
}
//修改root的left
root.left = pre;
//更新pre
pre = root;
inorder(root.right);
}
}
}
112. 路径总和
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null && root.val == targetSum) {
return true;
}
return hasPathSum (root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
}
}
404. 左叶子之和
解法:递归
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if(root == null) {
return 0;
}else {
int left = 0;
//如果root的左子树就有一个节点,那么left = root.left.val,否则递归求解left
if(root.left != null && root.left.right == null && root.left.left == null) {
left = root.left.val;
}else {
left = sumOfLeftLeaves(root.left);
}
int right = sumOfLeftLeaves(root.right);
return left + right;
}
}
}
下面是最开始不对的写法
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if(root == null) {
return 0;
//这是不对的,以例子为例,只会访问3节点的左子树,因为是elseif和else的关系
}else if(root.left != null && root.left.right == null && root.left.left == null){
return root.left.val;
}else {
int left = sumOfLeftLeaves(root.left);
int right = sumOfLeftLeaves(root.right);
return left + right;
}
}
}
617. 合并二叉树
解法:见注释。
class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
//最后将t1返回
if(t1 == null || t2 == null) {
return t1 == null ? t2 : t1;
}else {
//节点的值相加
t1.val += t2.val;
t1.left = mergeTrees(t1.left,t2.left);
t1.right = mergeTrees(t1.right,t2.right);
return t1;
}
}
}
501. 二叉搜索树中的众数
解法:
//找出 BST 中的所有众数
class Solution {
ArrayList<Integer> list = new ArrayList<>();
//表示最大的出现次数
int max;
//表示当前的出现次数
int current;
TreeNode pre;
public int[] findMode(TreeNode root) {
if(root == null) {
return new int[0];
}else {
max = Integer.MIN_VALUE;
//下面两个可以省略,因为它们的默认值就是0,null
current = 0;
pre = null;
findMax(root);
//执行过findmax之后,pre的值,current的值已经发生变化需要重新变成null,0
current = 0;
pre = null;
findAndFill(root);
int[] answer = new int[list.size()];
for(int i = 0;i < list.size();i ++) {
answer[i] = list.get(i);
}
return answer;
}
}
//中序遍历得到出现次数的最大值
//中序遍历是有序的,如果一个节点的值和中序遍历顺序下上一个节点的值相等,那么这个节点的值的出现次数加1
public void findMax(TreeNode node) {
if(node == null) {
return;
}else {
findMax(node.left);
if(pre != null && pre.val == node.val) {
current ++;
}else {
//else情况指的是pre为空,那么node是中序遍历情况下的第一个节点,遍历到node时,node.val出现的次数是1
//或者中序遍历的情况下,pre.val != node.val
//由于中序遍历BST得到的是有序的序列,那么遍历截止到node,node.val出现的次数也是1
current = 1;
}
max = Math.max(current,max);
//更新pre
pre = node;
findMax(node.right);
}
}
public void findAndFill(TreeNode node) {
if(node == null) {
return;
}else {
findAndFill(node.left);
if(pre != null && pre.val == node.val) {
current ++;
}else {
current = 1;
}
if(current == max) {
list.add(node.val);
}
//更新pre
pre = node;
findAndFill(node.right);
}
}
}
数组
剑指 Offer 57. 和为s的两个数字
解法:对撞指针,时间复杂度是O(n);使用HashMap的时间复杂度虽然也是O(n),但是空间复杂度是O(n);
对于nums[i],在区间[i + 1,nums.length - 1]使用二分查找,时间复杂度是O(nlogn)。
class Solution {
//nums已经有序,输出任意一对即可
//双指针时间复杂度是O(n),暴力时间复杂度是O(n^2)
public int[] twoSum(int[] nums, int target) {
int[] answer = new int[2];
if(nums.length <= 1 || nums[0] >= target) {
return answer;
}else {
int left = 0;
int right = nums.length - 1;
int tem = 0;
//不能是left <= right,如果nums[left] * 2 == target,不符合题意
while(left < right) {
tem = nums[left] + nums[right];
if(tem == target) {
answer[0] = nums[left];
answer[1] = nums[right];
return answer;
}else if(tem < target) {
left ++;
}else {
right --;
}
}
return answer;
}
}
}
剑指 Offer 57 - II. 和为s的连续正数序列
解法:双指针,类似于上题,leftright的初始值分别是1,2;
区间[left,right]之内的和要是等于target,符合题意;要是大于target,那么增大left,为什么不减小right?首先会重复,其次leftright的初始值是1和2,应该向右遍历;要是小于target,增大right,为什么不减小left原因同上。
具体见注释
class Solution {
public int[][] findContinuousSequence(int target) {
List<int[]> answer = new ArrayList<>();
if(target <= 0) {
return null;
}else {
int left = 1;
int right = 2;
int currentsum = left + right;
//当small大于stopflag的时候,区间[left,right]的和肯定大于target
int stopflag = (target + 1) / 2;
while(left < stopflag) {
if(currentsum == target) {
//每个list都得新new
int[] array = new int[right - left + 1];
for(int i = left;i <= right;i ++) {
array[i - left] = i;
}
answer.add(array);
//相等的时候,一定要移动left或者right,要不然程序一直循环
//移动left,right都可以
/*
right ++;
currentsum += right;
*/
currentsum -= left;
left ++;
}
//下面两个的while改成else if也可以,不过while更快
while(currentsum < target) {
right ++;
currentsum += right;
}
while(currentsum > target && left < stopflag) {
//这个得先减去
currentsum -= left;
left ++;
}
}
return answer.toArray(new int[answer.size()][]);
}
}
}
108. 将有序数组转换为二叉搜索树
要求的是有序数组转化为平衡二叉树
解法:平衡二叉树(高度平衡的二叉搜索树的中序遍历是有序的),其实自己模拟一下nums数组的长度分别是1,2,3就有思路了
class Solution {
//要求被转化为高度平衡的二叉搜索树也就是平衡二叉树
//思考:nums数组有两个数,把哪个当成根节点都行
//就约定一直把(left + right) / 2当作根节点
public TreeNode sortedArrayToBST(int[] nums) {
if(nums.length == 0) {
return null;
}else if(nums.length == 1) {
return new TreeNode(nums[0]);
}else {
return array2BST(nums,0,nums.length - 1);
}
}
//为什么要判断left > right的情况呢,假设原来的数组就是{1,2},进入下面的函数之后mid = 0,再进一步调用array2BST(nums,left,mid - 1);mid - 1 = -1
public TreeNode array2BST(int[] nums,int left,int right) {
if(left == right) {
return new TreeNode(nums[left]);
}else if(left > right) {
return null;
}else {
int mid = left + (right - left) / 2;
TreeNode answer = new TreeNode(nums[mid]);
answer.left = array2BST(nums,left,mid - 1);
answer.right = array2BST(nums,mid + 1,right);
return answer;
}
}
}
------------------------------------------------------------------------------------
下面是三个题重复的
106. 从中序与后序遍历序列构造二叉树
类似
注意,rebuild函数中的,leftlength用来标识左子树的长度,不可以因为感觉每次都得新申请一个int,把int leftlength设置成实例变量,因为rebuild函数中代码:answer.left = ......;会把leftlength改变!!,导致answer.right = ......;中的leftlength变化。105的注释讲解原因。
//题目中假设数组中没有重复元素
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder.length != postorder.length) {
throw new IllegalArgumentException();
}else if(inorder.length == 0) {
return null;
}else if(inorder.length == 1){
return new TreeNode(inorder[0]);
}else {
return rebuild(inorder,0,inorder.length - 1,postorder,0,postorder.length - 1);
}
}
//这个函数中,inright - inleft === postleft - postright
//这也导致了,如果inright > inleft 那么postright > postleft
//inright < inleft 那么postright < postleft
public TreeNode rebuild(int[] inorder,int inleft,int inright,int[] postorder,int postleft,int postright) {
//if(inleft == inright)
if(inleft == inright || postleft == postright) {
//由上面的注释可以知道,||之后的可以写也可以不写
return new TreeNode(inorder[inleft]);
}else if(inleft > inright || postleft > postright) {
//由上面的注释可以知道,||之后的可以写也可以不写
//if(inleft == inright)
//不能抛异常,如果树没有左子树,那么就会触发这个条件
//用没有左子树或者右子树举个例子
//throw new IllegalArgumentException();
return null;
}else {
TreeNode answer = new TreeNode(postorder[postright]);
//标识左子树的长度
int leftlength = getIndex(inorder,postorder[postright]) - inleft;
//下面举个例子就可以知道坐标的关系
//核心思路还是inright - inleft === postleft - postright
//需要注意的是postorder参与构建左右子树的起止处
answer.left = rebuild(inorder,inleft,inleft + leftlength - 1
,postorder,postleft,postleft + leftlength - 1);
answer.right = rebuild(inorder,inleft + leftlength + 1,inright
,postorder,postleft + leftlength,postright - 1);
return answer;
}
}
public int getIndex(int[] nums,int target) {
for(int i = 0;i < nums.length;i ++) {
if(nums[i] == target) {
return i;
}
}
return -1;
}
}
改进:由于数组中的数字不重复,可以用HashMap存储中序数组数值对应的下标
//题目中假设数组中没有重复元素
class Solution {
HashMap<Integer,Integer> map;
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder.length != postorder.length) {
throw new IllegalArgumentException();
}else if(inorder.length == 0) {
return null;
}else if(inorder.length == 1){
return new TreeNode(inorder[0]);
}else {
map = new HashMap<>();
for(int i = 0; i < inorder.length;i ++) {
map.put(inorder[i],i);
}
return rebuild(inorder,0,inorder.length - 1,postorder,0,postorder.length - 1);
}
}
//这个函数中,inright - inleft === postleft - postright
//这也导致了,如果inright > inleft 那么postright > postleft
//inright < inleft 那么postright < postleft
public TreeNode rebuild(int[] inorder,int inleft,int inright,int[] postorder,int postleft,int postright) {
//if(inleft == inright)
if(inleft == inright || postleft == postright) {
//由上面的注释可以知道,||之后的可以写也可以不写
return new TreeNode(inorder[inleft]);
}else if(inleft > inright || postleft > postright) {
//由上面的注释可以知道,||之后的可以写也可以不写
//if(inleft == inright)
//不能抛异常,如果树没有左子树,那么就会触发这个条件
//用没有左子树或者右子树举个例子
//throw new IllegalArgumentException();
return null;
}else {
TreeNode answer = new TreeNode(postorder[postright]);
//标识左子树的长度
//int leftlength = getIndex(inorder,postorder[postright]) - inleft;
int leftlength = map.get(postorder[postright]) - inleft;
answer.left = rebuild(inorder,inleft,inleft + leftlength - 1
,postorder,postleft,postleft + leftlength - 1);
answer.right = rebuild(inorder,inleft + leftlength + 1,inright
,postorder,postleft + leftlength,postright - 1);
return answer;
}
}
/*
public int getIndex(int[] nums,int target) {
for(int i = 0;i < nums.length;i ++) {
if(nums[i] == target) {
return i;
}
}
return -1;
}
*/
}
889. 根据前序和后序遍历构造二叉树
//返回一种即可
class Solution {
HashMap<Integer,Integer> map;
public TreeNode constructFromPrePost(int[] pre, int[] post) {
if(pre.length == 0) {
return null;
}else if(pre.length == 1) {
return new TreeNode(pre[0]);
}else {
map = new HashMap<>();
for(int i = 0;i < post.length;i ++) {
map.put(post[i],i);
}
return get(pre,0,pre.length - 1,post,0,post.length - 1);
}
}
public TreeNode get(int[] pre,int preleft,int preright,int[] post,int postleft,int postright) {
//if(preleft > preright)
//上下效果都一样
if(preleft > preright || postleft > postright) {
return null;
}
else if(preleft == preright) {
return new TreeNode(pre[preleft]);
}else {
TreeNode answer = new TreeNode(pre[preleft]);
//TreeNode answer = new TreeNode(post[right]);
//post的postleft到pre[preleft + 1]在post的出现位置可以看作左子树
int leftlength = map.get(pre[preleft + 1]) - postleft + 1;
answer.left = get(pre,preleft + 1,preleft + leftlength,post,postleft,postleft + leftlength - 1);
answer.right = get(pre,preleft + leftlength + 1,preright,post,postleft + leftlength,postright - 1);
return answer;
}
}
}
105. 从前序与中序遍历序列构造二叉树
解法:
class Solution {
HashMap<Integer,Integer> map;
//int leftlength;
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length == 0) {
return null;
}else if(preorder.length == 1) {
return new TreeNode(preorder[0]);
}else {
map = new HashMap<>();
for(int i = 0;i < inorder.length;i ++) {
map.put(inorder[i],i);
}
//leftlength = 0;
return get(preorder,0,preorder.length - 1,inorder,0,inorder.length - 1);
}
}
public TreeNode get(int[] preorder,int preleft,int preright,int[] inorder,int inleft,int inright) {
if(preleft == preright) {
return new TreeNode(preorder[preleft]);
}else if(preleft > preright || inleft > inright) {
return null;
}else {
TreeNode answer = new TreeNode(preorder[preleft]);
//leftlength表示左子树的长度
//下面这句不对,应该减去inleft
//int leftlength = map.get(preorder[preleft]) - preleft;
//下面这句就是把leftlength设置成实例变量,如果设置成实例变量由于answer.left = get(...)会改变leftlength的值因此不对
//leftlength = map.get(preorder[preleft]) - inleft;
int leftlength = map.get(preorder[preleft]) - inleft;
answer.left = get(preorder,preleft + 1,preleft + leftlength,inorder,inleft,inleft + leftlength - 1);
//下面这句不对,inorder参数后面不对,举例
//answer.right = get(preorder,preleft + leftlength + 1,preright,inorder,inleft + leftlength,inright);
answer.right = get(preorder,preleft + leftlength + 1,preright,inorder,inleft + leftlength + 1,inright);
return answer;
}
}
}
-------------------------------------------------------------------------------------------------------------------------------------------------
剑指 Offer 54. 二叉搜索树的第k大节点
解法:反中序遍历
class Solution {
//1 ≤ k ≤ 二叉搜索树元素个数
//二叉搜索树的中序遍历有序
int answer;
int k;
int current;
public int kthLargest(TreeNode root, int k) {
this.k = k;
current = 0;
order(root);
return answer;
}
//因为求得是第K大的数,因此要反中序遍历
//current代表当前遍历到第几个节点
public void order(TreeNode node) {
//如果遍历的大于current不需要继续遍历
if(current < k) {
if(node == null) {
return;
}
order(node.right);
current ++;
if(current == k) {
answer = node.val;
return;
}
order(node.left);
}
}
}
下面不行,因为java是这样current的值,不会被改变 ,java只有传递对象,数组等才可以改变对象的属性、数组的值
class Solution {
//1 ≤ k ≤ 二叉搜索树元素个数
//二叉搜索树的中序遍历有序
int answer;
public int kthLargest(TreeNode root, int k) {
order(root,0,k);
return answer;
}
//因为求得是第K大的数,因此要反中序遍历
//current代表当前遍历到第几个节点
public void order(TreeNode node,int current,int k) {
//如果遍历的大于current不需要继续遍历
if(current < k) {
if(node == null) {
return;
}
order(node.right,current,k);
current ++;
if(current == k) {
answer = node.val;
return;
}
order(node.left,current,k);
}
}
}
226. 翻转二叉树
解法:把root的左右子树反转之后,再把左子树反转的返回赋值给root的right;右子树反转之后的结果赋值给root的left。
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) {
return root;
}else {
TreeNode newright = invertTree(root.left);
TreeNode newleft = invertTree(root.right);
root.left = newleft;
root.right = newright;
return root;
}
}
}
数组
15. 三数之和
注意超时问题:当前三数字之和等于0的时候,left要加上1并且right要减去1,因为例如,只让left自增1,那么如果nums[index]、nums[left]、nums[right]之和为0,只能证明left自增1前后值不变,也就是答案是重复的,题意不要求有重复答案
while(left < right && nums[left] == nums[left + 1]) {
left ++;
}
left ++;
相当于下面的:
认真分析一下,循环执行之后,left至少加上1,上面的循环加上left ++ 一句才可以保证left至少加上1
while(left < right && nums[left] == nums[++ left]) {};或者while(left < right && nums[left] == nums[++ left]);
class Solution {
//回溯也可以,但是没有这种方法简单
//答案中不包括重复的三元组,并且数组的每个元素只能用一次
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> answer = new ArrayList<>();
if(nums.length < 3) {
return answer;
}else {
Arrays.sort(nums);
int index = 0;
int left = 0;
int right = 0;
int num = 0;
//边界条件是<nums.length也行
for(index = 0;index < nums.length - 2;index ++) {
//System.out.println(index);
//数组排序之后,nums[left],nums[right]都比nums[index大]
if(nums[index] > 0) {
continue;
}
//去重
if(index > 0 && nums[index] == nums[index - 1]) {
continue;
}
left = index + 1;
right = nums.length - 1;
//不是left <= right,因为等于就不是三个数相加了
while(left < right) {
num = nums[index] + nums[left] + nums[right];
if(num == 0) {
ArrayList<Integer> list = new ArrayList<>();
list.add(nums[index]);
list.add(nums[left]);
list.add(nums[right]);
answer.add(list);
//去重
while(left < right && nums[left] == nums[left + 1]) {
left ++;
}
left ++;
//去重
while(left < right && nums[right] == nums[-- right]) {
}
}else if(num < 0) {
//收缩的快一点
/*
while(left < right && nums[left] == nums[left + 1]) {
left ++;
}
left ++;
*/
left ++;
}else {
//以下三个都是可以的
/*
while(left < right && nums[right] == nums[right - 1]) {
right --;
}
right --;
*/
/*
while(left < right && nums[right] == nums[-- right]) {
}
*/
right --;
}
}
}
return answer;
}
}
}
344. 反转字符串
解法:两个指针向中间移动,需要注意的是while循环里面经常忘记写left ++;right --之类的造成死循环。
class Solution {
public void reverseString(char[] s) {
if(s.length <= 1) {
return;
}else {
int left = 0;
int right = s.length - 1;
char tem = '0';
while(left < right) {
tem = s[left];
s[left] = s[right];
s[right] = tem;
left ++;
right --;
}
}
}
}
349. 两个数组的交集
解法:使用HashMap,时间复杂度O(n)空间复杂度O(n)
class Solution {
//输出结果中的每个元素一定是唯一的。
//我们可以不考虑输出结果的顺序
public int[] intersection(int[] nums1, int[] nums2) {
ArrayList<Integer> list = new ArrayList<>();
Map<Integer, Integer> map = new HashMap<>();
//map中的key是nums1数组中数,value都是1代表出现一次,去重
for (int num : nums1) {
if(! map.containsKey(num)) {
map.put(num,1);
}
}
for (int num : nums2) {
if (map.containsKey(num) && map.get(num) > 0) {
list.add(num);
map.put(num, 0);
}
}
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
res[i] = list.get(i);
}
return res;
}
}
剑指 Offer 04. 二维数组中的查找
解法:见注释
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
//1.判断二维数组是否为空的方法
if(matrix.length == 0 || (matrix.length == 1 && matrix[0].length == 0)) {
return false;
}else {
//2.根据题目描述的二维数组的特点,每次减少一行或者一列
//3.终止条件是row或者col不合法
int row = 0;
int col = matrix[0].length - 1;
while(row < matrix.length && col >= 0) {
if(matrix[row][col] == target) {
return true;
}else if(matrix[row][col] > target) {
col --;
}else {
row ++;
}
}
return false;
}
}
}
与堆有关的问题,前K大,构建容量为K的小顶堆,前K小,构建容量为K的大顶堆
面试题 17.14. 最小K个数
美团面试的时候,写随机快排,quicksort函数的quicksort(array,left,index - 1)写错成quicksort(array,left,index)导致stackoverflowerror
解法1:快排
import java.util.*;
import java.lang.*;
class Solution {
public int findKthLargest(int[] nums, int k) {
quicksort(nums);
return nums[nums.length - k];
}
public void quicksort(int[] nums) {
if(nums.length <= 1) {
return;
}else {
quick(nums,0,nums.length - 1);
}
}
public void quick(int[] nums,int left,int right) {
if(left < right) {
int index = partition(nums,left,right);
//1.quick(nums,left,index - 1);而不是quick(nums,left,index);
//因为[left,index - 1]都小于哨兵,[index + 1,right]都大于哨兵
quick(nums,left,index - 1);
quick(nums,index + 1,right);
}
}
public int randomPartition(int[] nums,int left,int right) {
Random random = new Random();
int flag = random.nextInt(right - left + 1) + left;
int privot = nums[flag];
int index = left - 1;
swap(nums,flag,right);
for(int i = left;i <= right;i ++) {
if(nums[i] <= privot) {
index ++;
swap(nums,i,index);
}
}
return index;
}
//
public int partition(int[] nums,int left,int right) {
int privot = nums[left];
//2.确定哨兵之后,和nums[right]交换是必须的
//把nums[left]的值,放到最右侧,就是为了算法结束之后,nums[left]能把数组分成两部分,一部分是小于等于nums[left]的,另一部分是大于nums[left]的
swap(nums,left,right);
//index的意思是区间[left,index]都小于等于哨兵,因此index的初始值是left - 1,并当nums[i]小于哨兵,先让index增加
int index = left - 1;
for(int i = left;i <= right;i ++) {
if(nums[i] <= privot) {
index ++;
swap(nums,i,index);
}
}
return index;
}
public void swap(int[] nums,int index1,int index2) {
int tem = nums[index1];
nums[index1] = nums[index2];
nums[index2] = tem;
}
}
剑指 Offer 40. 最小的k个数
解法:建立容量为k的大顶堆
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k == 0) {
return new int[0];
}else if(k >= arr.length) {
return arr;
}else {
int[] heap = new int[k];
for(int i = 0;i < k;i ++) {
heap[i] = arr[i];
}
//1.对于数组heap要建大顶堆堆
buildMaxHeap(heap,k);
for(int i = k;i < arr.length;i ++) {
//2.heap[0]是大顶堆最大的元素,如果arr[i]小于heap[0],那么arr[i]就把heap[0]替换,从0开始调整堆,注意这里不是建堆
if(arr[i] < heap[0]) {
heap[0] = arr[i];
heapify(heap,0,k);
}
}
return heap;
}
}
//寻找前k个最小的数,用大顶堆。调整容量为k的大顶堆,k就是大顶堆的容量
public void buildMaxHeap(int[] heap,int k) {
//大顶堆heap[0]是最大的
//3.建堆应该自底向上,不应该自上而下,因此下面的for循环是不对的
/*
for(int i = 0;i <= (k - 1) / 2;i ++) {
heapify(heap,i,k);
}
*/
for(int i = (k - 1) / 2;i >= 0;i --) {
heapify(heap,i,k);
}
}
//4.调整heap[index]的左节点heap[2 * index + 1]和右节点heap[2 * index + 2],符合大顶堆的定义
public void heapify(int[] heap,int index,int k) {
if(index >= k) {
return;
}else {
int leftindex = 2 * index + 1;
int rightindex = 2 * index + 2;
int maxindex = index;
if(leftindex < k && heap[maxindex] < heap[leftindex]) {
maxindex = leftindex;
}
if(rightindex < k && heap[maxindex] < heap[rightindex]) {
maxindex = rightindex;
}
if(maxindex != index) {
int tem = heap[index];
heap[index] = heap[maxindex];
heap[maxindex] = tem;
//如果index和它的左节点或者右节点交换,应该考虑heap[index]被交换到heap[maxindex]会不会继续引发堆调整
heapify(heap,maxindex,k);
}
}
}
}
347. 前 K 个高频元素
解法:前K个出现最多的,,构建一个大小为K的小顶堆
class Solution {
//可以按任意顺序返回答案
//时间复杂度必须优于 O(n log n)
//前k个高频的,构建小顶堆
HashMap<Integer,Integer> map;
public int[] topKFrequent(int[] nums, int k) {
map = new HashMap<>();
//LinkedList<Integer> list = new LinkedList<>();
if(nums.length == 0 || k == 0) {
return new int[0];
}else {
for(int i = 0;i < nums.length;i ++) {
if(map.containsKey(nums[i])) {
map.put(nums[i],map.get(nums[i]) + 1);
}else {
map.put(nums[i],1);
}
}
//keys是去重的nums元素
int[] keys = new int[map.keySet().size()];
int index = 0;
for(int key : map.keySet()) {
keys[index ++] = key;
//System.out.println(index);
}
//答案不要求数组元素的顺序,如果k大于等于keys的长度,可以直接返回key
if(k >= map.keySet().size()) {
return keys;
}else {
int[] answer = new int[k];
//1.把answer作为小顶堆,先填充数据,然后进行建堆
for(int i = 0;i < k;i ++) {
answer[i] = keys[i];
}
//2.自下而上建小顶堆
buildHeap(answer,k);
for(int i = k;i < keys.length;i ++) {
if(map.get(keys[i]) > map.get(answer[0])) {
//3.替换堆顶元素,引发堆调整
answer[0] = keys[i];
heapify(answer,0);
}
}
return answer;
}
}
}
//4.建堆函数,自下而上调整,详见数据结构,不能自上而下,也就是不能for(i = 0;i < length;i ++){code}
public void buildHeap(int[] heap,int length) {
//把i --写成i ++导致错误
//i建议从length开始,方便记忆
//当然也可以从(length - 1) / 2 + 1开始,因为这个节点对应的左子树或者右子树是heap数组的最后一个节点
/*
for(int i = length;i >=0;i --) {
heapify(heap,i);
}
*/
for(int i = (length - 1) / 2 + 1;i >=0;i --) {
heapify(heap,i);
}
}
//5.堆调整函数
//调整heap[index]的左右子节点heap[2*index + 1],heap[2*index + 2],依据是他们出现的次数
public void heapify(int[] heap,int index) {
if(index >= heap.length) {
return;
}else {
int left = 2 * index + 1;
int right = 2 * index + 2;
int min = index;
if(left < heap.length && map.get(heap[min]) > map.get(heap[left])) {
min = left;
}
if(right < heap.length && map.get(heap[min]) > map.get(heap[right])) {
min = right;
}
//小顶堆比较的依据不是元素的大小而是出现的次数
/*
if(right < heap.length && heap[min] > heap[right]) {
min = right;
}
*/
if(min != index) {
int tem = heap[index];
heap[index] = heap[min];
heap[min] = tem;
//index和子节点交换之后,可能会触发新一轮的交换,因此要调用一下,这个时候,heap[min]保存的是之前heap[index]
heapify(heap,min);
}
}
}
}
剑指 Offer 58 - I. 翻转单词顺序
解法:一般问递归的解法。
class Solution {
StringBuilder sb = new StringBuilder();
public String reverseWords(String s) {
if(s.length() == 0) {
return "";
}else {
reverse(s,0,false);
return sb.toString();
}
}
//递归函数不设置返回值,把需要返回的数据添加到实例变量之中
//index表示从s的index开始寻找下一个单词
//flag表示添加完单词后面是否加空格,往sb添加原字符串第一个单词不加空格,因此reverseWords函数调用的时候,flag是false
public void reverse(String s,int index,boolean flag) {
//递归返回条件
if(index >= s.length()) {
return;
}else {
int start = index;
//找到单词的开始下标
while(start < s.length() && s.charAt(start) == ' ') {
start ++;
}
//找不到start直接返回,要是不返回," hello world! "
//结果是" world! hello"
if(start >= s.length()) {
return;
}
int end = start;
while(end < s.length() && s.charAt(end) != ' ') {
end ++;
}
//因为是逆序,类似于后序遍历二叉树,先递归,在添加当前单词
reverse(s,end,true);
sb.append(s.substring(start,end));
if(flag) {
sb.append(' ');
}
//return s.substring(start,end);
}
}
}
104. 二叉树的最大深度
解法:
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) {
return 0;
}else {
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return 1 + Math.max(left,right);
}
}
}
88. 合并两个有序数组
解法:时间复杂度O(n)空间复杂度O(1)的方法是从后往前使用双指针
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int index1 = m - 1;
int index2 = n - 1;
int index = m + n - 1;
int number1 = 0;
int number2 = 0;
while(index1 >= 0 || index2 >= 0) {
number1 = index1 >= 0 ? nums1[index1] : Integer.MIN_VALUE;
number2 = index2 >= 0 ? nums2[index2] : Integer.MIN_VALUE;
if(number1 >= number2) {
nums1[index] = number1;
index --;
index1 --;
}else {
nums1[index] = number2;
index --;
index2 --;
}
}
}
}