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

这周的周赛,总体不难,主要是第二题,实现有点麻烦,写的有点久,菜鸡呀,看了一些前面大佬的代码,是真的简洁。最后一题,就是一个简单的 DP 问题,达不到 hard 的难度。

第一题:字符串操作。

第二题:模拟。

第三题:分析。

第四题:DP。

详细题解如下。


1.重新格式化字符串(Reformat the String)

          AC代码(C++)

2. 点菜展示表(Display Table of Food Orders in A Restaurant)

          AC代码(C++)

3.数青蛙(Minimum Number of Frogs Croaking)

          AC代码(C++)

4.生成数组(Build Array Where You Can Find the Maximum Exactly K Comparisons)

          AC代码(C++)


LeetCode第185场周赛地址:

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


1.重新格式化字符串(Reformat the String)

题目链接

https://leetcode-cn.com/problems/reformat-the-string/

题意

给你一个混合了数字和字母的字符串 s,其中的字母均为小写英文字母。

请你将该字符串重新格式化,使得任意两个相邻字符的类型都不同。也就是说,字母后面应该跟着数字,而数字后面应该跟着字母。

请你返回 重新格式化后 的字符串;如果无法按要求重新格式化,则返回一个 空字符串 。

示例 1:

输入:s = "a0b1c2"
输出:"0a1b2c"
解释:"0a1b2c" 中任意两个相邻字符的类型都不同。 "a0b1c2", "0a1b2c", "0c2a1b" 也是满足题目要求的答案。

示例 2:

输入:s = "leetcode"
输出:""
解释:"leetcode" 中只有字母,所以无法满足重新格式化的条件。

示例 4:

输入:s = "covid2019"
输出:"c2o0v1i9d"

提示:

  • 1 <= s.length <= 500
  • s 仅由小写英文字母和/或数字组成。

解题思路

根据题意,进行分析。我们可以知道,要分别统计数字和字母,然后间隔放。

那么,什么时候不成立呢?

  • 两个数量一样的时候,可以,那么谁在前都行
  • 数字数量多 1,那么由于数字更多,先放数字,这样子后面就是一样的
  • 字母 数量 多 1,那么先放 字母
  • 剩下的情况,就无法构成一个要求字符串

所以,我们先分别统计数字和字母。然后先放 数量多的那个,再放另一个。(相同数量数量多都是一样)。

这样子放到最后,根据 数量多的,比如 数字,数字一直放(但是可能字母不够,所以放数字的时候,检查字母还有没有,没有就不放字母)

AC代码(C++)

class Solution {
public:
    string reformat(string s) {
        vector<char> a, b;  // 分别统计 数字 和 字母
        for(auto c : s)
        {
            if(c >= '0' && c <= '9') a.push_back(c);
            else b.push_back(c);
        }
        if(a.size() < b.size()) swap(a, b);  // 将数量多的,放在前面

        if(a.size() - b.size() > 1) return "";  // 数量超过 1,那么就无法构成

        string res = "";
        for(int i = 0;i < a.size(); ++i)  // 轮流间隔放,先放数量多的
        {
            res += a[i];
            if(i < b.size()) res += b[i];
        }

        return res;
    }
};

 


2. 点菜展示表(Display Table of Food Orders in A Restaurant)

题目链接

https://leetcode-cn.com/problems/display-table-of-food-orders-in-a-restaurant/

题意

给你一个数组 orders,表示客户在餐厅中完成的订单,确切地说, orders[i]=[customerNamei,tableNumberi,foodItemi] ,其中 customerNamei 是客户的姓名,tableNumberi 是客户所在餐桌的桌号,而 foodItemi 是客户点的餐品名称。

请你返回该餐厅的 点菜展示表 。在这张表中,表中第一行为标题,其第一列为餐桌桌号 “Table” ,后面每一列都是按字母顺序排列的餐品名称。接下来每一行中的项则表示每张餐桌订购的相应餐品数量,第一列应当填对应的桌号,后面依次填写下单的餐品数量。

注意:客户姓名不是点菜展示表的一部分。此外,表中的数据行应该按餐桌桌号升序排列。

示例 1:

