这周的周赛,总体不难,主要是第二题,实现有点麻烦,写的有点久,菜鸡呀,看了一些前面大佬的代码,是真的简洁。最后一题,就是一个简单的 DP 问题,达不到 hard 的难度。
第一题:字符串操作。
第二题:模拟。
第三题:分析。
第四题:DP。
详细题解如下。
1.重新格式化字符串(Reformat the String)
2. 点菜展示表(Display Table of Food Orders in A Restaurant)
3.数青蛙(Minimum Number of Frogs Croaking)
4.生成数组(Build Array Where You Can Find the Maximum Exactly K Comparisons)
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 范围内的整数。
解题思路
根据题意,其实就是一个模拟,我们要统计总共有多少种菜,然后统计每一桌上,点的哪些菜(数量)。
所以总共需要统计三个东西:菜的种类(去重),桌子号,每张桌子点的菜
所以我的做法是
- 先遍历一次 order,得到菜的种类 + 总共有多少张不同的桌子,统计下来(这里我用的 unordered_map + vector,来去重,看到大佬的是 直接用 set,因为可以 去重 + 排序 )
- 排序,因为最后输出的答案,要求桌子号 和 菜名 排序(桌子号--升序,菜名--其实就是字典序的升序)
- 然后我们要统计每张桌子对应点了什么菜,那么先用一个 二维的 int 数组 来记录,(因为后面是要转成字符串,字符串不好不断的 + 1 操作)。那么就遍历 order,我们知道这个二维 int 数组的,行是 桌子,列 是每种菜(刚好我们前面用到了 unordered_map ,我们可以用 unordered_map 将 桌子号 和 下标对应,菜名 和 下标对应)
- 最后就是将这个 二维 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/
题意
给你三个整数
n
、m
和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;
}
};