【算法】树形DP ①(树的直径)

news2024/11/25 3:00:08

文章目录

  • 知识准备
  • 例题
    • 543. 二叉树的直径
    • 124. 二叉树中的最大路径和
    • 2246. 相邻字符不同的最长路径
  • 相关题目练习
    • 687. 最长同值路径 https://leetcode.cn/problems/longest-univalue-path/solution/shi-pin-che-di-zhang-wo-zhi-jing-dpcong-524j4/
    • 1617. 统计子树中城市之间最大距离 https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/solution/tu-jie-on3-mei-ju-zhi-jing-duan-dian-che-am2n/⭐⭐⭐⭐⭐
      • 方法1——枚举子集+树的直径
        • Arrays.setAll()和Arrays.fill()
      • 方法2——二进制枚举
        • Integer.numberOfTrailingZeros()
      • 解法3——枚举直径端点+乘法原理
        • 思路
        • 代码
    • 2538. 最大价值和与最小价值和的差值 https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/solution/by-endlesscheng-5l70/⭐⭐⭐⭐⭐

知识准备

一般 dfs 内的逻辑是找这个子树内的最长路径, dfs 的返回值是这个子树内最长的那个从根节点开始的链。
在这里插入图片描述

回忆 104. 二叉树的最大深度 这道题目。
在这里插入图片描述
我们的做法是
整棵树的最大深度 = max(左子树的最大深度,右子树的最大深度) + 1

那么树的直径和最大深度之间是否有联系呢?

例题

543. 二叉树的直径

543. 二叉树的直径

在这里插入图片描述
换个角度看直径:从一个叶子出发向上,在某个节点「拐弯」,向下到达另一个叶子。得到了由两条链拼起来的路径。(也可能只有一条链)

class Solution {
    int ans = 0;

    public int diameterOfBinaryTree(TreeNode root) {
        dfs(root);
        return ans - 1;
    }

    // 求节点的高度
    public int dfs(TreeNode root) {
        if (root == null) return 0;
        int l = dfs(root.left), r = dfs(root.right);
        ans = Math.max(ans, l + r + 1);	// 更新答案
        return 1 + Math.max(l, r);
    }
}

这道题由于我本身就会做,所以没太感受到 dp 的味道。

我的解题思想是这样的:
以每个节点为拐点的直径长度就是 1 + 左叶子的高度 + 右叶子的高度,因此就用后序 dfs 不断得到各个节点的高度返回给它们的父节点,在这个过程中更新答案就好了。

124. 二叉树中的最大路径和

124. 二叉树中的最大路径和
在这里插入图片描述
和 543. 二叉树的直径 这道题目的思路基本一致,区别在于子节点返回给父节点的值不再是节点的高度,而是从它到叶子节点的个节点值的最大路径总和。

class Solution {
    int ans = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
        dfs(root);
        return ans;    
    }

    public int dfs(TreeNode root) {
        if (root == null) return 0;
        int l = dfs(root.left), r = dfs(root.right);
        ans = Math.max(ans, l + r + root.val);
        return Math.max(0, Math.max(l, r) + root.val);
    }
}

2246. 相邻字符不同的最长路径

2246. 相邻字符不同的最长路径

在这里插入图片描述

这道题目与上面题目的区别是:

  • 这是一个多叉树,所以不能只计算两个子节点的返回值,而是所有子节点的返回值。
  • 在计算答案时,需要确保当前节点和子节点的字符不相同

其它思路与 543. 二叉树的直径 类似。

class Solution {
    String s;
    int ans = 1;
    List<List<Integer>> childs = new ArrayList();

    public int longestPath(int[] parent, String s) {
        this.s = s;
        int n = parent.length;
        for (int i = 0; i < n; ++i) childs.add(new ArrayList());
        // 用列表记录每个节点的所有子节点
        for (int i = 1; i < n; ++i) {
            childs.get(parent[i]).add(i);
        }
        dfs(0);
        return ans;
    }

    public int dfs(int rootId) {
        char ch = s.charAt(rootId);
        int mxl1 = 0, mxl2 = 0;     // 分别记录到子节点的最大高度和第二大高度
        for (int i: childs.get(rootId)) {
            int mxL = dfs(i);
            if (ch != s.charAt(i)) {
                if (mxL > mxl1) {
                    mxl2 = mxl1;
                    mxl1 = mxL;
                } else if (mxL > mxl2) mxl2 = mxL;
            }
        }
        ans = Math.max(ans, 1 + mxl1 + mxl2);   // 答案是路径和
        return 1 + mxl1;    // 返回值是节点最大高度
    }
}

