LeetCode第179场周赛(Weekly Contest 179)解题报告

这周虽然AK了,但感觉不太满意,其实题目都不难,脑袋不清醒,不然可以更快,再接再厉吧。

这周的题,总体上,两道都是关于树的遍历,所以其实也不算很难。主要花时间思考的就是第二题,只要知道思路,代码其实很简单。我自己做的时候,第二题看了之后,一时没想法,做完第三,第四题后,才返回来做第二题的。

第一题:模拟 + 构造。

第二题:思维。

第三题:树的遍历  DFS 或者 BFS。

第四题:树的遍历  DFS。

详细题解如下。


1.生成每种字符都是奇数个的字符串(Generate A String With Characters That Have Odd Counts)

          AC代码(C++)

2. 灯泡开关 III(Bulb Switcher III)

          AC代码(方法一、C++)

          AC代码(方法二、C++)

3.通知所有员工所需的时间(Time Needed To Inform All Employees)

          AC代码(方法一、DFS  C++)

          AC代码(方法二、BFS  C++)

4.T 秒后青蛙的位置(Frog Position After T Seconds)

          AC代码(C++)


LeetCode第179场周赛地址:

https://leetcode-cn.com/contest/weekly-contest-179


1.生成每种字符都是奇数个的字符串(Generate A String With Characters That Have Odd Counts)

题目链接

https://leetcode-cn.com/problems/generate-a-string-with-characters-that-have-odd-counts/

题意

给你一个整数 n,请你返回一个含 n 个字符的字符串,其中每种字符在该字符串中都恰好出现 奇数次 。

返回的字符串必须只含小写英文字母。如果存在多个满足题目要求的字符串,则返回其中任意一个即可。

示例 1:

输入:n = 4
输出:"pppz"
解释:"pppz" 是一个满足题目要求的字符串,因为 'p' 出现 3 次,且 'z' 出现 1 次。当然,还有很多其他字符串也满足题目要求,比如:"ohhh" 和 "love"。

提示:

  • 1 <= n <= 500

解题思路

 

这道题很简单,不要受到示例的影响。其实就是,要求构造的字符串中,出现的那些字母,次数都是奇数。

那么当 n 为奇数的时候,我们直接全部同一个字母,就满足条件。

当 n 为偶数的时候,那就把 n - 1 (奇数)同一个字母,然后剩下的 单独一个,就其他字母。

 

AC代码(C++)

class Solution {
public:
    string generateTheString(int n) {
        string res = "";
        if(n % 2 == 1)
        {
            for(int i = 0;i < n; ++i) res += 'a';
        }
        else
        {
            for(int i = 0;i < n - 1; ++i) res += 'a';
            res += 'b';
        }
        return res;
    }
};

 


2. 灯泡开关 III(Bulb Switcher III)

题目链接

https://leetcode-cn.com/problems/bulb-switcher-iii/

题意

房间中有 n 枚灯泡,编号从 1 到 n,自左向右排成一排。最初,所有的灯都是关着的。

在 k  时刻( k 的取值范围是 0 到 n - 1),我们打开 light[k] 这个灯。

灯的颜色要想 变成蓝色 就必须同时满足下面两个条件:

  • 灯处于打开状态。
  • 排在它之前(左侧)的所有灯也都处于打开状态。

请返回能够让 所有开着的 灯都 变成蓝色 的时刻 数目 。

示例 1:

示例有图,具体看链接

输入:light = [2,1,3,5,4]
输出:3
解释:所有开着的灯都变蓝的时刻分别是 1,2 和 4 。

示例 2:

输入:light = [4,1,2,3]
输出:1
解释:所有开着的灯都变蓝的时刻是 3(index-0)。
第 4 个灯在时刻 3 变蓝。

提示:

  • n == light.length
  • 1 <= n <= 5 * 10^4
  • light 是 [1, 2, ..., n] 的一个排列。

解题思路

这道题,乍看起来,很复杂,但是,如果能从题目抽象出,具体问什么,那就很容易了。

根据题目,我们分析,如果对于当前 时刻 k ,我们打开了 某个灯的编号 light[ k ],如果要求所有开着的灯,都是变成蓝色的,也就是,应该前面的 k - 1 时刻,刚好把 1 2 3 .. (light[ k ] - 1) 打开了,也就是,在当前 k 时刻,打开的灯编号应该是 1 2 3 4 .... light[ k ]

