LeetCode 第264场周赛
作者:互联网
本次周赛开幕雷击,题目难度并不高,但是第一题就很麻烦,一定程度上影响了参赛者的积极性。
本文小结一下周赛题目。
No 1 句子中的有效单词数
句子仅由小写字母(‘a’ 到 ‘z’)、数字(‘0’ 到 ‘9’)、连字符(’-’)、标点符号(’!’、’.’ 和 ‘,’)以及空格(’ ')组成。每个句子可以根据空格分解成 一个或者多个 token ,这些 token 之间由一个或者多个空格 ’ ’ 分隔。
如果一个 token 同时满足下述条件,则认为这个 token 是一个有效单词:
- 仅由小写字母、连字符和/或标点(不含数字)。
- 至多一个 连字符 ‘-’ 。如果存在,连字符两侧应当都存在小写字母(“a-b” 是一个有效单词,但 “-ab” 和 “ab-”
不是有效单词)。 - 至多一个 标点符号。如果存在,标点符号应当位于 token 的 末尾 。
这里给出几个有效单词的例子:“a-b.”、“afad”、“ba-c”、“a!” 和 “!” 。
给你一个字符串 sentence ,请你找出并返回 sentence 中 有效单词的数目 。
示例 1:
输入:sentence = “cat and dog”
输出:3
解释:句子中的有效单词是 “cat”、“and” 和 “dog”
示例 2:
输入:sentence = “!this 1-s b8d!”
输出:0
解释:句子中没有有效单词
“!this” 不是有效单词,因为它以一个标点开头
“1-s” 和 “b8d” 也不是有效单词,因为它们都包含数字
解析
这道题繁琐程度根本不像一道签到题,虽然就是按照题意直接做,不需要考虑其他问题,但写起来还是很容易出错。
本题给定一个字符串,通过空格(单个或连续多个)划分成单词,让我们找出这些单词中有效单词的数目。题目给出了有效单词的定义,要求不含数字,可以包含连字符或标点,连字符左右两边必须都是小写字母且最多一个连字符,标点只能位于末尾同样最多出现一次。也即,有效单词只有’a’,‘a-a’,‘a!’,’!’,'a-a!'这么几种,a表示只包含小写字母的字符串。
本题按照题意遍历每个可能的单词即可,同时对连字符与标点符号计数。
遇到数字,则一定无效;
遇到小写字母,不做处理,遍历下一个字符;
遇到连字符,如果连字符不处在第一个或最后一个,同时前后都是小写字母,而且之前没有遇到连字符(计数器为0),那么这个连字符是合法的,对计数器加一,遍历下一个字符;否则,这个单词也是无效的;
遇到标点,如果标点符号不处在最后位置或是之前遇到了标点,则单词无效,否则继续遍历。
如果遍历结束整个单词都没有出现无效的情况,那么这个单词就是一个有效单词,结果加一。
遍历所有的单词,返回最后统计结果。
C++代码如下:
//No 1
int countValidWords(string sentence) {
stringstream ss(sentence);
int ans = 0;
string tmp;
while (ss >> tmp) {
int n = tmp.size(), i = 0, c = 0, b = 0;
while (i < n) {
if (tmp[i] >= '0' && tmp[i] <= '9') break;
else {
if (tmp[i] == '-') {
if (i>0&&i<n-1&&(tmp[i-1]>='a'&& tmp[i - 1] <= 'z')&& (tmp[i + 1] >= 'a' && tmp[i + 1] <= 'z')&&c==0) ++c;
else {
break;
}
}
else if (tmp[i] == ',' || tmp[i] == '.' || tmp[i] == '!') {
if (i != n - 1 || b > 0) break;
else ++b;
}
}
++i;
}
if (i == n)++ans;
}
return ans;
}
No 2 下一个更大的数值平衡数
如果整数 x 满足:对于每个数位 d ,这个数位 恰好 在 x 中出现 d 次。那么整数 x 就是一个 数值平衡数 。
给你一个整数 n ,请你返回 严格大于 n 的 最小数值平衡数 。
示例 1:
输入:n = 1
输出:22
解释:
22 是一个数值平衡数,因为:
- 数字 2 出现 2 次
这也是严格大于 1 的最小数值平衡数。
解析
本题定义了平衡数,即每一位的数d恰好出现了d次。如1位数中只有1是平衡数(包含1个1),2位数包含2个2的22.三位数则可以有3个3构成333,或者1个1,2个2构成122,212,221,以此类推。总之,一个平衡数就是有若干组k个k组成的。
本题要求我们找出严格大于输入n的最小平衡数。
其实本题最简单的思路就是从n+1开始,递增数字,以此判断是否为平衡数,找到第一个平衡数即为所求。但显然,平衡数在整数数组中是稀疏的,搜索到下一个平衡数可能需要遍历很大量的数字,会出现超时。同样,由于平衡数是稀疏的,我们可以先算出看范围内所有的平衡数,在查找第一个严格大于n的数即可。
计算一定范围内所有的平衡数,最直接的方法就是遍历每个数字。但实际上,根据我们分析得到的平衡数规律,我们并不需要这样做。
一个平衡数是有若干组k个k构成的,k彼此不同,实际上对于m位数,构成方案是一定的。比如1位数只能是1个1,2位数只能是2个2,而7位数可以有1个1+2个2+4个4, 1个1+6个6, 2个2+5个5,3个3+4个4, 7个7 这几种方法,不可能有其他方案了。
因为题目n的数据范围是1e6,我们计算到7位数的所有平衡数。
C++代码如下:
int nextBeautifulNumber(int n) {
unordered_map<int, vector<vector < int>> > comb{ {1,{{1}}},{2,{{2}}},{3,{{1,2},{3}} },{4,{{1,3},{4}}},{5,{{1,4},{2,3},{5}}},{6,{{1,2,3},{1,5},{2,4},{6}}},{7,{{1,2,4},{1,6},{2,5},{3,4},{7}}} };
vector<int>bn;
for (int i = 1; i <= 7; ++i) {
for (auto c : comb[i]) {
string s1 = "";
for (auto k : c) {
s1 += string(k, '0' + k);
}
sort(s1.begin(), s1.end());
cout << s1 << endl;
bn.push_back(atoi(s1.c_str()));
while (next_permutation(s1.begin(), s1.end())) {
bn.push_back(atoi(s1.c_str()));
}
sort(bn.begin(), bn.end());
for (auto b : bn) cout << b << " ";
cout << endl;
for (auto b : bn) { if (b > n) return b; }
return 0;
}
comb是一个哈希表,键位整数位数,这里需要1~7.值是一个二维数组,其中每个数组包含构成位数等于键值的平衡数可能的若干种方案,每个方案也是一个数组K,其中元素K[i]表示方案需要K[i]个K[i]。我们讨论数位从1到7,每个数位的平衡数遍历所有组成方案,对每个方案构造一个该方案下最小的平衡数,以字符串形式保存,然后通过全排列遍历所有平衡数并转化为整数存入数组。
通过上述过程,找到了从1位数到7位数所有的平衡数,对数组排序,查找(因为数量不多,不做二分查找也无所谓)大于n的最小数即可。
No 3 统计最高分的节点数目
给你一棵根节点为 0 的 二叉树 ,它总共有 n 个节点,节点编号为 0 到 n - 1 。同时给你一个下标从 0 开始的整数数组 parents 表示这棵树,其中 parents[i] 是节点 i 的父节点。由于节点 0 是根,所以 parents[0] == -1 。
一个子树的 大小 为这个子树内节点的数目。每个节点都有一个与之关联的 分数 。求出某个节点分数的方法是,将这个节点和与它相连的边全部 删除 ,剩余部分是若干个 非空 子树,这个节点的 分数 为所有这些子树 大小的乘积 。
请你返回有 最高得分 节点的 数目 。
示例 1:
输入:parents = [-1,2,0,2,0]
输出:3
解释:
- 节点 0 的分数为:3 * 1 = 3
- 节点 1 的分数为:4 = 4
- 节点 2 的分数为:1 * 1 * 2 = 2
- 节点 3 的分数为:4 = 4
- 节点 4 的分数为:4 = 4
最高得分为 4 ,有三个节点得分为 4 (分别是节点 1,3 和 4 )。
解析
本题要求是计算最高分节点的个数,题目输入一棵二叉树,对每个节点,将这个节点和它的边取走,那么这颗树会剩下若干相互不连通的子树,这些子树(不计算空树)包含节点数量的乘积就是这个被取走节点的分数。
本题要求我们返回所有节点取得最高分的节点数,也即,有多少个节点取得了最高分。这里很容易看错题目,误以为求解的最高的分数。
事实上,对于二叉树,取走一个节点,最多把这颗树分成三部分,首先这个节点的左右子节点将于其他部分不在连通,分割为左子树、右子树两部分,而剩下的节点仍然连在一起,构成剩余子树。一个节点的分数,就是左子树、右子树、剩余子树三部分节点数量的乘积(非空,若某部分为空则不计算)。显然,通过一次后序遍历,我们就知道了每个节点左右子树的节点数,而剩余子树节点数就是总结点数减去左右子树节点数之和,再减去该节点本身。
定义节点i的左右子树节点数量分别为
l
[
i
]
,
r
[
i
]
l[i], r[i]
l[i],r[i],总结点数为N,那么剩余子树节点数就是
r
e
s
t
[
i
]
=
N
−
1
−
l
[
i
]
−
r
[
i
]
rest[i]=N-1-l[i]-r[i]
rest[i]=N−1−l[i]−r[i]。通过后序遍历,先计算每个节点的
l
[
i
]
,
r
[
i
]
l[i], r[i]
l[i],r[i],再求出
r
e
s
t
[
i
]
rest[i]
rest[i],将三者不为0的值相乘,记录在map里。map保存每个值有几个节点,最后返回map中键最大的所对应的的值即可。
C++代码如下:
int getNums(vector<vector<int>>& ch, vector<vector<int>>& nums, int i) {
int sum = 1;
if (ch[i].empty()) {
nums[i].push_back({ 0 });
nums[i].push_back({ 0 });
}
else {
for (auto cc : ch[i]) {
int tmp = getNums(ch, nums, cc);
nums[i].push_back(tmp);
sum += tmp;
}
}
return sum;
}
int countHighestScoreNodes(vector<int>& parents) {
int n = parents.size();
vector<vector<int>>ch(n,vector<int>());
vector<vector<int>>nums(n, vector<int>());
for (int i = 1; i < n; ++i) {
ch[parents[i]].push_back(i);
}
getNums(ch, nums, 0);
map<long long, int>mm;
for (auto i : nums) {
long long a = 1;
long long t = n - 1;
for (auto k : i) {
t -= k;
if (k != 0)a *= k;
}
if (t != 0) a *= t;
mm[a]++;
}
return mm.rbegin()->second;
}
这里要注意,分数是乘积,在本题数据范围内是可能出现超出int范围值的,可以直接使用long long表示分数。
No 4 并行课程 III
给你一个整数 n ,表示有 n 节课,课程编号从 1 到 n 。同时给你一个二维整数数组 relations ,其中 relations[j] = [prevCoursej, nextCoursej] ,表示课程 prevCoursej 必须在课程 nextCoursej 之前 完成(先修课的关系)。同时给你一个下标从 0 开始的整数数组 time ,其中 time[i] 表示完成第 (i+1) 门课程需要花费的 月份 数。
请你根据以下规则算出完成所有课程所需要的 最少 月份数:
如果一门课的所有先修课都已经完成,你可以在 任意 时间开始这门课程。
你可以 同时 上 任意门课程 。
请你返回完成所有课程所需要的 最少 月份数。
注意:测试数据保证一定可以完成所有课程(也就是先修课的关系构成一个有向无环图)。
示例 1:
输入:n = 3, relations = [[1,3],[2,3]], time = [3,2,5]
输出:8
解释:上图展示了输入数据所表示的先修关系图,以及完成每门课程需要花费的时间。
你可以在月份 0 同时开始课程 1 和 2 。
课程 1 花费 3 个月,课程 2 花费 2 个月。
所以,最早开始课程 3 的时间是月份 3 ,完成所有课程所需时间为 3 + 5 = 8 个月。
解析
本题给定了一个有向无环图,图中节点表示一门课程,边表示课程依赖关系,如果存在节点u指向节点v的边,表示要先修u课程后才能修v课程。题目表示可同时学习任意门课程,换句话说,就是只要该课程先导课程都修完了,就直接开始学习这门课,能学就学。每一门课程有一个需求时间,要我们求出完成所有课程的时间。
本题既然可以同时修任意门课,就不存在可修课程的先后问题,只要可以学习,就加入学习。本题是一个拓扑排序结合动态规划的问题。一门课的开始时间,取决于先导课程最后的完成时间,而一门课程的完成时间,就在开始时间上加上该课程所用时间即可。
我们通过一个队列,维护课程学习过程,通过一个数组dp,记录课程时间。
初始时,dp所有值设为0.
我们定义一门课程入队表示开始学习,出队表示完成学习。节点i入队时,dp[i] 将更新为该课程的开始时间,出队时,dp[i]+=time[i],表示其完成时间。
我们首先基于入度进行拓扑排序,对于入度为0的课程(节点)将其加入队列,表示开始学习。接下来依次从队中取出节点,取出的节点 c u r cur cur表示已经完成,更新其完成时间;同时,该节点完成意味着后续课程 n e x t next next的先导条件完成,有连接的节点入度减一,而后续课程的开始时间一定是所有先导课程结束时间中最大的那个,所以更新 d p [ n e x t ] = m a x ( d p [ n e x t ] , d p [ c u r ] ) dp[next]=max(dp[next],dp[cur]) dp[next]=max(dp[next],dp[cur])。此时,如果后续课程 n e x t next next的入度减小到0,意味着该课程可以学习,将其加入队列,此时dp[next]保存了这个节点先导课程的最大结束时间,也就是该课程开始时间。
遍历计算所有课程结束时间,其中最大的就是完成全部课程的时间。
C++代码如下:
int minimumTime(int n, vector<vector<int>>& relations, vector<int>& time) {
vector<vector<int>>edge(n+1, vector<int>());
vector<int>indegree(n + 1, 0);
indegree[0] = -1;
for (auto re : relations) {
edge[re[0]].push_back(re[1]);
indegree[re[1]]++;
}
queue<int>q;
vector<int>dp(n + 1);
for (int i = 1; i <= n; ++i) {
if (indegree[i] == 0) q.push(i);
}
while (!q.empty()) {
int cur = q.front();
dp[cur] += time[cur - 1];
q.pop();
for (auto next : edge[cur]) {
dp[next] = max(dp[next], dp[cur]);
indegree[next]--;
if (indegree[next] == 0) {
q.push(next);
}
}
}
int maxT = 0;
for (auto d : dp) maxT = max(maxT, d);
return maxT;
}
标签:周赛,int,LeetCode,单词,课程,264,next,节点,dp 来源: https://blog.csdn.net/andyL_05/article/details/120932223