LeetCode 第 289 场周赛题解及思路

LeetCode 第 289 场周赛 题解及思路

    • 6070. 计算字符串的数字和
    • 6071. 完成所有任务需要的最少轮数
    • 6072. 转角路径的乘积中最多能有几个尾随零
    • 6073. 相邻字符不同的最长路径

赛题传送门

6070. 计算字符串的数字和

赛题

题目太啰嗦,简要概括如下:

给定一个全部由数字字符(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。

6071. 完成所有任务需要的最少轮数

赛题

给定一个下标从 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] / 3x = (tasks[i] - y) / 2,但这样有可能使得 2x + 3y != tasks[i]

于是分情况讨论,如果 tasks[i] 是奇数,则 y 应该必须也是奇数,如果 tasks[i] 是偶数,则 y 必须也是偶数。所以只需要在上面增加一种情况,即 y = tasks[i] / 3 - 1x = (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。

6072. 转角路径的乘积中最多能有几个尾随零

赛题

题目太啰嗦,简单概括如下:

给定一个二维整数数组 grid,大小为 m * n,每个元素都是 正整数。求出 转角路径的最大值。转角路径是路径中有且仅有一个点有向左(或向右)和向上(或向下)两个方向的延伸的路径,直线路径也算做特殊的转角路径。转角路径的值是 路径中所有元素乘积的尾随 0 的个数

提示:
1 <= m, n <= 10^5
1 <= m * n <= 10^5
1 <= grid[i][j] <= 1000

很容易想到,我们可以遍历整个二维数组,对于每个元素,我们都要求出其 上右右下下左左上 四种转角路径的最大值。

而尾随 0 的个数是由元素中含有的 1052 来决定的,简而言之,假设两个数 ab 可以分解成 a = (10 * a1) * (5 * a2) * (2 * a3) * a4b = (10 * b1) * (5 * b2) * (2 * b3) * b4,且尽可能使得 a1b1 更大。则两个数乘积的尾随 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;
    };
};

所以显然对于每个元素,其如果向某个方向延伸的话,肯定是延伸到数组边界时能达到上述 1052 的个数都最大,所以我们可以用 前缀和 进行预处理。

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 拆解成 rowcol 两个数组,并且只需要求 向上向左 的路径即可,向下向右 的路径可以通过用 dp 的边界值与 向上向左 路径相减得到。

完整代码见 GitHub。

6073. 相邻字符不同的最长路径

赛题

还是懒惰不愿意抄题,简要概括如下:

给定一棵树(连通、无环且无向),树上每个节点的值都是一个 小写英文字母,求出 最长路径 的长度。要求是这条最长路径中没有分叉(即不能有任何一个节点连接超过 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],如果节点 isons[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 账号。

你可能感兴趣的:(力扣周赛,力扣,c++)