LeetCode高频题:一棵树的第i个节点的权重ai定义为:从根节到该节点的路径上,红色节点和蓝色节点的数量之差,求所有节点的权值之和

发布于:2023-01-21 ⋅ 阅读:(466) ⋅ 点赞:(0)

LeetCode高频题:一棵树的第i个节点的权重ai定义为:从根节到该节点的路径上,红色节点和蓝色节点的数量之差,求所有节点的权值之和

提示:本题是系列LeetCode的150道高频题,你未来遇到的互联网大厂的笔试和面试考题,基本都是从这上面改编而来的题目
互联网大厂们在公司养了一大批ACM竞赛的大佬们,吃完饭就是设计考题,然后去考应聘人员,你要做的就是学基础树结构与算法,然后打通任督二脉,以应对波云诡谲的大厂笔试面试题!
你要是不扎实学习数据结构与算法,好好动手手撕代码,锻炼解题能力,你可能会在笔试面试过程中,连题目都看不懂!比如华为,字节啥的,足够让你读不懂题
在这里插入图片描述
基础知识:

【1】图数据结构,图的统一数据结构和非标准图的转化算法
【2】图的宽度优先遍历:BFS遍历
【3】图的深度优先遍历:DFS遍历


题目

你拿到了一颗有根树,根节点编号为1,每个节点被染成红色或者蓝色
假设第i个节点的权重ai定义为:从根节到该节点的路径上,红色节点和蓝色节点的数量之差,请你算一下,这个树,所有节点的权值之和。

输入描述:
第一行输入一个整数n,代表节点的数量
第二行输入一个长度为n的字符串,R代表第i个节点被染成了红色,B代表第i个节点被染成了蓝色
接下来的n-1行,每行输入两个正整数u v,代表节点u和v有一条路径链接
1<=n<=10的5次方

输出描述:
所有节点的权值之和


一、审题

示例:

输入
5
RBRBB
2 5
1 5
4 1
3 5

输出:
3


理解示例,往往对解题有很大的帮助

根据输入建一个图

不过图是树,没有环

在这里插入图片描述
我们来统计每个节点的权值

在这里插入图片描述

题目应该说清楚一点,实际上是路径上的R数量-B数量的绝对值

okay,其实我们可以bfs一遍,把每个节点的权值求出来,然后dfs去求累加和

其实完全还可以直接一遍bfs就搞定,直接求权值,然后把累加和加到结果上,看我的文末

注意:这是一家互联网大厂的秋招笔试,题目是真的挺难的,要求你熟悉图的数据结构,图的bfs和dfs

为了给你熟悉图和图的算法,我先将bfs和dfs一起搞的
再给你讲笔试迅速ac的代码【即:其实完全还可以直接一遍bfs就搞定,直接求权值,然后把累加和加到结果上】


先bfs求权重,再dfs求权重累加和试试

这种思路比较清晰

bfs正常从cur=根节点1开始访问,用一个节点Node(内部有编号,颜色,权重)表示
然后不断根据当前节点cur,推cur的邻居的权重,非常简单的

关键就是在建图:

(1)图怎么表示??——其实一切关于图的算法题,算法是真的不难,就是bfs和dfs,但是图的表示很难

我自己习惯自己的图表示方法,标准图改变来的
这个文章,我讲过标准图的构建方法:
【1】图数据结构,图的统一数据结构和非标准图的转化算法

但是本题,实际上不是图,而是一个树而已
所以,我们也不用建立什么最小生成树,我们只需要统计每个节点的权值,而不需要管边的事情

所以,本题的图节点,完全可以不要边:

        //图节点--本题中不需要边
        public static class Node{
            public int id;
            public char color;//染色
            public int weight;
            public List<Node> nexts;//直接邻居

            public Node(int i, char c){
                id = i;
                color = c;
                weight = 0;//初始化权重没有
                nexts = new ArrayList<>();//邻居没有空
            }
        }

每个节点Node内部有自己的id,颜色,权重初始化为0,还有自己的直接邻居nexts,用列表挂好

然后就可以建一个无边的图了

        //图
        public static class Graph{
            public HashMap<Integer, Node> nodes;//图的点集合

            public Graph(){
                nodes = new HashMap();//用哈希表表示我的节点
            }
        }