相关题目练习

687. 最长同值路径 https://leetcode.cn/problems/longest-univalue-path/solution/shi-pin-che-di-zhang-wo-zhi-jing-dpcong-524j4/

https://leetcode.cn/problems/longest-univalue-path/solution/shi-pin-che-di-zhang-wo-zhi-jing-dpcong-524j4/

在这里插入图片描述
这道题目要求 当前节点和子节点的数值相同时 才可以选择。

class Solution {
    int ans = 1;

    public int longestUnivaluePath(TreeNode root) {
        dfs(root);
        return ans - 1;     // 返回的是边数,所以是节点数-1
    }

    public int dfs(TreeNode root) {
        if (root == null) return 0;
        int l = dfs(root.left), r = dfs(root.right);
        // 如果当前节点和子节点的数值不相同,那就不能选对应的子节点
        if (l != 0 && root.val != root.left.val) l = 0;
        if (r != 0 && root.val != root.right.val) r = 0;
        ans = Math.max(ans, 1 + l + r);     // 更新答案
        return 1 + Math.max(l, r);          // 返回值
    }
}

1617. 统计子树中城市之间最大距离 https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/solution/tu-jie-on3-mei-ju-zhi-jing-duan-dian-che-am2n/⭐⭐⭐⭐⭐

https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/solution/tu-jie-on3-mei-ju-zhi-jing-duan-dian-che-am2n/

在这里插入图片描述

在这里插入图片描述

方法1——枚举子集+树的直径

使用回溯枚举整个城市的所有子集,
分别计算各个子集作为树的时候,它的直径是多少。

class Solution {
    List<Integer>[] g;  	// 记录各个节点相邻的所有节点
    int n, diameter;    
    boolean[] inSet, vis;	// inSet记录子集中的节点,vis记录求直径时遍历到的节点
    int[] ans;

    public int[] countSubgraphsForEachDiameter(int n, int[][] edges) {
        this.n = n;
        g = new ArrayList[n];
        Arrays.setAll(g, e -> new ArrayList<Integer>());
        // 建树
        for (int[] edge: edges) {
            int x = edge[0] - 1, y = edge[1] - 1;
            g[x].add(y);
            g[y].add(x);
        }

        inSet = new boolean[n];
        ans = new int[n - 1];
        dfs(0);
        return ans;
    }

    public void dfs(int i) {
        if (i == n) {
            diameter = 0;       // 计算之前先归零
            // 计算当前子集树的直径
            for (int v = 0; v < n; ++v) {
                if (inSet[v]) { // 找到了一个在子集中的节点
                    vis = new boolean[n];
                    get(v);     // 计算直径
                    break;      // 只需要算一次就够了(从树中任意一个节点开始算都能算出相同的结果)
                }
            }
            if (diameter > 0 && Arrays.equals(vis, inSet)) ans[diameter - 1]++;
            return;
        }
        // 不选i
        dfs(i + 1);
        // 选i
        inSet[i] = true;
        dfs(i + 1);
        inSet[i] = false;
    }

    public int get(int x) {
        vis[x] = true;
        int mxLen = 0;			// 记录已经枚举过的分支的最大长度
        for (int y: g[x]) {
            if (!vis[y] && inSet[y]) {
                int ml = get(y) + 1;						// 当前枚举的分支的最大长度
                diameter = Math.max(diameter, ml + mxLen);	// 更新答案
                mxLen = Math.max(mxLen, ml);				// 更新当前枚举过的分支的最大长度
            }
        }
        return mxLen;		// 返回当前分支的最大长度
    }
}

Q:为什么需要判断 if (diameter > 0 && Arrays.equals(vis, inSet)) ans[diameter - 1]++;
A:因为当 inSet 中的元素数量只有 0 个或者 1 个的时候,经过遍历同样 vis 和 inSet 会变得相同,但此时显然 diameter = 0,不符合题意。

Arrays.setAll()和Arrays.fill()

Arrays.setAll(g, e -> new ArrayList<Integer>()); 不能使用 Arrays.fill(g, new ArrayList()); ,因为 Arrays.fill() 进去的各个下标对应的其实是同一个列表,而不是像 setAll 那样每个位置创建了一个新列表。

方法2——二进制枚举

相当于方法1的优化,即是用二进制表示集合/布尔数组。
二进制从低到高第 i 位为 1 表示 i 在集合中,为 0 表示 i 不在集合中,例如集合 {0,2,3} 对应的二进制数为 110 1 ( 2 ) 1101 _{(2)} 1101(2)

