10 如何灵活运用递归?

178 阅读7分钟

灵神【基础算法精讲】视频的个人笔记。

视频讲的题

100. 相同的树 - 力扣(LeetCode)

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(p == nullptr || q == nullptr)  
            return p == q; //都为空 才会相等,  其中一个为空 不相等
            
	    //val和左右子树都相等 为true
        return p->val == q->val && isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
    }
};

if(p == nullptr || q == nullptr) 中,有三种情况

  1. p = nullptrq != nullptr
    • p、q 结点不相同
  2. q != nullptrq != nullptr
    • p、q 结点不相同
  3. p = nullptr并且 q = nullptr
    • p 和 q 相同

如果两棵子树相同,null 也得相同,所有可以直接返回p == q


101. 对称二叉树 - 力扣(LeetCode)

该题跟上一题很像,可以逐步转换

  1. 判断 root 的左子树和右子树是否相等。
  2. 轴对称,判断时反过来,p 的左子树跟 q 的右子树对比,p 的右子树跟 q 的左子树对比。
class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(p == nullptr || q == nullptr)  
            return p == q; //都为空 才会相等,  其中一个为空 不相等
            
	    //左右子树反过来
        return p->val == q->val && isSameTree(p->left, q->right) && isSameTree(p->right, q->left);
    }

    bool isSymmetric(TreeNode* root) {
        return isSameTree(root->left, root->right);
    }
};

110. 平衡二叉树 - 力扣(LeetCode)

该题需要求高度判断高度平衡

如果不是平衡树,得返回一个不同的值,由于高度不可能有负数,因此返回 -1 作为标识。

class Solution {
public:
    int dfs(TreeNode* root) {
        if(root == nullptr) return 0;

        int lh = dfs(root->left);
        if(lh == -1) return -1;

        int rh = dfs(root->right);
        if(rh == -1) return -1;

        return abs(lh - rh) <= 1 ? max(lh, rh) + 1 : -1;
    }

    bool isBalanced(TreeNode* root) {
        return dfs(root) != -1;
    }
};

199. 二叉树的右视图 - 力扣(LeetCode)

ans做全局变量,数组的长度作为访问过的节点的最大高度

class Solution {
public:
    vector<int> ans;

    void dfs(TreeNode* root, int h) {
        if(root == nullptr) return;

        if(ans.size() == h) 
            ans.emplace_back(root->val);

        dfs(root->right, h + 1); //先右子树
        dfs(root->left, h + 1);
    }

    vector<int> rightSideView(TreeNode* root) {
        dfs(root, 0);
        return ans;
    }
};

课后作业

226. 翻转二叉树 - 力扣(LeetCode)

你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

示例 1:

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

子问题:交换左右子树

class Solution {
public:
    void dfs(TreeNode* p) {
        if(p == nullptr) return;

        dfs(p->left);
        dfs(p->right);

        //交换
        TreeNode* tmp = p->left;
        p->left = p->right;
        p->right = tmp;
    }

    TreeNode* invertTree(TreeNode* root) {
        if(root == nullptr) return root;

        dfs(root);
        return root;
    }
};

1026. 节点与其祖先之间的最大差值 - 力扣(LeetCode)

给定二叉树的根节点 root,找出存在于 不同 节点 A 和 B 之间的最大值 V,其中 V = |A.val - B.val|,且 A 是 B 的祖先。

(如果 A 的任何子节点之一为 B,或者 A 的任何子节点是 B 的祖先,那么我们认为 A 是 B 的祖先)

示例 1:

输入:root = [8,3,10,1,6,null,14,null,null,4,7,13]
输出:7
解释: 
我们有大量的节点与其祖先的差值,其中一些如下:
|8 - 3| = 5
|3 - 7| = 4
|8 - 1| = 7
|10 - 13| = 3
在所有可能的差值中,最大值 7 由 |8 - 1| = 7 得出。

  • 维护一条路径上的最大值最小值
  • 到了终点,为空,更新该路径的最大差值
class Solution {
public:
    int ans = 0;

    void dfs(TreeNode* root, int mx, int mn) {
        if(root == nullptr) {
            ans = max(ans, mx - mn); //终点才更新
            return;
        }

        //最大值和最小值
        mx = max(root->val, mx);
        mn = min(root->val, mn);
        
        dfs(root->left, mx, mn);
        dfs(root->right,  mx, mn);
    }

    int maxAncestorDiff(TreeNode* root) {
        dfs(root, root->val, root->val);
        return ans;
    }
};

1080. 根到叶路径上的不足节点 - 力扣(LeetCode)

给你二叉树的根节点 root 和一个整数 limit ,请你同时删除树中所有 不足节点 ,并返回最终二叉树的根节点。

假如通过节点 node 的每种可能的 “根-叶” 路径上值的总和全都小于给定的 limit,则该节点被称之为 不足节点 ,需要被删除。

叶子节点,就是没有子节点的节点。

示例 1:

输入:root = [1,2,3,4,-99,-99,7,8,9,-99,-99,12,13,-99,14], limit = 1
输出:[1,2,3,4,null,null,7,8,9,null,14]

节点有两种情况

  1. 叶子节点:只有一条路径,该路径和小于limit就删除。
  2. 非叶子节点:通向左子树的路径 和 通向右子树的路径。
    1. 子树没有被删完:不删该节点
    2. 子树都被删完了:删除该节点