输入:orders = [["David","3","Ceviche"],["Corina","10","Beef Burrito"],["David","3","Fried Chicken"],["Carla","5","Water"],["Carla","5","Ceviche"],["Rous","3","Ceviche"]]
输出:[["Table","Beef Burrito","Ceviche","Fried Chicken","Water"],["3","0","2","1","0"],["5","0","1","0","1"],["10","1","0","0","0"]] 
解释:
点菜展示表如下所示:
Table,Beef Burrito,Ceviche,Fried Chicken,Water
3    ,0           ,2      ,1            ,0
5    ,0           ,1      ,0            ,1
10   ,1           ,0      ,0            ,0
对于餐桌 3:David 点了 "Ceviche" 和 "Fried Chicken",而 Rous 点了 "Ceviche"
而餐桌 5:Carla 点了 "Water" 和 "Ceviche"
餐桌 10:Corina 点了 "Beef Burrito" 

提示:

  • 1 <= orders.length <= 5 * 10^4
  • orders[i].length == 3
  • 1 <= customerNamei.length, foodItemi.length <= 20
  • customerNamei 和 foodItemi 由大小写英文字母及空格字符 ' ' 组成。
  • tableNumberi 是 1 到 500 范围内的整数。

 

解题思路

根据题意,其实就是一个模拟,我们要统计总共有多少种菜,然后统计每一桌上,点的哪些菜(数量)。

所以总共需要统计三个东西:菜的种类(去重),桌子号,每张桌子点的菜

所以我的做法是

  1. 先遍历一次 order,得到菜的种类 + 总共有多少张不同的桌子,统计下来(这里我用的 unordered_map + vector,来去重,看到大佬的是 直接用 set,因为可以 去重 + 排序 )
  2. 排序,因为最后输出的答案,要求桌子号 和 菜名 排序(桌子号--升序,菜名--其实就是字典序的升序)
  3. 然后我们要统计每张桌子对应点了什么菜,那么先用一个  二维的 int 数组 来记录,(因为后面是要转成字符串,字符串不好不断的 + 1 操作)。那么就遍历 order,我们知道这个二维 int 数组的,行是 桌子,列 是每种菜(刚好我们前面用到了 unordered_map ,我们可以用 unordered_map  将 桌子号 和 下标对应,菜名 和 下标对应)
  4. 最后就是将这个 二维 int ,还有菜单的标题整合成一个  二维 string 即可。

AC代码(C++)

class Solution {
public:
    
    unordered_map<string, int> isFood;
    unordered_map<int, int> isTable;
    vector<int> tables;
    vector<string> foods;
    
    vector<vector<string>> displayTable(vector<vector<string>>& orders) {

        for(auto& s : orders)  // 第一次遍历,得到 桌子 和 菜名 的种类
        {
            int t = 0;
            for(int i = 0;i < s[1].size(); ++i)  // 桌子号是 string,要变成 int
            {
                t = t * 10 + (s[1][i] - '0');
            }
            
            if(isTable[t] == 0)  // 去重
            {
                isTable[t] = 1;
                tables.push_back(t);
            }

            for(int i = 2;i < s.size(); ++i)  // 这里一开始,我以为一次可以点很多次,实际上 order 的 列 只有 3 ,数据范围限制了
            {
                if(isFood[s[i]] == 0)  // 菜名去重
                {
                    isFood[s[i]] = 1;
                    foods.push_back(s[i]);
                }
            }
        }
        // 排序
        sort(tables.begin(), tables.end());
        sort(foods.begin(), foods.end());
        
        vector<vector<string>> res;  // 最后结果,第一行是 标题,那我们先处理
        vector<string> tep;
        tep.clear();
        tep.push_back("Table");
        
        int n = foods.size();
        for(int i = 0;i < n; ++i)
        {
            tep.push_back(foods[i]);
            isFood[foods[i]] = i + 1;   // 将 菜名和 下标对应,方便后面 二维 int 统计数量
        }
        res.push_back(tep);
        tep.clear();
        
        
        vector<vector<int>> nums;  // 二维 int 数组
        vector<int> tepNum;
        tepNum.clear();
        for(int i = 0;i < tables.size(); ++i) // 行是 桌子,列 是 菜名
        {
            isTable[tables[i]] = i + 1;
            tepNum.push_back(tables[i]);
            for(int j = 0;j < n; ++j) tepNum.push_back(0);
            nums.push_back(tepNum);
            tepNum.clear();
        }
        
        for(auto& s : orders)  // 又在一次遍历,这回就是得到 对应的桌子都点了哪些菜
        {
            int t = 0;
            for(int i = 0;i < s[1].size(); ++i)
            {
                t = t * 10 + (s[1][i] - '0');
            }
            
            int row = isTable[t] - 1;
            
            for(int i = 2;i < s.size(); ++i)
            {
                int col = isFood[s[i]];
                ++nums[row][col];
            }
        }
        
        for(int i = 0;i < nums.size(); ++i)  // 最后就是将 二维 int 整合到 之前的那个 二维 string 结果种即可。
        {
            tep.clear();
            for(int j = 0;j < n + 1; ++j)
            {
                tep.push_back(to_string(nums[i][j]));  // 数字变 string,可以直接用函数
            }
            res.push_back(tep);
        }
        
        return res;
    }
};

