2024.3.17力扣每日一题——最小高度树

news/2024/7/20 21:36:51 标签: leetcode, 深度优先, 算法, 职场和发展, 数据结构

2024.3.17

      • 题目来源
      • 我的题解
        • 方法一 深度优先遍历
        • 方法二 广度优先遍历
        • 方法三 拓扑排序

题目来源

力扣每日一题;题序:310

我的题解

方法一 深度优先遍历

从每一个节点开始进行深度优先遍历并计算以该节点为根节点的树的深度,使用哈希表存储对应深度有哪些节点,最后取出key最小对应的value的作为结果。超时。

时间复杂度:O( n 2 n^2 n2)
空间复杂度:O(n)

TreeMap<Integer,Set<Integer>> map=new TreeMap<>();
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
    List<Integer>[] g=createTree(n,edges);
    for(int i=0;i<n;i++){
        int minDeep=dfs(g,i,i,-1,1);
        System.out.println(minDeep);
        Set<Integer> l=map.getOrDefault(minDeep,new HashSet<>());
        l.add(i);
        map.put(minDeep,l);
    }
    return new ArrayList<>(map.firstEntry().getValue());
}
public List<Integer>[] createTree(int n,int[][] edges){
    List<Integer>[] g=new ArrayList[n];
    for(int i=0;i<n;i++){
        g[i]=new ArrayList<>();
    }
    for(int[] t:edges){
        int from = t[0];
        int to = t[1];
        g[from].add(to);
        g[to].add(from);
    }
    return g;
}
public int  dfs(List<Integer>[] g,int root,int cur,int pre,int deepth){
    int minDeep=0;
    for(int next:g[cur]){
        if(next!=pre){
            int t=dfs(g,root,next,cur,deepth+1);
            minDeep=Math.max(minDeep,t);
        }
    }
    return minDeep+1;
}

由于一定是树,所以具有最小深度的根节点只有可能最多两个节点作为个根时的深度是相同的。所以,可以求该图中最长的直径,中间节点就是需要求的根节点。所以,不用对所有节点都进行深度优先遍历,只需要遍历两个节点就行了。
时间复杂度:O(n)
空间复杂度:O(n)

//优化版本
TreeMap<Integer,Set<Integer>> map=new TreeMap<>();
List<Integer> path=new ArrayList<>();
int deep=0;
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
    List<Integer>[] g=createTree(n,edges);
    //优化的地方
    List<Integer> p=new ArrayList<>();
    int x=0;
    p.add(x);
    dfs(g,x,-1,1,p);

    p=new ArrayList<>();
    int y=path.get(path.size()-1);
    p.add(y);
    dfs(g,y,-1,1,p);
    int sz=path.size();
    if(sz%2==0){
        return path.subList(sz/2-1,sz/2+1);
    }else{
        return path.subList(sz/2,sz/2+1);
    }
}
public List<Integer>[] createTree(int n,int[][] edges){
    List<Integer>[] g=new ArrayList[n];
    for(int i=0;i<n;i++){
        g[i]=new ArrayList<>();
    }
    for(int[] t:edges){
        int from = t[0];
        int to = t[1];
        g[from].add(to);
        g[to].add(from);
    }
    return g;
}
public void  dfs(List<Integer>[] g,int cur,int pre,int deepth,List<Integer> p){
    if(deepth>deep){
        path=new ArrayList<>(p);
        deep=deepth;
    }
    for(int next:g[cur]) {
        if (next != pre) {
            p.add(next);
            dfs(g, next, cur, deepth + 1, p);
            p.remove(p.size() - 1);
        }
    }
}
方法二 广度优先遍历

在方法一的优化版本基础上,也可以采用广度优先遍历来求最长直径。

时间复杂度:O(n)
空间复杂度:O(n)

TreeMap<Integer,Set<Integer>> map=new TreeMap<>();
List<Integer> path=new ArrayList<>();
int deep=0;
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
    List<Integer>[] g=createTree(n,edges);
    //变化之处在于采用广度优先遍历求最长直径
    int x=0;
    bfs(g,x);
    int y=path.get(path.size()-1);
    bfs(g,y);
    int sz=path.size();
    if(sz%2==0){
        return path.subList(sz/2-1,sz/2+1);
    }else{
        return path.subList(sz/2,sz/2+1);
    }
}
public List<Integer>[] createTree(int n,int[][] edges){
    List<Integer>[] g=new ArrayList[n];
    for(int i=0;i<n;i++){
        g[i]=new ArrayList<>();
    }
    for(int[] t:edges){
        int from = t[0];
        int to = t[1];
        g[from].add(to);
        g[to].add(from);
    }
    return g;
}
public void bfs(List<Integer>[] g,int cur){
    Queue<Integer> queue=new LinkedList<>();
    queue.offer(cur);
    int[] parent=new int[g.length];
    //记录路径
    parent[cur]=-1;
    boolean[] visited=new boolean[g.length];
    visited[cur]=true;
    //最后一层的节点
    int end=cur;
    while(!queue.isEmpty()){
        int sz=queue.size();
        for (int i = 0; i < sz; i++) {
            int t=queue.poll();
            boolean f=false;
            for(int next:g[t]){
                if(visited[next])
                    continue;
                parent[next]=t;
                visited[next]=true;
                queue.offer(next);
                f=true;
            }
            if(!f)
                end=t;
        }
    }
    path.clear();
    while(end!=-1){
        path.add(0,end);
        end=parent[end];
    }
}
方法三 拓扑排序

