七月集训(15)深度优先搜索(dfs)

本文详细介绍了LeetCode上的四道二叉树问题,包括计算布尔二叉树的值、翻转等价二叉树、找到所有的农场组以及寻找重复的子树。每道题目的解法都通过深度优先搜索(DFS)策略,结合逻辑运算、树的翻转和染色等技巧,展示了如何解决这类问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.LeetCode:2331. 计算布尔二叉树的值

原题链接


        给你一棵 完整二叉树 的根,这棵树有以下特征:

        叶子节点 要么值为 0 要么值为 1 ,其中 0 表示 False ,1 表示 True 。

        非叶子节点 要么值为 2 要么值为 3 ,其中 2 表示逻辑或 OR ,3 表示逻辑与 AND 。

        计算 一个节点的值方式如下:

        如果节点是个叶子节点,那么节点的 值 为它本身,即 True 或者 False 。

        否则,计算 两个孩子的节点值,然后将该节点的运算符对两个孩子值进行 运算 。

        返回根节点 root 的布尔运算值。

        完整二叉树 是每个节点有 0 个或者 2 个孩子的二叉树。

        叶子节点 是没有孩子的节点。

        示例 1:

        输入:root = [2,1,3,null,null,0,1]

        输出:true

        示例 2:

        输入:root = [0]

        输出:false

        提示:

        树中节点数目在 [1, 1000] 之间。

        0 <= Node.val <= 3

        每个节点的孩子数为 0 或 2 。

        叶子节点的值为 0 或 1 。

        非叶子节点的值为 2 或 3 。


        这道题就直接按照根节点的值不断dfs就好了,如果根为叶子结点返回根的值,如果根为2返回左右子树按位或的结果,如果根为3返回左右子树按位与的结果

bool isleaf(TreeNode* root){
    return !root->left&&!root->right;
}
bool dfs(TreeNode* root){
    if(isleaf(root)) return root->val;
    if(root->val==2){
        return dfs(root->left)|dfs(root->right);
    }else return dfs(root->left)&dfs(root->right);
}
class Solution {
public:
    bool evaluateTree(TreeNode* root) {
        return dfs(root);
    }
};

2.LeetCode:951. 翻转等价二叉树

原题链接


        我们可以为二叉树 T 定义一个 翻转操作 ,如下所示:选择任意节点,然后交换它的左子树和右子树。

        只要经过一定次数的翻转操作后,能使 X 等于 Y,我们就称二叉树 X 翻转 等价 于二叉树 Y。

        这些树由根节点 root1 和 root2 给出。如果两个二叉树是否是翻转 等价 的函数,则返回 true ,否则返回 false 。

        示例 1:

        输入:root1 = [1,2,3,4,5,6,null,null,null,7,8], root2 = [1,3,2,null,6,4,5,null,null,null,null,8,7]

        输出:true

        示例 2:

        输入: root1 = [], root2 = []

        输出: true

        示例 3:

        输入: root1 = [], root2 = [1]

        输出: false

        提示:

        每棵树节点数在 [0, 100] 范围内

        每棵树中的每个值都是唯一的、在 [0, 99] 范围内的整数


        这道题就是看吧这两棵树对应的某个结点的左右子树交换位置后是否相等,那么能交换的条件肯定这两棵树左子树相等或者右子树相等或者左子树和右子树相等,要达到这个判断条件就需要递归到最深处来处理,所以本题就直接递归处理即可。

        递归判断的条件具体来说是如果同时为空返回true,有一个为空另一个不为空返回false,根节点值不同返回false,最后判断左子树和右子树或者左右子树是否对应相等

class Solution {
public:
    bool flipEquiv(TreeNode* root1, TreeNode* root2) {
        if(root1&&!root2||!root1&&root2) return false;
        if(!root1&&!root2) return true;
        if(root1->val!=root2->val) return false;
        return (flipEquiv(root1->left,root2->left)&&
        		flipEquiv(root1->right,root2->right))
             ||(flipEquiv(root1->left,root2->right)&&
             	flipEquiv(root1->right,root2->left));
    }
};

