读题还是要细心呀。读题没读全,耽误了半个小时🤦♂️。
2062. 统计字符串中的元音子字符串
思路:暴力枚举
时间复杂度:
枚举所有的子字符串,然后再检查子字符串是否合法即可。
class Solution {
public:
bool check(const std::string &word, int l, int r) {
unordered_set<char> mark;
// 将 word[l:r] 全部插入 mark
for (int i = l; i <= r; i++) {
mark.insert(word[i]);
}
// mark 中有且仅有五个元音才合法,反之不合法。
return mark.size() == 5 && mark.count('a') && mark.count('e') && mark.count('i') && mark.count('o') && mark.count('u');
}
int countVowelSubstrings(string word) {
int cnt = 0;
// 枚举以 word[i] 开始,以 word[j] 结尾的子字符串。
for (int i = 0; i < word.size(); i++) {
for (int j = i; j < word.size(); j++) {
// 检查 word[i:j] 是否符合要求
if (check(word, i, j)) {
cnt++;
}
}
}
return cnt;
}
};
2063. 所有子字符串中的元音
思路:逆向计算
时间复杂度:
按题意,可以枚举所有的子字符串 个,然后每个子字符串逐个统计元音的数量。整体的时间复杂度为 。鉴于 ,这个复杂度必然超时。
逆向思考,枚举每个字符 ,包含 的子字符串的总数易得,为
则答案为
class Solution {
public:
bool check(char c) {
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}
long long countVowels(string word) {
int64_t anw = 0;
for (int64_t i = 0; i < word.size(); i++) {
// 检查 word[i] 是否为元音
if (check(word[i])) {
// 包含 word[i] 的子字符串的数量为 (i+1) * (word.size()-i)
anw += (i+1) * (word.size()-i);
}
}
return anw;
}
};
2064. 分配给商店的最多商品的最小值
思路:二分
时间复杂度:
像之前说的,看到最多/最少,最大/最小,这样的字眼出现,就可以考虑二分了。接下来分析本题是否符合二分的要求。
如果每个商店放 个商品时能放完 种商品,则 也必然可以。
反之,如果每个商店放 个商品时,放不完 种商品,则 也必然不可以。
显然符合二分的单调性要求,直接套用二分即可。
class Solution {
public:
bool check(int n, const vector<int> &q, int limit) {
int cnt = 0;
for (int i = 0; i < q.size(); i++) {
cnt += (q[i]+limit-1)/limit;
}
return cnt <= n;
}
int minimizedMaximum(int n, vector<int>& quantities) {
// 按照题意,答案必然在 [1, 100000] 内。
int l = 1, r = 100000;
while (l <= r) {
// 取区间 [l,r] 的中心 mid。
int mid = (l+r)>>1;
// O(n) 的检查:每个商店最多放 mid 个商品时,能否放下所有的商品。
if (check(n, quantities, mid)) {
// 能放下:则 [mid+1,r]均可以。则 mid 为已知的最小的限制。
r = mid-1;
} else {
// 放不下:则 [l, mid] 均不可以。答案必然在 [mid+1,r] 中。
l = mid+1;
}
}
// 观察上述 while 循环,不难发现,每次更新 r 时,r+1 即 mid 就是最小的限制,也就是答案咯。
return r+1;
}
};
2065. 最大化一张图中的路径价值
思路:深度优先搜索
时间复杂度:。 为节点边数的上限,本题中为 4。 为搜索的深度,本题为 10。
比赛时险些放弃这个题,还好顶住了🤦♂️
初看题目以为是BFS,有两个原因推翻了该思路:
- 点权是正的,但要计算最长路。
- 需要记录中间状态——路径包含的点集(点可以重复走,但权值只累加一次)。
那就考虑下 DFS 吧,但是最多有 1000 个点,2000 条边。不带剪枝的 DFS 必然超时。
又看到「每个节点至多有四条边与之相连」 的提示,心想这里肯定文章。奈何分析了近五十分钟,无果💔。
接着读题,从头到尾读一遍,细细的读,终于发现了关键:
上述限制意味着,最优路径最多包含 「10」条边,「11」个点。再结合「每个节点至多有四条边与之相连」的限制,DFS 的时间复杂度为 ,大约是千万量级。这个量级问题不大,甚至很小!
思路有了,代码就不难写,几分钟写完,提交,通过,干饭去~
具体细节见注释啦~
class Solution {
public:
// 建立边表
vector<pair<int, int>> next[1000];
// mark[i] 大于 0 表示该节点已经走过了,反之则尚未走过。
int mark[1000] = {0};
// cur: 当前走到了 cur 点
// cost: 从 0 点到 cur 点花费的时间
// gain: 从 0 点到 cur 点的得分
// maxTime:时间限制
// values: 每个点的权值
// anw: 存储答案
void dfs(int cur, int cost, int gain, int maxTime, const vector<int> &values, int &anw) {
if (cur == 0) {
// 当前点为 0,可以作为最后一个点,尝试更新 anw
anw = max(anw, gain);
}
// 枚举与 cur 相邻的边
for (const auto &e : next[cur]) {
int v = e.first, w = e.second;
// 时间还允许走到 v 点
if (cost + w > maxTime) {
continue;
}
// 标记一下 v
mark[v]++;
// 开始下一层 dfs
// gain + (mark[v] == 1 ? values[v] : 0),mark[v] == 1 说明是第一次经过 v,所以要加上得分。
dfs(v, cost + w, gain + (mark[v] == 1 ? values[v] : 0), maxTime, values, anw);
// 尝试解除 v 的限制
mark[v]--;
}
}
int maximalPathQuality(vector<int>& values, vector<vector<int>>& edges, int maxTime) {
// 建立边表
int n = values.size();
for (const auto &e : edges) {
int u = e[0], v = e[1], w = e[2];
next[u].push_back({v, w});
next[v].push_back({u, w});
}
// anw 用来存储答案。
int anw = 0;
// 因为从 0 点出发,所以先把 0 的权值算进去。
mark[0] = true;
dfs(0, 0, values[0], maxTime, values, anw);
return anw;
}
};