实际上这个图,内部放的是一堆节点集合,
题目输入的每一行,就是连节点,相邻有链接的,把两者相互做直接邻居就行
当然了,为了寻找方便,我们用哈希表nodes存编号id和整个节点,这样既能快速找到根节点1,也能知道这个节点的属性就在Node中

这个建表图的过程,在输入阶段就要搞定

我手撕代码搞定这个过程

            Scanner in = new Scanner(System.in);

            int N = in.nextInt();//N
            String s = in.next();
            char[] str = s.toCharArray();//字符串
            //计划放入一个邻接表中
            Graph graph = new Graph();
            for (int i = 0; i < N - 1; i++) {//N-1行
                int u = in.nextInt();
                int v = in.nextInt();//节点编号//u对应的节点染色

                if (!graph.nodes.containsKey(u)) {
                    graph.nodes.put(u, new Node(u, str[u - 1]));//没有才加这个节点
                }
                if (!graph.nodes.containsKey(v)) {
                    graph.nodes.put(v, new Node(v, str[v - 1]));
                }

                Node from = graph.nodes.get(u);
                Node to = graph.nodes.get(v);
                from.nexts.add(to);
                to.nexts.add(from);//互加邻居
            }

就是跟题目说的ACM输入格式一样的东西

只不过每次输入N-1行u v时,我们要把节点,还有直接邻居这些串好就行了


然后利用bfs广播求这个图graph的每个节点的权值

咋做呢?

至于图的bfs咋搞,实在是太简单了,就是跟二叉树的bfs一模一样
你可以看看我写过这个图的bfs代码:
【2】图的宽度优先遍历:BFS遍历

BFS特点用队列搞定,弹出打印,结算结果。

比如刚刚从队列中弹出cur节点,是根节点1,根是红色R,代表权重就是1,
如果是绿色权重认为是-1

看看cur的邻居next们
4和5都一样,颜色是蓝色
故4和5的权重都是
cur.weight + (-1)=1-1=0
在这里插入图片描述
懂?

中途访问过的节点,统统放入set中,避免重复遍历

这样一遍就能把所有的节点的权重搞定了

手撕代码:

        //有了图,拿着graph,从根节点cur=1出发,bfs广播去求各个节点权重
        public static void bfsWriteWeight(Graph graph){
            //先找图中的根节点1
            Node head = graph.nodes.get(1);
            head.weight = head.color == 'R' ? 1 : -1;//红色染1,蓝色染-1

            LinkedList<Node> queue = new LinkedList<>();//队列用来做bfs
            HashSet<Node> set = new HashSet<>();//访问过的加入set,不要访问了
            queue.add(head);
            set.add(head);

            while (!queue.isEmpty()){
                Node cur = queue.poll();//弹出结算
                for(Node next : cur.nexts){
                    //所有邻居,根据cur的weight和next的染色决定next的权重
                    next.weight = cur.weight + (next.color == 'R' ? 1 : -1);

                    //然后将其压入队列
                    if (!set.contains(next)){
                        queue.add(next);
                        set.add(next);
                    }
                }
            }
        }

然后有了完整的graph,也就是权重ai都有了,就挨个dfs遍历,把ai加入结果ans,返回就是结果了

关于图的dfs,我也说过了
【3】图的深度优先遍历:DFS遍历

DFS是用栈实现的,最重要的2点,压入时结算,弹出不结算,还有就是看cur的邻居之前,必须先把cur再次压栈,保证一条道走到黑,回来还能找到我cur,只压cur的一个邻居,立马break;

从cur出发,ans=1
到4,ans+=0,立马break;
返回cur,然后再去看cur的邻居5
到5,ans+=0,立马break;
返回cur,遍历结束,返回ans
在这里插入图片描述

这个dfs代码必须熟悉啊
手撕代码:

        //graph已经有了权重,那就dfs去取绝对值求和返回
        public static int dfsSum(Graph graph){
            Node head = graph.nodes.get(1);//根开始遍历吧

            Stack<Node> stack = new Stack<>();//用来做dfs的,复习一下
            HashSet<Node> set = new HashSet<>();//记录遍历过得
            stack.push(head);
            set.add(head);

            int ans = Math.abs(head.weight);//加入结算
            while (!stack.isEmpty()){
                Node cur = stack.pop();//弹出不结算,加入时结算
                for(Node next : cur.nexts){
                    if (!set.contains(next)){
                        //然后重新把cur压栈,里面退出,一条道走到黑,这是dfs绝的重点地方
                        stack.push(cur);//还必须——先压入cur,这样保证回去还能找到cur
                        //加邻居
                        stack.push(next);
                        ans += Math.abs(next.weight);
                        set.add(next);
                        break;
                    }
                }
            }

            return ans;
        }

