数据结构力扣学习--二叉树总结

力扣过去几天做过的题目总结:

暂时留下这两个异类不做整理。
213. 打家劫舍 II
337. 打家劫舍 III


一、分治法(大问题化作小问题):

114. 二叉树展开为链表
方法1:(不详细说)
①先按照先序遍历把遍历的顺序保存到数组中
②遍历数组,构建链表。
方法2
将整颗数展开成列表,我们可以大问题化作小问题:
①将左子树展开成列表
②将右子树展开成列表
③根节点的左右孩子再展开成列表

在这里插入图片描述
代码:

var flatten = function (root) {
  if (root == null) return;
  
  flatten(root.left);//1.左子树展开成列表
  flatten(root.right);//2.右子树展开成列表

  // 3.将根节点的左右子树展开
  //为了防止后面找不到左后孩子,先存下来。
  let left = root.left;
  let right = root.right;

  root.left = null;
  root.right = left;

  // 3、将原先的右子树接到当前右子树的末端
  while (root.right != null) {
    root = root.right;
  }
  root.right = right;
};

剑指 Offer 27. 二叉树的镜像
这个题其实是递归,但我更愿称之为分治法。

思路:
交换整棵树=
①交换根左孩子节点的左右子树。
②交换根的右孩子节点的左右子树
③最后根的左右孩子交换一下。

递归出口:树空。

void Switch(BiTree T){
	if(T){
		Switch(T->lchild);//左子树交换
		Switch(T->rchild);//右子树交换
		//交换根的左右孩子
		tmp=T->lchild; 
		T->lchild=T->rchild;
		T->rchild=tmp;
	}
}	

二、递归法

437. 路径总和 III
题目:求二叉树里节点值等于targetsum的路径和。
要求:不需要从根开始,也不需要到叶子结束。

思路:以某种遍历顺序遍历所有节点。然后以该节点为起点往下递归所有的路径,返回该点往下的所有路径和。

如:
①以先序遍历顺序来dfs所有节点。
②visit(每一个节点):
构建递归方程:rootSum(root,targetSum)
表示以root为起点,路径和为targetSum的路径的总数。
递归结束条件:root==null

class Solution {
public:
//visit(每一个节点):
    int rootSum(TreeNode* root, int targetSum) {
        if (!root)  return 0;
        
        int ret = 0;
        if (root->val == targetSum) {
            ret++;
        } 

        ret += rootSum(root->left, targetSum - root->val);
        ret += rootSum(root->right, targetSum - root->val);
        return ret;
    }
 //①以先序遍历顺序来遍历所有节点。
    int pathSum(TreeNode* root, int targetSum) {
        if (!root)   return 0;
        int ret = rootSum(root, targetSum);
        ret += pathSum(root->left, targetSum);
        ret += pathSum(root->right, targetSum);
        return ret;
    }
};

988. 从叶结点开始的最小字符串

题目:从叶子开始,到根结束的最小字典序。
思路:本题跟上一题类似,都可以通过dfs实现。但不同之处在于本题的起点、终点固定。不需要像让一个题一样使用先序遍历dfs每一个节点。
我们先倒置,以根节点为起点。这样变为:仅以根起点为起点,往下递归至叶子节点结束。
①设置全局变量ans,表示:最表字典序
②从根节点开始往下递归每一条路径。
③搜索完一条路径:与ans比较,更新ans。
搜索完一条路径的条件:

if(!root->left && !root->right)
class Solution {
    //全局变量:最小字典序ans
    String ans = "~";
    public String smallestFromLeaf(TreeNode root) {
        dfs(root, new StringBuffer());
        return ans;
    }

    //深度优先搜索每一条根到叶的路径
    public void dfs(TreeNode root, StringBuffer sb){
        //遍历到树的末端,返回
        if(root == null) return;
        //将当前节点的字符拼接到路径字符串中
        sb.append(....);
        //叶子节点:当前路径字符串s的字典序若小于ans,则更新到ans中
        if(root.left == null && root.right == null){
            sb.reverse();//为了比较最小字典序
            String s = sb.toString();
            sb.reverse(); // 恢复到原来状态,便于后面的dfs

            if(s.compareTo(ans) < 0)  ans = s;
        }
        //深度优先遍历
        dfs(root.left, sb);
        dfs(root.right, sb);
        //状态重置到上一次
        sb.deleteCharAt(sb.length() - 1);
    }
}

124. 二叉树中的最大路径和
题目:从任意路径出发,沿着父节点-子节点 到达任意节点的节点和最大路径。
如:
在这里插入图片描述
思路:
一个节点的最大路径和 = 左子树提供的最大路径和 + 根节点值 + 右子树提供的最大路径和。

其中对于子树的每一个节点的收益有三种情况:
①只有当前根节点最大:root->val;
②最大收益:根节点+左子树 root->val+dfs(root->left)
③最大收益:根节点+右子树 root->val+dfs(root->right)

子树的最大收益即:root->val+max(0,dfs(left),dfs(right))

过程:
①定义全局变量:maxsum 表示最大路径和
②从根节点开始递归;
③每搜索完子树的一个节点的最大路径和 与maxsum比较,进行更新。