3.数青蛙(Minimum Number of Frogs Croaking)

题目链接

https://leetcode-cn.com/problems/minimum-number-of-frogs-croaking/

题意

给你一个字符串 croakOfFrogs,它表示不同青蛙发出的蛙鸣声(字符串 "croak" )的组合。由于同一时间可以有多只青蛙呱呱作响,所以 croakOfFrogs 中会混合多个 “croak” 。请你返回模拟字符串中所有蛙鸣所需不同青蛙的最少数目。

注意:要想发出蛙鸣 "croak",青蛙必须 依序 输出 ‘c’, ’r’, ’o’, ’a’, ’k’ 这 5 个字母。如果没有输出全部五个字母,那么它就不会发出声音。

如果字符串 croakOfFrogs 不是由若干有效的 "croak" 字符混合而成,请返回 -1 。

示例 1:

输入:croakOfFrogs = "croakcroak"
输出:1 
解释:一只青蛙 “呱呱” 两次

示例 2:

输入:croakOfFrogs = "crcoakroak"
输出:2 
解释:最少需要两只青蛙,“呱呱” 声用黑体标注
第一只青蛙 "crcoakroak"
第二只青蛙 "crcoakroak"

示例 3:

输入:croakOfFrogs = "croakcrook"
输出:-1
解释:给出的字符串不是 "croak" 的有效组合。

提示:

  • 1 <= croakOfFrogs.length <= 10^5
  • 字符串中的字符只有 'c''r''o''a' 或者 'k'

解题分析

一开始以为是 DP,实际上不是,仅仅只需要分析就好。

就是当我们对遍历 字符串 的时候,我们就不断的更新 c,r,o,a,k 对应字符出现的次数,那么总共需要最多几个青蛙,也就是 在遍历的时候,c 同时出现次数最多的时候,对应的就是同时需要那么多青蛙发出 c。

那么我们遍历的时候

  • 对应出现的字符 + 1,然后取 c 出现的次数(每次取最大值),这就是当前需要的青蛙数,那么遍历结束时,就是在这个过程种,出现的最大数
  • 这个时候要注意,因为我们青蛙一定是 c 开始的,那么 每一次遍历的时候,只要 字符 + 1,那么 c 出现的次数一定不会是 0,不然就是,青蛙没发 c,就直接发后面的,那么就不对。说明返回 -1。
  • 然后要判断一下,如果出现的字符时 k,那么相当于这只青蛙可以结束了,下次可以重新用。那么对应这只青蛙发出的  c,r,o,a,k 的次数都 -1。
    • 这里要注意,按理来说,都 -1,应该要都 >= 0,如果出现 < 0 的,说明不符合要求,返回 -1.
  • 最后结束遍历的时候,应该要求,所有字符出现的次数 == 0,因为刚好都青蛙配对,然后多少个 k 出现,都刚好 减去等于 0。所以如果最后还有字符 数量 > 0,也是不符合要求,返回 - 1。

AC代码(C++)

class Solution {
public:
    unordered_map<char, int> mp;
    
    int minNumberOfFrogs(string croakOfFrogs) {
        mp['c'] = 1; mp['r'] = 2; mp['o'] = 3; mp['a'] = 4; mp['k'] = 5;  // 这一部分,是为了方便,将 char,映射为 下标。 这里可以使用 函数 来处理,参数是 char,返回值 int
        int n = croakOfFrogs.size();
        
        int cnt[5] = {0};  // 统计每个字符出现的次数
        
        int ans = 0;
        
        for(int i = 0;i < n; ++i)
        {
            ++cnt[mp[croakOfFrogs[i]] - 1];  // 对应这个字符出现次数 + 1
            if(cnt[0] == 0) return -1;  // 因为都是从 c 开始,所以 其他字符有,那么 c 肯定就要有
            
            ans = max(ans, cnt[0]);  // c 其实就是对应此时有几只青蛙,每次都取最大。
            
            if(croakOfFrogs[i] == 'k')   // 如果是 k ,说明结束了,对应的前面字符出现 - 1。
            {
                for(int j = 0;j < 5; ++j)
                {
                    --cnt[j];
                    if(cnt[j] < 0) return -1;  // 不能出现 -1,变成 < 0,这说明缺少成立了
                }
            }
        }
        for(int j = 0;j < 5; ++j)   // 最后结束的时候,应该刚好都匹配消去了。
        {
            if(cnt[j] > 0) return -1;
        }
        
        return ans;
        
    }
};

 