方法一、利用编号求和

如果当前所有灯都是蓝色,那么亮灯的编号和必定等于1+2+...+亮灯数目,

因此,我们维护,此时前面亮灯的编号和,和如果要是都蓝色的情况下,当前理论上的亮灯编号和。

也就是,一个值 cur ,是不断求,根据时刻 k 来亮灯的编号

一个值 sum ,是求 1 + 2 + 3 + ... + 亮灯数(即 时刻+ 1).

如果两个值,相等,说明这个时刻的亮着的灯,都变成了蓝色。

(如果不相等,说明这个时候的亮灯编号前,有一个灯没亮,亮的是后面的灯,那么求出来的 cur ,就不会等于 sum)

 

方法二、维护最大值

也就是,当前时刻 k,本来的编号最大值是 k + 1,这样子才是蓝色(因为对于时刻 k,应该都是亮 1 2 3 ... k+1  ),然后不断的更新编号最大值,判断如果 == ,那么就是这个时刻是蓝色的。

 

AC代码(方法一、C++)

class Solution {
public:
    int numTimesAllBlue(vector<int>& light) {
        int n = light.size();
        int res = 0;
        
        int sum = 0;
        int cur = 0;
        
        for(int i = 0;i < n; ++i)
        {
            sum += (i + 1);
            cur += light[i];
            if(sum == cur) ++res;
        }
        
        return res;
    }
};

 

AC代码(方法二、C++)

class Solution {
public:
    int numTimesAllBlue(vector<int>& light) {
        int n = light.size();
        int res = 0;
        int mx = 0;
        
        for(int i = 0;i < n; ++i)
        {
            mx = max(mx, light[i]);
            if(mx == i + 1) ++res;
        }
        
        return res;
    }
};

 


3.通知所有员工所需的时间(Time Needed To Inform All Employees)

题目链接

https://leetcode-cn.com/problems/time-needed-to-inform-all-employees/

题意

公司里有 n 名员工,每个员工的 ID 都是独一无二的,编号从 0 到 n - 1。公司的总负责人通过 headID 进行标识。

在 manager 数组中,每个员工都有一个直属负责人,其中 manager[i] 是第 i 名员工的直属负责人。对于总负责人,manager[headID] = -1。题目保证从属关系可以用树结构显示。

公司总负责人想要向公司所有员工通告一条紧急消息。他将会首先通知他的直属下属们,然后由这些下属通知他们的下属,直到所有的员工都得知这条紧急消息。

第 i 名员工需要 informTime[i] 分钟来通知它的所有直属下属(也就是说在 informTime[i] 分钟后,他的所有直属下属都可以开始传播这一消息)。

返回通知所有员工这一紧急消息所需要的 分钟数 

示例 2:

示例有图,具体看链接

输入:n = 6, headID = 2, manager = [2,2,-1,2,2,2], informTime = [0,0,1,0,0,0]
输出:1
解释:id = 2 的员工是公司的总负责人,也是其他所有员工的直属负责人,他需要 1 分钟来通知所有员工。
上图显示了公司员工的树结构。

示例 3:

输入:n = 7, headID = 6, manager = [1,2,3,4,5,6,-1], informTime = [0,6,5,4,3,2,1]
输出:21
解释:总负责人 id = 6。他将在 1 分钟内通知 id = 5 的员工。
id = 5 的员工将在 2 分钟内通知 id = 4 的员工。
id = 4 的员工将在 3 分钟内通知 id = 3 的员工。
id = 3 的员工将在 4 分钟内通知 id = 2 的员工。
id = 2 的员工将在 5 分钟内通知 id = 1 的员工。
id = 1 的员工将在 6 分钟内通知 id = 0 的员工。
所需时间 = 1 + 2 + 3 + 4 + 5 + 6 = 21 。

提示:

  • 1 <= n <= 10^5
  • 0 <= headID < n
  • manager.length == n
  • 0 <= manager[i] < n
  • manager[headID] == -1
  • informTime.length == n
  • 0 <= informTime[i] <= 1000
  • 如果员工 i 没有下属,informTime[i] == 0 。
  • 题目 保证 所有员工都可以收到通知。

 