其实dfs和bfs就是非常简单的关于图的算法了

只要你搞懂了图,怎么表示节点,邻居,还有边(需要考虑边的时候就要把graph中加入边集合哦),剩下的事情都好办了

okay,本题主函数,只需要先调用bfs算节点们的权重,然后用dfs走一圈,把结果累加就行

手撕代码,关键关键的关键

我说了,是图的表达,用你熟悉的图的表达方法,去表示就行

没有什么对错,好坏

        public static void main(String[] args) {
            Scanner in = new Scanner(System.in);

            int N = in.nextInt();//N
            String s = in.next();
            char[] str = s.toCharArray();//字符串
            //计划放入一个邻接表中
            Graph graph = new Graph();
            for (int i = 0; i < N - 1; i++) {//N-1行
                int u = in.nextInt();
                int v = in.nextInt();//节点编号//u对应的节点染色

                if (!graph.nodes.containsKey(u)) {
                    graph.nodes.put(u, new Node(u, str[u - 1]));//没有才加这个节点
                }
                if (!graph.nodes.containsKey(v)) {
                    graph.nodes.put(v, new Node(v, str[v - 1]));
                }

                Node from = graph.nodes.get(u);
                Node to = graph.nodes.get(v);
                from.nexts.add(to);
                to.nexts.add(from);//互加邻居
            }

            //有了图,拿着graph,从根节点cur=1出发,bfs广播去求各个节点权重
            bfsWriteWeight(graph);

            //graph已经有了权重,那就dfs去取绝对值求和返回
            int ans = dfsSum(graph);

            System.out.println(ans);

        }

测试:结果

5
RBRBB
2 5
1 5
4 1
3 5
3

这样就完成了


笔试快速ac代码:其实完全还可以直接一遍bfs就搞定,直接求权值,然后把累加和加到结果上

你上面bfs不是已经彻底搞定了权重吗???

本身bfs遍历过所有的节点,而且把他们的权重都求出来了,所以呢,就干呗,中途加入ans即可

手撕代码:

        //有了图,拿着graph,从根节点cur=1出发,bfs广播去求各个节点权重——同时直接把它加入ans
        public static int bfsWriteWeightSum(Graph graph){
            //先找图中的根节点1
            Node head = graph.nodes.get(1);
            head.weight = head.color == 'R' ? 1 : -1;//红色染1,蓝色染-1

            LinkedList<Node> queue = new LinkedList<>();//队列用来做bfs
            HashSet<Node> set = new HashSet<>();//访问过的加入set,不要访问了
            queue.add(head);
            set.add(head);

            int ans = 0;
            while (!queue.isEmpty()){
                Node cur = queue.poll();//弹出结算
                ans += Math.abs(cur.weight);//结算结果

                for(Node next : cur.nexts){
                    //所有邻居,根据cur的weight和next的染色决定next的权重
                    next.weight = cur.weight + (next.color == 'R' ? 1 : -1);

                    //然后将其压入队列
                    if (!set.contains(next)){
                        queue.add(next);
                        set.add(next);
                    }
                }
            }

            return ans;
        }

测试结果:

5
RBRBB
2 5
1 5
4 1
3 5
3

很OK

笔试你就调用这个函数就搞定了,非常容易


总结

提示:重要经验:

0)图的难点在于图的数据结构表示,搞成你熟悉的方法就行,另外,图的算法是不难的,无非就是bfs和dfs运用而已
1)BFS特点用队列搞定,弹出打印,结算结果。
2)DFS是用栈实现的,最重要的2点,压入时结算,弹出不结算,还有就是看cur的邻居之前,必须先把cur再次压栈,保证一条道走到黑,回来还能找到我cur,只压cur的一个邻居,立马break;
3)当然了,一遍bfs搞定也行的,最关键的还是图数据结构的构建
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到