3.LeetCode:1992. 找到所有的农场组

原题链接


        给你一个下标从 0 开始,大小为 m x n 的二进制矩阵 land ,其中 0 表示一单位的森林土地,1 表示一单位的农场土地。

        为了让农场保持有序,农场土地之间以矩形的 农场组 的形式存在。每一个农场组都 仅 包含农场土地。且题目保证不会有两个农场组相邻,也就是说一个农场组中的任何一块土地都 不会 与另一个农场组的任何一块土地在四个方向上相邻。

        land 可以用坐标系统表示,其中 land 左上角坐标为 (0, 0) ,右下角坐标为 (m-1, n-1) 。请你找到所有 农场组 最左上角和最右下角的坐标。一个左上角坐标为 (r1, c1) 且右下角坐标为 (r2, c2) 的 农场组 用长度为 4 的数组 [r1, c1, r2, c2] 表示。

        请你返回一个二维数组,它包含若干个长度为 4 的子数组,每个子数组表示 land 中的一个 农场组 。如果没有任何农场组,请你返回一个空数组。可以以 任意顺序 返回所有农场组。

        示例 1:

        输入:land = [[1,0,0],[0,1,1],[0,1,1]]

        输出:[[0,0,0,0],[1,1,2,2]]

        示例 2:

        输入:land = [[1,1],[1,1]]

        输出:[[0,0,1,1]]

        示例 3:

        输入:land = [[0]]

        输出:[]

        提示:

        m == land.length

        n == land[i].length

        1 <= m, n <= 300

        land 只包含 0 和 1 。

        农场组都是 矩形 的形状。


        很明显的dfs染色的题目。由于题目说明了所有的农场组都是矩形,那么我们要找的就是每个农场组左上角坐标和右下角坐标,左上角自然是该农场组我们第一次找到的位置,右下角是我们dfs到的最深的坐标。

        首先遍历数组,每遇到1就说明遇到了农场组对该组进行dfs染色,r1 ,c1首先为i,j,r2,c2为dfs到的i,j的最大值。在dfs中首先判断该点是否被搜索过,如果是直接返回,然后调整r2,c2的值,之后从r1.c1向四个方向搜索并把1染色为0.最后在答案中加入{r1,c1,r2.c2}即可。

class Solution {
    int n,m;
    bool vis[310][310];
    int r1,c1,r2,c2;
    int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
    void dfs(vector<vector<int>>& land,int r,int c,int& r2,int &c2){
        if(!land[r][c]){
            return ;
        }
        land[r][c]=0;
        r2=max(r2,r);
        c2=max(c2,c);
        for(int i=0;i<4;++i){
            int tx=r+dir[i][0];
            int ty=c+dir[i][1];
            if(tx==-1||ty==-1||tx==m||ty==n){
                continue;
            }
            dfs(land,tx,ty,r2,c2);
        }
    }
public:
    vector<vector<int>> findFarmland(vector<vector<int>>& land) {
        m=land.size();
        n=land[0].size();
        vector<vector<int>> ans;
        for(int i=0;i<m;++i){
            for(int j=0;j<n;++j){
                if(land[i][j]){
                    r1=i;
                    c1=j;
                    r2=i;
                    c2=j;
                    dfs(land,i,j,r2,c2);
                    ans.push_back({r1,c1,r2,c2});
                }
            }
        }
        return ans;
    }
};

4.LeetCode:652. 寻找重复的子树