解题分析

根据题目意思,再结合示例的说明,发现就是将从属关系,变为一棵树(有向树),同时通知时间,相当于是边的长度。

那么这道题就是,要求出,根节点,到所有叶节点花费的时间的最大值(也就是树的遍历)

 

树的遍历(有向树),DFS 和 BFS 都可以使用 

无论是,DFS还是BFS,实际上都是每个点,都遍历了一次,时间复杂度和空间复杂度,都是 O(N)

 

AC代码(方法一、DFS  C++)

const int MAXN = 1e5 + 10;
class Solution {
public:
    
    vector<int> G[MAXN];
    int ans = 0;
    
    void dfs(int x, int n, vector<int>& informTime, int sum)
    {
        if(G[x].size() == 0)
        {
            ans = max(ans, sum);  // 当前点没有可以往下了,说明,这个点就是叶节点,那么更新总时间
            return;
        }
        for(int i = 0;i < G[x].size(); ++i)  // 继续 DFS 下一个点
        {
            dfs(G[x][i], n, informTime, sum + informTime[x]);
        }
    }
    
    int numOfMinutes(int n, int headID, vector<int>& manager, vector<int>& informTime) {
        
        for(int i = 0;i < n; ++i)
        {
            if(manager[i] == -1) continue;
            G[manager[i]].push_back(i);
        }
        ans = 0;
        dfs(headID, n, informTime, 0);  // DFS,从当前根节点开始遍历
        return ans;
        
        
        
    }
};

 

AC代码(方法二、BFS  C++)

const int MAXN = 1e5 + 10;
class Solution {
public:
    
    vector<int> G[MAXN];
    int dist[MAXN];
    int vis[MAXN];
    
    int numOfMinutes(int n, int headID, vector<int>& manager, vector<int>& informTime) {
        // 树的邻接矩阵
        for(int i = 0;i < n; ++i)
        {
            if(manager[i] == -1) continue;
            G[manager[i]].push_back(i);
        }

        memset(vis, 0, sizeof(vis));
        memset(dist, 0, sizeof(dist));
        
        queue<int> q;  // BFS模板,利用队列
        while(!q.empty()) q.pop();
        
        int ans = 0;
        
        q.push(headID);
        vis[headID] = 1;
        dist[headID] = 0;
        
        while(!q.empty())
        {
            int x = q.front();
            q.pop();
            ans = max(ans, dist[x]);   // 每次都取,根节点到当前节点的花费时间最大值
            
            for(int i = 0;i < G[x].size(); ++i)  // 从 x 可以到 其他的点
            {
                int y = G[x][i];
                if(vis[y] == 1) continue;
                
                vis[y] = 1;
                dist[y] = dist[x] + informTime[x];   // x 到 y,那么 y 的时间,就是 x 的时间,再加上 x 通知 y 的时间
                q.push(y);
            }
            
        }
        
        return ans;
        
    }
};

4.T 秒后青蛙的位置(Frog Position After T Seconds)

题目链接

https://leetcode-cn.com/problems/frog-position-after-t-seconds/

题意

给你一棵由 n 个顶点组成的无向树,顶点编号从 1 到 n。青蛙从 顶点 1 开始起跳。规则如下:

  • 在一秒内,青蛙从它所在的当前顶点跳到另一个 未访问 过的顶点(如果它们直接相连)。
  • 青蛙无法跳回已经访问过的顶点。
  • 如果青蛙可以跳到多个不同顶点,那么它跳到其中任意一个顶点上的机率都相同。
  • 如果青蛙不能跳到任何未访问过的顶点上,那么它每次跳跃都会停留在原地。

无向树的边用数组 edges 描述,其中 edges[i] = [fromi, toi] 意味着存在一条直接连通 fromi 和 toi 两个顶点的边。

返回青蛙在 t 秒后位于目标顶点 target 上的概率。

示例 1:

示例有图,具体看链接

