赛题传送门
赛题
题目太啰嗦,简要概括如下:
给定一个全部由数字字符(
0
-9
)组成的字符串s
和一个整数k
。只要当s
长度 大于k
时,便进行分组迭代,具体规则是将s
从头到尾切分成多个由k
个字符组成的子字符串(最后一个子字符串允许长度小于k
),将每个子字符串中的每个字符所代表的数字相加,并将得到的数字重新转换成字符串形式,依次相连得到下一次迭代的字符串。
所以简单地进行模拟即可,注意每次迭代时最后一个子字符串长度可以小于 k
。
while (s.size() > k) {
string ss;
int index = 0, n = s.size();
while (index < n) {
int tot = 0, cnt = 0;
while (index < n && cnt++ < k) tot += s[index++] - '0';
ss.append(to_string(tot));
};
s = ss;
};
完整代码见 GitHub。
赛题
给定一个下标从 0 开始的整数数组
tasks
,其中tasks[i]
表示任务的难度级别。在每一轮中,你可以完成 2 个或者 3 个 相同难度级别 的任务。返回完成所有任务需要的 最少 轮数,如果无法完成所有任务,返回-1
。
先对级别难度相同的任务计数,然后就是要在满足 2x + 3y = tasks[i]
的前提下使得 x + y
尽可能小。
unordered_map counts;
for (const int& task : tasks)
counts[task]++;
最简单的方法是遍历 x
求解,但由于数据量大小在 10^5
显然超时。于是想到要尽可能使得 y
大一些(因为 y
的系数比 x
大),即令 y = tasks[i] / 3
则 x = (tasks[i] - y) / 2
,但这样有可能使得 2x + 3y != tasks[i]
。
于是分情况讨论,如果 tasks[i]
是奇数,则 y
应该必须也是奇数,如果 tasks[i]
是偶数,则 y
必须也是偶数。所以只需要在上面增加一种情况,即 y = tasks[i] / 3 - 1
且 x = (tasks[i] - y) / 2
然后判断条件 2x + 3y = tasks[i]
是否满足,如果满足则更新 ans
,否则舍弃。
int ans = 0;
for (const auto iter : counts) {
int num = iter.second;
int cnt3 = num / 3, cnt2 = (num - cnt3 * 3) / 2;
// 尽量使得 cnt3 大,这里的 if-else 对应不相容的两种情况。
if (cnt3 * 3 + cnt2 * 2 == num) ans += cnt3 + cnt2;
else if (cnt3 > 0) {
cnt3--;
cnt2 = (num - cnt3 * 3) / 2;
if (cnt3 * 3 + cnt2 * 2 == num) ans += cnt3 + cnt2;
else return -1;
} else return -1;
};
完整代码见 GitHub。
赛题
题目太啰嗦,简单概括如下:
给定一个二维整数数组
grid
,大小为m * n
,每个元素都是 正整数。求出 转角路径的最大值。转角路径是路径中有且仅有一个点有向左(或向右)和向上(或向下)两个方向的延伸的路径,直线路径也算做特殊的转角路径。转角路径的值是 路径中所有元素乘积的尾随 0 的个数。
提示:
1 <= m, n <= 10^5
1 <= m * n <= 10^5
1 <= grid[i][j] <= 1000
很容易想到,我们可以遍历整个二维数组,对于每个元素,我们都要求出其 上右,右下,下左 和 左上 四种转角路径的最大值。
而尾随 0 的个数是由元素中含有的 10
,5
和 2
来决定的,简而言之,假设两个数 a
和 b
可以分解成 a = (10 * a1) * (5 * a2) * (2 * a3) * a4
和 b = (10 * b1) * (5 * b2) * (2 * b3) * b4
,且尽可能使得 a1
和 b1
更大。则两个数乘积的尾随 0 的个数就是 a1 + b1 + min(a2 + b2, a3 + b3)
。因此我们可以预先计算出 1 ~ 1000
的对应系数。
vector> umap(N + 1, vector(3, 0)); // 预处理 10 的倍数 5 的倍数和 2 的倍数
for (int i = 1; i <= N; ++i) {
int tmp = i;
while (tmp > 0) {
if (tmp % 10 == 0) {
umap[i][0]++;
tmp /= 10;
} else break;
};
while (tmp > 0) {
if (tmp % 5 == 0) {
umap[i][1]++;
tmp /= 5;
} else break;
};
while (tmp > 0) {
if (tmp % 2 == 0) {
umap[i][2]++;
tmp /= 2;
} else break;
};
};
所以显然对于每个元素,其如果向某个方向延伸的话,肯定是延伸到数组边界时能达到上述 10
,5
和 2
的个数都最大,所以我们可以用 前缀和 进行预处理。
int m = grid.size(), n = grid[0].size();
vector>>> dp(m, vector>>(n, vector>(4, vector(3, 0)))); // 0 上 1 左 2 下 3 右
// 向上,向左
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < 3; k++) {
if (i > 0) dp[i][j][0][k] = dp[i - 1][j][0][k] + umap[grid[i - 1][j]][k];
if (j > 0) dp[i][j][1][k] = dp[i][j - 1][1][k] + umap[grid[i][j - 1]][k];
};
// 向右,向下
for (int i = m - 1; i >= 0; i--)
for (int j = n - 1; j >= 0; j--)
for (int k = 0; k < 3; k++) {
if (i < m - 1) dp[i][j][2][k] = dp[i + 1][j][2][k] + umap[grid[i + 1][j]][k];
if (j < n - 1) dp[i][j][3][k] = dp[i][j + 1][3][k] + umap[grid[i][j + 1]][k];
}
然后对于遍历整个二维数组,对于每个元素分别求 上右,右下,下左 和 左上 这四种路径的最大值。
int ans = 0;
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++) {
ans = max(ans, dp[i][j][0][0] + dp[i][j][1][0] + umap[grid[i][j]][0] + min(dp[i][j][0][1] + dp[i][j][1][1] + umap[grid[i][j]][1], dp[i][j][0][2] + dp[i][j][1][2] + umap[grid[i][j]][2]));
ans = max(ans, dp[i][j][1][0] + dp[i][j][2][0] + umap[grid[i][j]][0] + min(dp[i][j][1][1] + dp[i][j][2][1] + umap[grid[i][j]][1], dp[i][j][1][2] + dp[i][j][2][2] + umap[grid[i][j]][2]));
ans = max(ans, dp[i][j][2][0] + dp[i][j][3][0] + umap[grid[i][j]][0] + min(dp[i][j][2][1] + dp[i][j][3][1] + umap[grid[i][j]][1], dp[i][j][2][2] + dp[i][j][3][2] + umap[grid[i][j]][2]));
ans = max(ans, dp[i][j][3][0] + dp[i][j][0][0] + umap[grid[i][j]][0] + min(dp[i][j][3][1] + dp[i][j][0][1] + umap[grid[i][j]][1], dp[i][j][3][2] + dp[i][j][0][2] + umap[grid[i][j]][2]));
};
我打力扣竞赛时就是这么做的,但是很可惜被卡常数超时了一点点。我认为应该是 dp
维度太多导致局部性太差,以至于运行时间过长。进一步的优化可以是将 dp
拆解成 row
和 col
两个数组,并且只需要求 向上 和 向左 的路径即可,向下 和 向右 的路径可以通过用 dp
的边界值与 向上 和 向左 路径相减得到。
完整代码见 GitHub。
赛题
还是懒惰不愿意抄题,简要概括如下:
给定一棵树(连通、无环且无向),树上每个节点的值都是一个 小写英文字母,求出 最长路径 的长度。要求是这条最长路径中没有分叉(即不能有任何一个节点连接超过 2 个其他节点),且相邻节点不能有相同字符。
由于题目中给出的是父节点数组 parent
,需要先将其转换成子节点数组 sons
。
int n = parent.size();
vector> sons(n);
for (int i = 1; i < n; i++) {
int son = i, pt = parent[i];
sons[pt].push_back(son);
};
其实就是简单的 深度优先搜索 的应用,对于节点 i
的每个子节点 sons[i]
,如果节点 i
和 sons[i]
的字符不一样,则比较 (sons[i]
为头的单链长度 + 其他子节点单链长度最大值 res
) 与现有结果 ans
的大小并更新。并且注意更新子节点单链长度最大值 res
,每层递归都要向上返回 res
,即以当前节点为头的单链长度最大值。
int dfs(const vector>& sons, const string& s, int root, int& ans) {
int res = 0;
for (const int& son : sons[root]) {
int len = dfs(sons, s, son, ans); // 求该子节点的单链最大长度
if (s[root] != s[son]) {
ans = max(ans, len + res + 1); // 更新现有结果
res = max(res, len); // 更新所有子节点单链最大长度
} else {
ans = max(ans, len);
};
};
return res + 1;
};
完整代码见 GitHub。
这场周赛较为简单没有 AK 其实还是挺懊恼的,这也提醒我在一些细节上还需要多加注意。
好啦,以上就是力扣第 289 场周赛的全部思路啦。最后,欢迎关注我的 GitHub 账号。