class Solution {
    private List<Integer>[] g;
    private int mask, vis, diameter;

    public int[] countSubgraphsForEachDiameter(int n, int[][] edges) {
        g = new ArrayList[n];
        Arrays.setAll(g, e -> new ArrayList<>());
        for (var e : edges) {
            int x = e[0] - 1, y = e[1] - 1; // 编号改为从 0 开始
            g[x].add(y);
            g[y].add(x); // 建树
        }

        var ans = new int[n - 1];
        // 二进制枚举
        for (mask = 3; mask < 1 << n; ++mask) {
            if ((mask & (mask - 1)) == 0) continue;     // 需要至少两个点
            vis = diameter = 0;
            dfs(Integer.numberOfTrailingZeros(mask));   // 从一个在 mask 中的点开始递归
            if (vis == mask) ++ans[diameter - 1];
        }
        return ans;
    }

    // 求树的直径
    private int dfs(int x) {
        vis |= 1 << x; // 标记 x 访问过
        int maxLen = 0;
        for (int y : g[x])
            if ((vis >> y & 1) == 0 && (mask >> y & 1) == 1) { // y 没有访问过且在 mask 中
                int ml = dfs(y) + 1;
                diameter = Math.max(diameter, maxLen + ml);
                maxLen = Math.max(maxLen, ml);
            }
        return maxLen;
    }
}

Q:为什么 if (vis == mask) ++ans[diameter - 1]; 不需要判断 diameter 的大小了。
A:因为 mask 确保了其中至少有两个点。(如果两个点不在一个树中,那么 vis 和 mask 也不会相等)

Integer.numberOfTrailingZeros()

Java中的Integer.numberOfTrailingZeros()方法是用来返回指定int值的二进制补码表示中最低位(最右边或最不重要的“1”位)后面跟着的零位的个数。如果指定的值在其二进制补码表示中没有一 位,也就是说它等于零,那么它返回32。

解法3——枚举直径端点+乘法原理

思路

暴力枚举 i 和 j 作为直径的两个端点 ,那么从 i 到 j 的这条简单路径是直径,这上面的每个点都必须选。
还有哪些点是可以选的?

在这里插入图片描述
为了计算树上任意两点的距离 dis,枚举 i 作为树的根,计算 i 到其余点的距离。这通常用 BFS 来做,但是对于树来说,任意两点的简单路径是唯一的,所以 DFS 也可以。

那么通过 n 次 DFS,就可以得到树上任意两点的距离了。

代码

class Solution {
    List<Integer>[] g;
    int[][] dis;

    public int[] countSubgraphsForEachDiameter(int n, int[][] edges) {
        g = new ArrayList[n];
        Arrays.setAll(g, e -> new ArrayList());
        for (int[] edge: edges) {
            int x = edge[0] - 1, y = edge[1] - 1;
            g[x].add(y);
            g[y].add(x);
        }

        dis = new int[n][n];
        for (int i = 0; i < n; ++i) dfs(i, i, -1);  // 计算i到其余各点的距离
        int[] ans = new int[n - 1];
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                // 加上从i到j的方案数
                ans[dis[i][j] - 1] += dfs2(i, j, dis[i][j], i, -1);
            }
        }
        return ans;
    }

    public void dfs(int i, int x, int father) {
        for (int y: g[x]) {
            if (y != father) {
                dis[i][y] = dis[i][x] + 1;
                dfs(i, y, x);
            }
        }
    }

    public int dfs2(int i, int j, int d, int x, int father) {
        int cnt = 1;
        for (int y: g[x]) {
            if (y != father &&
                (dis[i][y] < d || (dis[i][y] == d && y > j)) &&
                (dis[j][y] < d || (dis[j][y] == d && y > i))) {
                cnt *= dfs2(i, j, d, y, x);     // 每棵子树互相独立,采用乘法原理
            }
        }
        // != d表示x不是从i到j的最短路径上的节点,可以贡献方案
        if (dis[i][x] + dis[j][x] != d) ++cnt;
        return cnt;
    }
}

时间复杂度是 O ( n 3 ) O(n^3) O(n3)

2538. 最大价值和与最小价值和的差值 https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/solution/by-endlesscheng-5l70/⭐⭐⭐⭐⭐

https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/solution/by-endlesscheng-5l70/

在这里插入图片描述

在这里插入图片描述

参考资料:https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/solution/by-endlesscheng-5l70/

思路

显然价值和最小的路径就是只有一个节点的路径。
那么开销实际上就是——一条路径,去掉一个端点。

class Solution {
    List<Integer>[] g;
    int[] price;
    long ans;