原题链接


        给定一棵二叉树 root,返回所有重复的子树。

        对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。

        如果两棵树具有相同的结构和相同的结点值,则它们是重复的。

        示例 1:

        输入:root = [1,2,3,4,null,2,4,null,null,4]

        输出:[[2,4],[4]]

        示例 2:

        输入:root = [2,1,1]

        输出:[[1]]

        示例 3:

        输入:root = [2,2,2,3,null,3,null]

        输出:[[2,3],[3]]

        提示:

        树中的结点数在[1,10^4]范围内。

        -200 <= Node.val <= 200


        本题就是很典型的哈希问题了,在一棵树上确定相等的树哈希是最简单最快的解决方式。而哈希是什么就很难在这里一句两句说清楚了,简单的说就是我们认为每一棵树按照某个算法都能够计算出一个唯一的哈希值,构造出这种算法来让哈希值和每一个树进行映射,我们可以快速判断某两个数是否相等。

        具体的算法就五花八门了,而研究出来这种方法就是数学家们要做的事了,这里我们利用现成结论就好。一种简单的计算哈希值的方法是把某棵树抽象成一个p进制的数,每个结点都有自己对应的权值,这里我们就认为左子树的权值为 base ^ 2,右子树的权值为base根为1。这其中左右子树的自己的左右子树也是这样,不过往上计算的时候他们的权值相应的乘上了base ^ 2或者base,也就是这里是后序遍历来构造这个哈希值,并且在后序遍历的过程中我们也随时可以吧所有子树的哈希值也加入了哈希表中, 这样显然是符合我们的预期的

        那么这个base的选择就决定了我们哈希碰撞的概率,恰好数学家们已经找出了这个base恰当的数,并且已经验证了当选择这些数为base的时候碰撞概率足够小,小到我们一辈子都有极大概率碰不到。那么这些base就是质数,而这些质数中比较优秀的有131,13331,233,2333,10007.

        同时我们也应该注意到这样计算出来的哈希值是很大的,那么就需要用unsigned long long 来缩小哈希值,也就是对2^64取模。总的来说哈希的思路就是吧一个树看作一个base进制的数。

        不过这里对哈希的介绍十分十分十分的简略,甚至可以说十分的粗糙,大家还是需要自己去深入了解一下字符串哈希。那么回到本题,首先对root来dfs进行哈希构造,并用一个哈希表来进行哈希值与以根节点为代表的子树进行映射。每次计算出哈希值向相应的哈希值的哈希表的val中加入当前根节点。dfs结束后遍历哈希表,如果某个key的val的size大于1了的话就说明存在相同的子树,而题目说了我们随意加入一个根节点即可,那么我们随意加入val中的一个结点即可。

        不过本题似乎是故意卡了哈希这种做法,卡了几个特殊数据,也就是故意制造了哈希碰撞。那么这个时候我们可以对val中的结点进行check检查,检查val中的每个结点代表的子树是否相同,如果相同就返回当前遍历到的结点即可。这个检查的方法和第二题类似,不过不是交换我们直接看左右子树是否对应相等。

#define ull unsigned long long
#define base ((ull)13331)
#define inf  ((ull)401)
class Solution {
    unordered_map<ull,vector<TreeNode*>> map;
    
    ull dfs(TreeNode* root){
        if(!root){
            return inf;
        }
        ull val=dfs(root->left)*base*base+root->val+200+dfs(root->right)*base;
        map[val].push_back(root);
        return val;
    }
    bool checksame(TreeNode* t1,TreeNode* t2){
        if(t1&&!t2||!t1&&t2) return false;
        if(!t1&&!t2) return true;
        if(t1->val!=t2->val) return false;
        return checksame(t1->left,t2->left)&&checksame(t1->right,t2->right);
    }
    int check(vector<TreeNode*>&nodes){
        for(int i=0;i<nodes.size();++i){
            for(int j=i+1;j<nodes.size();++j){
                if(checksame(nodes[i],nodes[j])){
                    return i;
                }
            }
        }
        return -1;
    }
public:
    vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
        vector<TreeNode*> ans;
        dfs(root);
        for(auto it=map.begin();it!=map.end();++it){
            if(it->second.size()>1){
                int idx=check(it->second);
                if(idx!=-1)
                ans.push_back(it->second[idx]);
            }
        }
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值