非叶子节点

要删除非叶节点 node,当且仅当 node 的所有儿子都被删除。


flowchart TB
	1-->3 --> -99 & 7
	-99 --> 12 & 13
	7 --> A[-99] & 14

子树没有被删完

对于7这个节点,通过节点的路径有两条

  • 1 --> 3 --> 7 --> -99
  • 1 --> 3 --> 7 --> 14

先删除叶子节点:

  • 1到-99这条路径和总和小于1,叶子节点-99为不足节点,删除。
  • 1到14这条路径和总和大于1,叶子节点14不需要删除。
flowchart TB
	1-->3 --> -99 & 7
	-99 --> 12 & 13
	7 -->  14

假如通过节点 node 的每种可能的 “根-叶” 路径上值的总和全都小于给定的 limit,则该节点被称之为 不足节点 ,需要被删除。

这时对于节点7,==子树没有被删完==,其中一种可能的 “根-叶” 路径上值的总和大于1,所以节点7不用删除。


子树都被删完

对于-99这个节点,通过节点的路径有两条

  • 1 --> 3 --> -99 --> 12
  • 1 --> 3 --> -99 --> 13

先删除叶子节点:

  • 1到12 1到13 的两条路径和总和都是小于1的,叶子节点12和13为不足节点,都要删除。
flowchart TB
	1-->3 --> -99 & 7
	7 -->  14

假如通过节点 node 的每种可能的 “根-叶” 路径上值的总和全都小于给定的 limit,则该节点被称之为 不足节点 ,需要被删除。

这时对于节点-99,子树都被删完了,因为通过节点 -99 的每种可能的 “根-叶” 路径上值的总和全都小于1,所以-99节点也是不足节点,故也得删除。(注意是根-叶路径)

flowchart TB
	1-->3 --> 7
	7 -->  14

class Solution {
public:
    TreeNode* sufficientSubset(TreeNode* root, int limit) {
        if(root == nullptr) return nullptr;
        
        limit -= root->val;
        if(root->left == nullptr && root->right == nullptr) //叶子结点
            return 0 < limit ? nullptr : root;

        root->left = sufficientSubset(root->left, limit);
        root->right = sufficientSubset(root->right, limit);

        //如果还有儿子,就不删 p,否则删 p
        return root->left || root->right ? root : nullptr;
    }
};

1110. 删点成林 - 力扣(LeetCode)

给出二叉树的根节点 root,树上每个节点都有一个不同的值。

如果节点值在 to_delete 中出现,我们就把该节点从树上删去,最后得到一个森林(一些不相交的树构成的集合)。

返回森林中的每棵树。你可以按任意顺序组织答案。

示例 1:

输入:root = [1,2,3,4,5,6,7], to_delete = [3,5]
输出:[[1,2,null,4],[6],[7]]

这题跟上一题的思路基本一样,归的时候返回null来删除节点。

class Solution {
public:
    vector<TreeNode*> ans;

    TreeNode* dfs(TreeNode* p, vector<int>& to_delete) {
        if(p == nullptr) return nullptr;

        p->left = dfs(p->left, to_delete);
        p->right = dfs(p->right, to_delete);

        for(int i: to_delete) {
            if(p->val == i) {
                //左右子树,有就加入ans
                if(p->left) ans.emplace_back(p->left);
                if(p->right) ans.emplace_back(p->right);
                return nullptr; //删除该节点
            }
        }

        return p; //不删除,返回
    }

    vector<TreeNode*> delNodes(TreeNode* root, vector<int>& to_delete) {
        TreeNode* r = dfs(root, to_delete);
        if(r != nullptr) ans.emplace_back(r);
        return ans;
    }
};

1372. 二叉树中的最长交错路径 - 力扣(LeetCode)

给你一棵以 root 为根的二叉树,二叉树中的交错路径定义如下:

  • 选择二叉树中 任意 节点和一个方向(左或者右)。
  • 如果前进方向为右,那么移动到当前节点的的右子节点,否则移动到它的左子节点。
  • 改变前进方向:左变右或者右变左。
  • 重复第二步和第三步,直到你在树中无法继续移动。

交错路径的长度定义为:访问过的节点数目 - 1(单个节点的路径长度为 0 )。

请你返回给定树中最长 交错路径 的长度。

示例 1:

输入:root = [1,null,1,1,1,null,null,1,1,null,1,null,null,null,1,null,1]
输出:3
解释:蓝色节点为树中最长交错路径(右 -> 左 -> 右)。

  • 维护一个全局变量:最长交错路径的长度。
#define turn_left 0
#define turn_right 1

class Solution {
public:
    int maxLen = 0;

    void dfs(TreeNode* p, int len, int turn) {
        if(!p) return;

        maxLen = max(maxLen, len);

        if(turn == turn_left) { //上一次左转
            dfs(p->left, 1, turn_left); //方向未改变,重算长度
            dfs(p->right, len + 1, turn_right); //方向改变,+1
        } else { //上一次右转
            dfs(p->left, len + 1, turn_left);
            dfs(p->right, 1, turn_right);
        }
    }

    int longestZigZag(TreeNode* root) {
        if(!root) return 0;
        dfs(root->left, 1, turn_left);
        dfs(root->right, 1, turn_right);
        return maxLen;
    }
};