最小深度也可以看做是一直删除叶子节点,最后剩下的一个或者两个节点就是满足条件的节点。这种每次删除叶子节点的操作实质就是拓扑排序。

时间复杂度:O(n)
空间复杂度:O(n)

public List<Integer> findMinHeightTrees(int n, int[][] edges) {
	//注意:使用拓扑排序需要将只有一个节点的情况单独考虑
    if(n==1)
        return Arrays.asList(0);
    int[] tp=new int[n];
    List<Integer>[] g=createTree(n,edges,tp);
    Queue<Integer> queue=new LinkedList<>();
    for(int i=0;i<n;i++){
        if(tp[i]==1)
            queue.offer(i);
    }
    return tpFunc(g,tp,n,queue);
}
public List<Integer>[] createTree(int n,int[][] edges,int[] tp){
    List<Integer>[] g=new ArrayList[n];
    for(int i=0;i<n;i++){
        g[i]=new ArrayList<>();
    }
    for(int[] t:edges){
        int from = t[0];
        int to = t[1];
        g[from].add(to);
        g[to].add(from);
        tp[from]++;
        tp[to]++;
    }
    return g;
}
public List<Integer> tpFunc(List<Integer>[] g,int[] tp,int remain,Queue<Integer> queue){
    List<Integer> res=new ArrayList<>();
    while(remain>2){
        int sz=queue.size();
        remain-=sz;
        for(int i=0;i<sz;i++){
            int t=queue.poll();
            for(int next:g[t]){
                tp[t]--;
                tp[next]--;
                if(tp[next]==1)
                    queue.offer(next);
            }
        }
        
    }
    while(!queue.isEmpty()){
        res.add(queue.poll());
    }
    return res;
}

有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~


http://www.niftyadmin.cn/n/5470503.html

相关文章

三、keepalived双机热备

一、双机热备概述 1、为什么需要双机热备&#xff1a; 双机热备主要为了解决服务器的单点故障问题。 在主机 MASTER 宕机之后可以马上切换到备选服务器 BACKUP。 服务器规划&#xff1a; 2、克隆产生web01服务器&#xff1a; (1) 基于LNMP克隆生成Web01服务器&#xff1a; (…

口语 4.5

drive me crazy :把我弄疯 drive me nuts my desire for money is fueling me to work harder pump the breaks:踩下刹车 i am a very driven person &#xff1a;我要很大的内驱力 projection&#xff1a;投影 enticing&#xff1a;诱人的 limbic system&#xff1…

Ubuntu中文输入法设置指南:从添加输入源到自定义设置的完整步骤

文章目录 Ubuntu设置中文输入法教程步骤1&#xff1a;打开“设置”应用程序步骤2&#xff1a;进入“区域与语言”设置步骤3&#xff1a;添加中文输入源步骤4&#xff1a;选择中文输入法步骤5&#xff1a;设置默认输入源&#xff08;可选&#xff09;步骤6&#xff1a;使用中文输…

可以写网易云的了!

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 1枚程序媛&#xff0c;大专生&#xff0c;2年时间从1800到月入过万&#xff0c;工作5年买房。 分享成长心得。 259篇原创内容-gzh 后台回复“前端工具”可获取开发工具&#xff0c;持续更新中…

JSON的定义、基本使用二

<script>//定义jsonvar json{"name" : "张三","age" : "18岁","addr" : ["北京","上海","天津"]}//获取数据console.log(json.age)console.log(json.name)console.log(json.addr)</…

python相对路径导包与绝对路径导包的正确方式

【python相对路径导包与绝对路径导包的正确方式】 python相对路径导包与绝对路径导包的正确方式_哔哩哔哩_bilibilipython导包的难题&#xff0c;今天解决了&#xff0c;相对路径导包和绝对路径导包&#xff0c;均可以&#xff01;&#xff01;&#xff01;, 视频播放量 5、弹…

【TB作品】MSP430单片机读取大气压强传感器BMP180

文章目录 实物main所有代码 实物 main #include <msp430.h> #include "stdio.h" #include "OLED.h"#include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h>// P2.2 oled scl // P2.3 oled sda// p…

SV学习笔记(四)

OCP Open Closed Principle 开闭原则 文章目录 随机约束和分布为什么需要随机&#xff1f;为什么需要约束&#xff1f;我们需要随机什么&#xff1f;声明随机变量的类什么是约束权重分布集合成员和inside条件约束双向约束 约束块控制打开或关闭约束内嵌约束 随机函数pre_random…