4.生成数组(Build Array Where You Can Find the Maximum Exactly K Comparisons)

题目链接

https://leetcode-cn.com/problems/build-array-where-you-can-find-the-maximum-exactly-k-comparisons/

题意

给你三个整数 nm 和 k 。下图描述的算法用于找出正整数数组中最大的元素。

【有一张图,描述找最大值的算法】

请你生成一个具有下述属性的数组 arr :

  • arr 中有 n 个整数。
  • 1 <= arr[i] <= m 其中 (0 <= i < n) 。
  • 将上面提到的算法应用于 arr ,search_cost 的值等于 k 。
  • 返回上述条件下生成数组 arr 的 方法数 ,由于答案可能会很大,所以 必须 对 10^9 + 7 取余。

示例 1:

输入:n = 2, m = 3, k = 1
输出:6
解释:可能的数组分别为 [1, 1], [2, 1], [2, 2], [3, 1], [3, 2] [3, 3]

示例 4:

输入:n = 50, m = 100, k = 25
输出:34549172
解释:不要忘了对 1000000007 取余

提示:

  • 1 <= n <= 50
  • 1 <= m <= 100
  • 0 <= k <= n

解题分析

发现,我们要构造,那么也就是已经得到 有 i - 1 个数的情况,新加进去一个数,看能组成多少个满足要求的。

那么其实这个时候,就是类似 DP,因为是 i - 1 的状态会转移到 i。

1)设状态

我们假设 dp[ i ][ j ][ maxVal ] 表示,第 i 个 数字,因为使用了 j 次 search_cost,这 i 个数的最大值 maxVal(要记录的是最大值,因为当下一个值出现的时候,如果是 > maxVal, 那么 search_cost 就会 + 1)

2)状态转移

因为题目的数据小,所以我们的 DP 状态记录的多。

那么就直接遍历 i ,遍历 j,遍历当前 加入数组的值 now。

那么这样子,上一个值 last(是最大值)

  • 如果 last < now,那么相当于是 search_cost + 1 转移过来的,所以 dp[ i ][ j ][ now ] += dp[ i - 1 ][ j - 1 ][ last]
  • 如果 last >= now,相当于 search_cost 没变,那么 dp[ i ][ j ][ last] += dp[ i - 1 ][ j ][ last]  ,要注意,这里到 i 的时候,因为第三个状态时记录最大值,那么 由于 last 更大,所以 转移后,是 到 last,而不是用 now。

3)初始化

根据题目最大值算法的描述,这样子出现了数组的第一个值,那么 search_cost 就会变为 1,而此时这时出现的数都有可能

所以 dp[ 1 ][ 1 ][ all ] = 1

其他都是 0。

4)结果

因为我们只能保证 dp[ n ][ k ],但是无法保证第三个状态的数是什么,所以是要把每一种可能出现的情况都累加

所以是:ans += dp[ n ][ k ][ all ]

注意:上面的相加过程,都是要 取模的,别忘了

AC代码(C++)

const int MOD = 1e9 + 7;
class Solution {
public:
    // dp[n][k][maxVal]
    int dp[55][55][105];
    int numOfArrays(int n, int m, int k) {
        if(k == 0) return 0;
        memset(dp, 0, sizeof(dp));
        for(int v = 1;v <= m; ++v) dp[1][1][v] = 1;

        // n * n * m * m,不会超时。
        for(int i = 2;i <= n; ++i)
        {
            for(int j = 1;j <= k; ++j)
            {
                for(int now = 1;now <= m; ++now)
                {
                    for(int last = 1; last < now; ++last)
                    {
                        dp[i][j][now] = (dp[i][j][now] + dp[i - 1][j - 1][last]) % MOD;
                    }
                    for(int last = now; last <= m; ++last)
                    {
                        dp[i][j][last] = (dp[i][j][last] + dp[i - 1][j][last]) % MOD;
                    }
                }
            }
        }

        int ans = 0;
        for(int v = 1;v <= m; ++v) ans = (ans + dp[n][k][v]) % MOD;

        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值