    public long maxOutput(int n, int[][] edges, int[] price) {
        this.price = price;
        this.g = new ArrayList[n];
        Arrays.setAll(g, e -> new ArrayList());
        for (int[] edge: edges) {
            int x = edge[0], y = edge[1];
            g[x].add(y);
            g[y].add(x);
        }
        dfs(0, -1);
        return ans;
    }

    // 返回带叶子的最大路径和,不带叶子的最大路径和(分别表示当前节点的两边链)
    public long[] dfs(int x, int father) {
        long p = price[x];
        long maxS1 = p, maxS2 = 0;		// maxS1表示完整的,maxS2表示不完整的
        for (int y: g[x]) {
            if (y != father) {
                long[] res = dfs(y, x);
                long s1 = res[0], s2 = res[1];
                // 更新答案  一条完整的+一条不完整的
                ans = Math.max(ans, Math.max(maxS1 + s2, maxS2 + s1));
                // 更新路上叶子都选上的最大路径和
                maxS1 = Math.max(maxS1, s1 + p);
                // 更新路上叶子有一个没被选上的最大路径和(这个没被选上的不是当前节点而是之前枚举过的节点,因为s2中表示有节点没被选)
                maxS2 = Math.max(maxS2, s2 + p);
            }
        }
        return new long[]{maxS1, maxS2};
    }
}

这道题目的 dfs 过程还没太弄明白。。。

先把 dfs 当模板背过!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/715153.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

测试的流程,jira工具的使用

目录&#xff1a; 测试流程价值与体系测试计划业务架构分析思路bug基本概念bug处理流程测试总结业务架构分析工具plantuml测试流程管理jira系统-测试流程定制测试流程管理 jira 系统-Bug管理流程定制 1.测试流程价值与体系 软件测试流程 完成软件测试工作的必要步骤 测试流…

用图计算解密大脑,蚂蚁技术研究院与复旦联手启动类脑研究

大脑为什么会产生意识&#xff1f;我们为什么会失眠&#xff1f;帕金森、阿尔兹海默等神经性疾病如何有效治疗&#xff1f;这一切谜题的背后都绕不开脑科学。可以说脑科学问题是人类面临的基础科学问题之一&#xff0c;是我们解密人类自身的“终极疆域”。 我们的大脑由大约86…

第十二章线程池

文章目录 享元模式手写数据库连接池 为什么需要线程池自定义线程池自定义拒绝策略接口自定义任务队列自定义线程池 JDK中的线程池常用的线程池的类和接口的之间的关系线程池状态构造方法线程池的工作流程拒绝策略 ExecuctorsnewFixedThreadPoolnewCachedThreadPoolnewSingleThr…

【Matlab】智能优化算法_平衡优化器算法EO

【Matlab】智能优化算法_平衡优化器算法EO 1.背景介绍2.数学模型2.1 初始化和功能评估2.2 平衡池和候选者&#xff08;Ceq&#xff09;2.3 指数项&#xff08;F&#xff09;2.3 生成率&#xff08;G&#xff09; 3.文件结构4.伪代码5.详细代码及注释5.1 EO.m5.2 Get_Functions_…

Linux基础服务7——lamp架构

文章目录 一、基本了解二、单机部署LAMP2.1 安装httpd2.2 安装mysql2.3 安装php环境2.4 配置apache 三、分离部署四、脚本单机部署 一、基本了解 LAMP架构介绍&#xff1a; lamp是由LinuxApacheMysql/MariaDBPhp/Perl/Python的一组动态网站或者服务器的开源软件。LAMP指Linux&a…

多元回归预测 | Matlab基于深度置信网络(DBN)回归预测,matlab代码回归预测,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab多元回归预测 | Matlab基于深度置信网络(DBN)回归预测,matlab代码回归预测,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分…

原来工作18年的企业大佬都是这样自定义企业微信扫码登录的样式

前言 由于企业微信扫码登录都是固定样式和模板&#xff0c;每个公司在前期使用的时候可能会使用原样的模版&#xff0c;随着业务场景的复杂及细分场景优化&#xff0c;这个固定样式的模版满足不了企业的需求&#xff0c;所以需要对模版进行改造&#xff0c;使它更加贴合企业业务…

【elementplus】解决el-table开启show-overflow-tooltip后,tooltip的显示会被表格边框遮挡的问题

如图所示&#xff1a; 原因&#xff1a; 1. el-table没有设置高度&#xff1b;2.就是被遮住了 解决&#xff1a; 方法一&#xff1a;给el-table设置高度 方法二: .el-table {overflow: visible !important;}如果不想给el-table设置高度&#xff0c;就直接使用方法二解决即可