const maxPathSum = (root) => {
    let maxSum = Number.MIN_SAFE_INTEGER; // 最大路径和

    const dfs = (root) => {
        if (root == null) { // 遍历到null节点,收益0
           return 0;
        }
		//当前节点的最大路径和
        const innerMaxSum = dfs(root->left) + root.val + dfs(root->right) 
        //③每搜索完子树的一个节点的最大路径和 与maxsum比较,进行更新
        maxSum = Math.max(maxSum, innerMaxSum);  
          
		//作为子树像父节点返回的最大路径和。
        const outputMaxSum = root.val + Math.max(0, left, right); 

        // 如果对外提供的路径和为负,直接返回0。否则正常返回
        return outputMaxSum < 0 ? 0 : outputMaxSum;
    };

    dfs(root);  // 递归的入口

    return maxSum; 
};

二.五、基础重要的递归变形:

105. 从前序与中序遍历序列构造二叉树
已知先序序列 A【1…n】 中序序列 B【1…n】 构建二叉链表

如先序: A BDFUECG
中序: FUDBEACG

过程:
①根据先序序列找到根节点
②在中序序列中找到根节点所在位置,划分出左右子树 (递归创建二叉树)
③在左右子树中递归构建二叉链表
重复①②

BiTree PreInCreat(pre[],In[],l1,ln1,l2,ln2){
	root=(BiTree*)malloc(sizeof(BiTNode);
	root->data=pre[l1];//创建根节点
	//在中序中找根的位置
	for(i=l2;In[i]!=root->data;i++){
		//确定左右子树的长度
		llen=i-l2; rlen=ln2-i;
		//递归创建左右子树
		if(llen){
			root->lchild=PreInCreat(pre,In,l1+1,l1+llen,l2,i-1);
		}
		else{
			root->lchild=null;
		}
		if(llen){
			root->rchild=PreInCreat(pre,In,l1-rlen+1,ln1,i+1,ln2);
		}
		else{
			root->rchild=null;
		}
		return root;
	}

满二叉树已知前序转后序
这个题很重要
思路也是一样的,看之前博客。
定义递归函数:

PreToPost(pre,l1,h1,post,l2,h2)

三、层次遍历的变形

剑指 Offer 27. 二叉树的镜像不多说了,看以前博客。

662. 二叉树最大宽度
题目:每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。
思路:
利用完全二叉树的性质–节点的编号与满二叉树对应。采用层次遍历,将该节点值重新编号。

过程:
①根节点入队
②将根节点值初始化为“1”,
③计算根节点坐在层的宽度(R-L+1)
④将根节点的左右子树入队列。
重复 2、3、4

同样需要定义一个全局变量 maxdepth
在每一层的开始会首先计算下一层的宽度,比较,更新maxdepth。
<代码不写,但之前博客>

二、二叉树的最大宽度2

题目:节点数最多的某层的节点个数。(不包括null)

思路1:
跟上一个题一样,采用一个全局变量maxdepth,一层一层的比较。

当遍历到该层的最后一个节点时,队列中已经存储了下一层的所有节点。此时统计一下下一层的宽度(rear-front),与maxdepth进行比较更新。

思路2:采用层次遍历,定义level来存储每一个节点所在的层次。最后遍历level,统计一下层次值相同的个数,最大即为所求。

过程:
①根节点入队
②将根节点值初始化为“1”,
③根节点出队
④根节点左右子树入队并在level中记录相应的层次值(k+1)
其中 k为上一层的层数。
重复 2、3、4

《代码看以前博客》

617. 合并二叉树

考虑以下两种情况:

  1. 如果两个原始二叉树的左子节点都不为空,则合并后的二叉树的左子节点的值为两个原始二叉树的左子节点的值之和。
  2. 如果两个原始二叉树的左子节点有一个为空,则合并后的二叉树的左子树即为另一个原始二叉树的左子树。
class Solution {
    public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        if (t1 == null) {
            return t2;
        }
        if (t2 == null) {
            return t1;
        }
        TreeNode merged = new TreeNode(t1.val + t2.val);
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        Queue<TreeNode> queue1 = new LinkedList<TreeNode>();
        Queue<TreeNode> queue2 = new LinkedList<TreeNode>();
        queue.offer(merged);
        queue1.offer(t1);
        queue2.offer(t2);
        while (!queue1.isEmpty() && !queue2.isEmpty()) {
            TreeNode node = queue.poll(), node1 = queue1.poll(), node2 = queue2.poll();
            TreeNode left1 = node1.left, left2 = node2.left, right1 = node1.right, right2 = node2.right;
            if (left1 != null || left2 != null) {
                if (left1 != null && left2 != null) {
                    TreeNode left = new TreeNode(left1.val + left2.val);
                    node.left = left;
                    queue.offer(left);
                    queue1.offer(left1);
                    queue2.offer(left2);
                } else if (left1 != null) {
                    node.left = left1;
                } else if (left2 != null) {
                    node.left = left2;
                }
            }
            if (right1 != null || right2 != null) {
                if (right1 != null && right2 != null) {
                    TreeNode right = new TreeNode(right1.val + right2.val);
                    node.right = right;
                    queue.offer(right);
                    queue1.offer(right1);
                    queue2.offer(right2);
                } else if (right1 != null) {
                    node.right = right1;
                } else {
                    node.right = right2;
                }
            }
        }
        return merged;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苏轼'

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值