LC-1617. 统计子树中城市之间最大距离(回溯+求树的直径)

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

1617. 统计子树中城市之间最大距离

难度困难113

给你 n 个城市,编号为从 1n 。同时给你一个大小为 n-1 的数组 edges ,其中 edges[i] = [ui, vi] 表示城市 uivi 之间有一条双向边。题目保证任意城市之间只有唯一的一条路径。换句话说,所有城市形成了一棵

一棵 子树 是城市的一个子集,且子集中任意城市之间可以通过子集中的其他城市和边到达。两个子树被认为不一样的条件是至少有一个城市在其中一棵子树中存在,但在另一棵子树中不存在。

对于 d1n-1 ,请你找到城市间 最大距离 恰好为 d 的所有子树数目。

请你返回一个大小为 n-1 的数组,其中第 d 个元素(下标从 1 开始)是城市间 最大距离 恰好等于 d 的子树数目。

请注意,两个城市间距离定义为它们之间需要经过的边的数目。

示例 1:

img

输入:n = 4, edges = [[1,2],[2,3],[2,4]]
输出:[3,4,0]
解释:
子树 {1,2}, {2,3} 和 {2,4} 最大距离都是 1 。
子树 {1,2,3}, {1,2,4}, {2,3,4} 和 {1,2,3,4} 最大距离都为 2 。
不存在城市间最大距离为 3 的子树。

示例 2:

输入:n = 2, edges = [[1,2]]
输出:[1]

示例 3:

输入:n = 3, edges = [[1,2],[2,3]]
输出:[2,1]

提示:

  • 2 <= n <= 15
  • edges.length == n-1
  • edges[i].length == 2
  • 1 <= ui, vi <= n
  • 题目保证 (ui, vi) 所表示的边互不相同。

回溯(枚举子集+树的直径)

本题结合了 78 题和 1245 题:枚举城市的子集(子树),求这棵子树的直径。

需要注意的是,枚举的子集不一定是一棵树,可能是森林(多棵树,多个连通块)。我们可以在计算树形 DP 的同时去统计访问过的点,看看是否与子集相等,只有相等才是一棵树。

class Solution {
    private List<Integer>[] g;
    private boolean[] inSet, vis;
    private int[] ans;
    private int n, diameter;
    public int[] countSubgraphsForEachDiameter(int n, int[][] edges) {
        this.n = n;
        g = new ArrayList[n];
        Arrays.setAll(g, e -> new ArrayList<>());
        for(int[] e : edges){
            int x = e[0] - 1, y = e[1] - 1; // 编号改为从 0 开始
            g[x].add(y);
            g[y].add(x); // 建树
        }
        ans = new int[n - 1];
        inSet = new boolean[n];
        f(0);
        return ans;
    }

    private void f(int i){
        if(i == n){
            for(int v = 0; v < n; v++){
                if(inSet[v]){
                    vis = new boolean[n];
                    diameter = 0;
                    dfs(v);
                    break;
                }
            }
            if(diameter > 0 && Arrays.equals(vis, inSet)){
                ++ans[diameter-1];
            }
            return;
        }
        // 不选城市i
        f(i+1);
        // 选城市i
        inSet[i] = true;
        f(i+1);
        inSet[i] = false; // 恢复现场
    }
    
    // 求树的直径
    private int dfs(int x){
        vis[x] = true;
        int maxLen = 0;
        for(int y : g[x]){
            if(!vis[y] && inSet[y]){
                int ml = dfs(y) + 1;
                diameter = Math.max(diameter, maxLen + ml);
                maxLen = Math.max(maxLen, ml);
            }
        }
        return maxLen;
    }
}

78. 子集

难度中等1959

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素 互不相同
//  枚举I位置选还是不选
class Solution {
    List<List<Integer>> res;
    List<Integer> tmp;
    int[] nums;
    int n;
    public List<List<Integer>> subsets(int[] nums) {
        res = new ArrayList<>();
        tmp = new ArrayList<>();
        this.nums = nums;
        n = nums.length;
        dfs(0);
        return res;
    }

    public void dfs(int i){
        if(i == n) {
            res.add(new ArrayList<Integer>(tmp));
            return;
        }
        dfs(i+1);
        tmp.add(nums[i]);
        dfs(i+1);
        tmp.remove(tmp.size() - 1);
        return;
    }
}

// 枚举I位置选哪个
class Solution {
    List<List<Integer>> res;
    List<Integer> tmp;
    int[] nums;
    int n;
    public List<List<Integer>> subsets(int[] nums) {
        res = new ArrayList<>();
        tmp = new ArrayList<>();
        this.nums = nums;
        n = nums.length;
        dfs(0);
        return res;
    }

    public void dfs(int i){
        res.add(new ArrayList<Integer>(tmp));
        if(i == n) {
            return;
        }
        for(int j = i; j < n; j++){
            tmp.add(nums[j]);
            dfs(j+1);
            tmp.remove(tmp.size() - 1);
        }
    }
}

543. 二叉树的直径

难度简单1272

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

示例 :
给定二叉树

          1
         / \
        2   3
       / \     
      4   5    

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

**注意:**两结点之间的路径长度是以它们之间边的数目表示。

class Solution {
    //遍历每一个节点,以每一个节点为中心点计算最长路径(左子树边长+右子树边长),更新全局变量max。
    int res = 0;
    public int diameterOfBinaryTree(TreeNode root) {
        if(root == null) return 0;
        dfs(root);
        return res;
    }
    
    private int dfs(TreeNode root){
        if(root.left == null && root.right == null){
            return 0; //不能是root == null 
        }
        // 获得左右子树的最长路径
        int left = root.left == null ? 0 : dfs(root.left) + 1;
        int right = root.right == null ? 0 : dfs(root.right) + 1;
        res = Math.max(res, left + right); 
        return Math.max(left, right); // 返回左右子树长度较长的那一个
    }
    
}

leetcode — 1245.树的直径

https://blog.csdn.net/qq_29051413/article/details/108617150

思路:

1、遍历 edges 数组,把它转为邻接链表表示的图;

2、定义一个全局变量 ans 表示最终返回的最长路径的边数;

3、遍历图,对每一个结点进行 DFS;

DFS的实现:

1、遍历当前结点的所有子结点进行 DFS,找出长度最长的两个路径 max1 和 max2;

2、更新 ans = Math.max(ans, max1 + max2),其中 max1 + max2 就好像是以当前点为圆心,加上两个半径,得到直径;

3、DFS 返回的是 Math.max(max1, max2);

class Solution {
    private Map<Integer, List<Integer>> g = new HashMap();
    private int res;
    
    public int treeDiameter(int[][] edges) {
        for (int[] it : edges) {
            if (!g.containsKey(it[0])) g.put(it[0], new ArrayList());
            if (!g.containsKey(it[1])) g.put(it[1], new ArrayList());
            g.get(it[0]).add(it[1]);
            g.get(it[1]).add(it[0]);
        }
        dfs(0, -1);
        return res;
    }
    
    private int dfs(int cur, int pre) {
        int d1 = 0, d2 = 0;
        for (int i : g.get(cur)) {
            if (i != pre) {
                int d = dfs(i, cur);
                if (d > d1) {
                    int tmp = d1; d1 = d; d2 = tmp;
                } else if (d > d2) d2 = d;
            }
        }
        res = Math.max(res, d1 + d2);
        return d1 + 1;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值