Pycharm使用Anoconda配置虚拟环境

目录 1.Anoconda的介绍 2.Anaconda的作用 3.Anaconda的安装 4.Anaconda的配置 4.1添加镜像源 4.2创建、使用并切换虚拟环境 5.pycharm的集成 1.Anoconda的介绍 Anaconda是一个可用于科学计算的 Python 发行版&#xff0c;可以便捷获取和管理包&#xff0c;同时对环境进行…

Java内存结构分析

一、Java内存结构划分 Java虚拟机的运行时数据区域主要包括程序计数器、Java虚拟机栈、本地方法栈、堆、方法区。 &#xff08;1&#xff09;程序计数器&#xff08;Program Counter Register&#xff09; 它是一块较小的内存空间&#xff0c;它可以看作是当前线程所执行的字…

SpringBoot项目整合WebSocket+netty实现前后端双向通信(同时支持前端webSocket和socket协议哦)

目录 前言 技术栈 功能展示 一、springboot项目添加netty依赖 二、netty服务端 三、netty客户端 四、测试 五、代码仓库地址 专属小彩蛋&#xff1a;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家…

OLS回归分析理论基础

前言 由于目前的实证研究中需要对变量间的因果关系进行定量分析&#xff0c;所以以伍德里奇和陈强两版本计量经济学教材为基础&#xff0c;有针对性的整理出OLS回归的相关知识&#xff0c;以解决实证分析中的实际问题。 1&#xff09;本文重点&#xff1a;本文重点研究OLS下面板…

vs code koroFileHeader插件相关配置

https://www.cnblogs.com/melodyjerry/p/14449990.html 一、安装插件 koroFileHeader 插件作用&#xff1a;在文件顶部添加头部注释 VS Code 中搜索并安装插件 koroFileHeader&#xff1b; 点击插件右下方的 设置 按钮 > 扩展设置 > 点击 在settings.json 中编辑&…

数据结构 线性表的定义和基本操作(以顺序表为例)

名人说&#xff1a;一花独放不是春&#xff0c;百花齐放花满园。——《增广贤文》 作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 〇、线性表是什么&#xff1f;1、定义2、特点3、基本操作 一、代码实现二、思路阐明…

记录使用ffmpeg把mp4转换成m3u8

背景:公司需要上一些视频资源,平均每一个都在600m以上,经过考虑以后采取视频分片以后上传到oss上进行加速播放的流程.这里记录一下使用ffmpeg进行转换视频格式的过程中的一些命令. 准备工作: 下载ffmpeg到本地,以及配置ffmpeg到环境变量中,这里就不多说了. 使用的时候先打开…

软考每年成绩几月公布 软考考试历年成绩查询时间

软考成绩一般在考试结束后两个月内公布&#xff0c;上半年软考考试成绩一般在7月查询&#xff0c;下半年软考考试成绩一般在12月查询。软考成绩在中国计算机技术职业资格网公布&#xff0c;从2022年起&#xff0c;软考的合格标准为满分的60%&#xff0c;即45分合格。 软考考试…

MybatisX插件自动生成sql失效问题的详细分析

mybatis框架提供了非常好用的逆向工程插件&#xff0c;但是根据数据库驱动版本的不同会出现一些问题。 在使用mybatisX插件的时候使用Generate mybatis sql无法实现自动生成sql 解决方案&#xff1a; 1.首先检查自己的数据库中表是否有主键&#xff0c;如果没有主键是不会生…

流及其相关操作

本文已收录于专栏 《Java》 目录 概念说明流 流的分类根据数据流向的不同&#xff0c;可以分为输入流和输出流。根据处理单位的不同&#xff0c;可以分为字节流和字符流。根据功能不同&#xff0c;可以分为节点流和处理流。 提供服务过滤操作&#xff08;Filter&#xff09;映射…

后端基础:IO cell的pre driver与post driver的区别

pre driver就是接core电压的部分&#xff0c;一般叫VDD/VSS&#xff0c;post driver就是接pad的高压部分。所以power IO起到一个level shifter的作用&#xff0c;将高压转换为低压。 pre driver和post driver地共用的情况可以节省一个PAD&#xff0c;esd也很好&#xff0c;但是…

华为IMC培训——通信基础与路由协议

目录 环境搭建 wireshark安装 VirtualBox安装 WinPcap安装 eNSP安装 数据在七层模型间的传输过程 路由 静态路由 动态路由 rip OSPF 单臂路由 ——————————————————————————————————————————— 虽然是白嫖的课&#xff…