输入:n = 7, edges = [[1,2],[1,3],[1,7],[2,4],[2,6],[3,5]], t = 2, target = 4
输出:0.16666666666666666 
解释:上图显示了青蛙的跳跃路径。青蛙从顶点 1 起跳,第 1 秒 有 1/3 的概率跳到顶点 2 ,然后第 2 秒 有 1/2 的概率跳到顶点 4,因此青蛙在 2 秒后位于顶点 4 的概率是 1/3 * 1/2 = 1/6 = 0.16666666666666666 。 

示例 3:

输入:n = 7, edges = [[1,2],[1,3],[1,7],[2,4],[2,6],[3,5]], t = 20, target = 6
输出:0.16666666666666666

提示:

  • 1 <= n <= 100
  • edges.length == n-1
  • edges[i].length == 2
  • 1 <= edges[i][0], edges[i][1] <= n
  • 1 <= t <= 50
  • 1 <= target <= n
  • 与准确值误差在 10^-5 之内的结果将被判定为正确。

 

解题分析

根据题目,结合示例,发现也是一个树的问题,但是这个问题,是一个无向树(可是由于题目说明了一定是从 1 出发,其实也就是变成了一个单向的了,但是由于题目已经说明是无向树,为了防止数据上设陷阱,我们存的树结构,还是用无向树的邻接矩阵来存储)

也就是,当我们从根节点出发,要求判断,T 秒的时间,出现在指定位置的概率。因为这个有很多种可能达到,所以我们用 DFS,找到所有可能,把概率值求和,就是答案。

那么从某一个点出发,如果它有 cnt 个子节点,那么这个点到达任意一个子节点的概率 = 当前点概率 * (1.0 / cnt)。

那么我们就用DFS从根节点去搜索,用一个值去记录,到达当前节点的概率。

  • 假如,这个点就是目标点,同时我们还要考虑:如果它没有子节点了,说明会一直呆在这个点,即使还有时间,也动不了(示例 3);要么即使它还有子节点,但是时间刚好走完了(不会继续往下走了),那么也是会处于目标点。这个时候,就把此时到达目标点的概率累加起来(也就是答案)。那么其他情况,即使到达了目标点,但是由于不会呆在这个点上,会继续向其他点走,也就是此时这条路线的DFS已经没必要继续下去了,就进行剪枝。
  • 如果这个达到,这个点,已经时间为 0 了,说明也不用继续往下DFS了
  • 剩下的,就表示,还可以继续往下走,也就是找到所有 x 可以走到的 y,那么继续 DFS 的时候:时间 - 1,概率 = x 的概率 * (1.0 / cnt)。(注意这里要回溯,因为可能同一个点,还有其他路线到达,万一有这种情况)

那么总时间复杂度应该是 O(N * T),我们每个点,都遍历一次,同时遍历一次,还要注意 最多 T 的时间

 

AC代码(C++)

const int MAXN = 115;

class Solution {
public:
    
    int vis[MAXN];
    vector<int> G[MAXN];
    
    double ans = 0.0;
    
    void dfs(int x, int t, int target, double val)
    {
        int cnt = 0;
        for(int i = 0;i < G[x].size(); ++i) // 统计这个点,还可以走到几个点
            if(vis[G[x][i]] == 0) ++cnt;

        if(x == target)  // 如果是目标值
        {
            if(cnt == 0 || t == 0)  // 要求是,不能再继续往下移动(不然就无法呆在这个点上),也就是它是叶节点,或者没时间了
            {
                ans += val;
                return;
            }
            else
                return;
        }
        if(t == 0) return;
        
        // 继续往下走
        for(int i = 0;i < G[x].size(); ++i)
        {
            if(vis[G[x][i]] == 1) continue;
            vis[G[x][i]] = 1;
            dfs(G[x][i], t - 1, target, val * (1.0 / cnt));  // 概率的更新,还有时间 - 1
            vis[G[x][i]] = 0;
        }  
        
    }
    
    
    double frogPosition(int n, vector<vector<int>>& edges, int t, int target) {
        // 无向图
        for(auto e : edges)
        {
            G[e[0]].push_back(e[1]);
            G[e[1]].push_back(e[0]);
        }
        
        memset(vis, 0, sizeof(vis));

        vis[1] = 1;
        dfs(1, t, target, 1.0);  // 其实概率是 1
        
        return ans;
    }
};

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值