数据结构刷题
刷题
二叉树
综上,遇到一道二叉树的题目时的通用思考过程是:
1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse 函数配合外部变量来实现。
2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值。
3、无论使用哪一种思维模式,你都要明白二叉树的每一个节点需要做什么,需要在什么时候(前中后序)做。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
Leetcode-94:二叉树的中序遍历
使用的栈实现二叉树的中序遍历操作
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stack=new Stack();
List<Integer> result=new ArrayList<>();
if(root==null) return result;
TreeNode cur=root;
while(cur!=null || !stack.isEmpty()){//这里为什么要cur!=null? 因为栈一开始为空无法进入循环
if(cur!=null){
stack.push(cur);
cur=cur.left;//如果指针指向的左节点不为空,则一直往左节点指
}else{
cur = stack.pop();//这里要注意就是要让cur指向stack.pop()的返回值,否则cur会空指针异常,也是让指针上移
result.add(cur.val);
cur = cur.right;//指针往右节点指
}
}
return result;
}
}
Leetcode-543:二叉树的直径
求二叉树最大深度的变式,也是理解二叉树中后序位置的含义,也就是二叉树分解问题的解题思路
class Solution {
// 记录最大直径的长度
int maxDiameter = 0;
public int diameterOfBinaryTree(TreeNode root) {
diameter(root);
return maxDiameter;
}
//递归函数:求二叉树最大深度的同时求二叉树的最大直径
private int diameter(TreeNode root){
if(root==null) return 0;
int left=diameter(root.left);
int right=diameter(root.right);
//后序位置
int maxlength=left+right;//左子树节点个数+右子树节点个数
maxDiameter=Math.max(maxDiameter,maxlength);
return 1+Math.max(left,right);//算出左右子树最大深度并返回
}
}
Leetcode-226:翻转二叉树
就像开头所说的,二叉树的题目,两个大方向,是否可以遍历整颗二叉树得到答案,另一种就是通过子树(子问题)解决推导出原问题的答案。
并且还有思考单个节点需要做什么:单独抽出一个节点,需要让它做什么?让它把自己的左右子节点交换一下。
第一种方向的解法:
class Solution {
public TreeNode invertTree(TreeNode root) {
invert(root);
return root;
}
//定义一个递归函数:遍历整个二叉树,让每个节点的左右子树左右调换
public void invert(TreeNode root){
if(root==null) return;
//前序位置操作,交换该节点的左子树和右子树,注意这里是交换整颗子树,不是单个节点(后序位置也可以)
TreeNode node =root.left;
root.left=root.right;
root.right=node;
invert(root.left);
invert(root.right);
}
}
第二种方向的解法:你要给递归函数一个定义,相信它能完成你给的定义,具体的递归细节不要去纠结。
class Solution {
public TreeNode invertTree(TreeNode root) {
root=invert(root);
return root;
}
//定义一个递归函数:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点
public TreeNode invert(TreeNode root){
if(root==null) return null;
TreeNode left=invert(root.left);//先翻转root根节点的左子树
TreeNode right=invert(root.right);//再翻转root根节点的右子树
//后序位置:最后将root根节点的左子树与翻转后的右子树调换,同理,最后返回根节点root
root.left=right;
root.right=left;
return root;
}
}
Leetcode-102:层序遍历
直接上代码(模板):核心就是外循环从上到下对整个树进行遍历,内循环就是对树的每一层从左到右进行遍历。
class Solution {
List<List<Integer>> result=new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
level(root);
return result;
}
public void level(TreeNode root){
if(root==null) return;
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);//入栈根节点
//外层while循环从上到下遍历
while(!queue.isEmpty()){
List<Integer> res=new ArrayList<>();
int size=queue.size();//统计队列中元素个数,也就是统计树的每一行有多少个节点
//内层for循环遍历树每行的从左到右
for(int i=0;i<size;i++){
TreeNode node=queue.poll();
res.add(node.val);
if(node.left!=null){
queue.offer(node.left);
}
if(node.right!=null){
queue.offer(node.right);
}
}
result.add(res);
}
}
}
Leetcode_114:二叉树展开链表
大问题分解小问题的思路:定义一个递归函数,让它的作用就是输入节点 root,然后 root 为根的二叉树就会被拉平为一条链表,具体如何拉平,就是用两个指针分别保存root节点左右子树信息,将左子树覆盖到右子树,最后将右子树插入末尾即可。
class Solution {
public void flatten(TreeNode root) {
traverse(root);
}
// 定义:输入节点 root,然后 root 为根的二叉树就会被拉平为一条链表
public void traverse(TreeNode root){
if(root==null) return;
traverse(root.left);
traverse(root.right);
//后序位置
//先用指针记录(保存)左右子树位置
TreeNode right=root.right;
TreeNode left=root.left;
root.left=null;//让左子树置空,由于前面指针保存了左子树的位置,所以左子树信息不会丢失
root.right=left;//让左子树覆盖右子树
//定义一个指针指向root
TreeNode p=root;
while(p.right!=null){
p=p.right;//一直指向右子树末尾
}
p.right=right;//将原来的右子树插入新的右子树末尾即可
}
}
Leetcode-654:最大二叉树
构造树的分解问题:构造根节点+构造根节点的左子树+构造根节点的右子树
从这道题中我们可以学到如果要控制数组的索引时,我们可以多封装一个函数,然后用形参的方式去控制数组索引。例如下面的build(int[] nums,int l,int r),l控制数组循环条件的左边界,r控制数组循环条件的右边界,for(int i=l;i<=r;i++),来达到逻辑上的在同一个数组上寻找"最大值"。
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return build(nums,0,nums.length-1);
}
public TreeNode build(int[] nums,int l,int r){
//这里是l>r而不是l>=r
if(l>r) return null;
int Max=Integer.MIN_VALUE;
int index=-1;
//遍历数组求最大值作为根节点
//for(int i=0;i<nums.length;i++){//错误写法:
//正确写法:l和r记录了我们每次循环nums数组的起始值和结束值,如果i从0开始nums.length结束那么最大值就永远只有一个会造成栈溢出
for(int i=l;i<=r;i++){
if(nums[i]>Max){
index=i;
Max=nums[i];
}
}
//构造二叉树的根节点
TreeNode root=new TreeNode(Max);
//递归构造二叉树的左右子树
root.left=build(nums,l,index-1);
root.right=build(nums,index+1,r);
return root;
}
}
Leetcode-106:从中序与后序遍历序列构造二叉树
构造树的分解问题:构造根节点+构造根节点的左子树+构造根节点的右子树
构造左子树和右子树中索引范围如图所示:
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
return build(inorder,0,inorder.length-1,postorder,0,postorder.length-1);
}
public TreeNode build(int[] in,int il,int ir,
int[] post,int pl,int pr){
if(pl>pr) return null;
int index=-1;
int rootVal=post[pr];//后序遍历最后一个即是根节点的值
for(int i=il;i<=ir;i++){
if(in[i]==rootVal){
index=i;//找到后序遍历中根节点在中序遍历中的位置索引
break;
}
}
//创建根节点
TreeNode root=new TreeNode(rootVal);
int leftSize=index-il;//左子树节点个数
//创建根节点的左子树
root.left=build(in,il,index-1,post,pl,pl+leftSize-1);
//创建根节点的右子树
root.right=build(in,index+1,ir,post,pl+leftSize,pr-1);
return root;
}
}

浙公网安备 33010602011771号