LeetCode第 458 场周赛题解
题目地址
https://leetcode.cn/contest/weekly-contest-458/
锐评
参赛人数仍然不足2k!留下的应该是纯热爱了吧!
题目难度适中。这Q2确定不是来捣乱的?跟上周Q3几乎一模一样了吧!代码复制过来,改下check里面的符号就可以了。Q4比Q3简单些,先做的Q4。后面发现Q3长度不会溢出,正难则反,跟上周Q4一样,倒着做更简单。
题解
Q1. 用特殊操作处理字符串 I
题意
给你一个字符串 s,它由小写英文字母和特殊字符:*、# 和 % 组成。
请根据以下规则从左到右处理 s 中的字符,构造一个新的字符串 result:
- 如果字符是 小写 英文字母,则将其添加到 result 中。
- 字符 ‘*’ 会 删除 result 中的最后一个字符(如果存在)。
- 字符 ‘#’ 会 复制 当前的 result 并 追加 到其自身后面。
- 字符 ‘%’ 会 反转 当前的 result。
在处理完 s 中的所有字符后,返回最终的字符串 result。
示例
示例1
输入: s = "a#b%*"
输出: "ba"
说明
i | s[i] | 操作 | 当前 result |
---|---|---|---|
0 | ‘a’ | 添加 ‘a’ | “a” |
1 | ‘#’ | 复制 result | “aa” |
2 | ‘b’ | 添加 ‘b’ | “aab” |
3 | ‘%’ | 反转 result | “baa” |
4 | ‘*’ | 删除最后一个字符 | “ba” |
因此,最终的 result 是 “ba”。
示例2
输入: s = "z*#"
输出: ""
说明
i | s[i] | 操作 | 当前 result |
---|---|---|---|
0 | ‘z’ | 添加 ‘z’ | “z” |
1 | ‘*’ | 删除最后一个字符 | “” |
2 | ‘#’ | 复制字符串 | “” |
因此,最终的 result 是 “”。
提示
- 1<=s.length<=201 <= s.length <= 201<=s.length<=20
- s 只包含小写英文字母和特殊字符 *、# 和 %。
解题思路:模拟
中等题。考虑到字符串长度非常短,直接模拟即可,时间复杂度为O(2n)O(2^n)O(2n)。
参考代码(C++)
class Solution {
public:string processStr(string s) {string ans;for (char& ch : s) {if (islower(ch))ans.push_back(ch);else if (ch == '*') {if (!ans.empty())ans.pop_back();} else if (ch == '#')ans += ans;elsereverse(ans.begin(), ans.end());}return ans;}
};
Q2. 最小化连通分量的最大成本
题意
给你一个无向连通图,包含 n 个节点,节点编号从 0 到 n - 1,以及一个二维整数数组 edges,其中 edges[i]=[ui,vi,wi]edges[i] = [u_i, v_i, w_i]edges[i]=[ui,vi,wi] 表示一条连接节点 uiu_iui 和节点 viv_ivi 的无向边,边权为 wiw_iwi,另有一个整数 k。
你可以从图中移除任意数量的边,使得最终的图中 最多 只包含 k 个连通分量。
连通分量的 成本 定义为该分量中边权的 最大值 。如果一个连通分量没有边,则其代价为 0。
请返回在移除这些边之后,在所有连通分量之中的 最大成本 的 最小可能值 。
示例
示例1
输入: n = 5, edges = [[0,1,4],[1,2,3],[1,3,2],[3,4,6]], k = 2
输出: 4
说明
- 移除节点 3 和节点 4 之间的边(权值为 6)。
- 最终的连通分量成本分别为 0 和 4,因此最大代价为 4。
示例2
输入: n = 4, edges = [[0,1,5],[1,2,5],[2,3,5]], k = 1
输出: 5
说明
- 无法移除任何边,因为只允许一个连通分量(k = 1),图必须保持完全连通。
- 该连通分量的成本等于其最大边权,即 5。
提示
- 1<=n<=5∗1041 <= n <= 5 * 10^41<=n<=5∗104
- 0<=edges.length<=1050 <= edges.length <= 10^50<=edges.length<=105
- edges[i].length==3edges[i].length == 3edges[i].length==3
- 0<=ui,vi<n0 <= u_i, v_i < n0<=ui,vi<n
- 1<=wi<=1061 <= w_i <= 10^61<=wi<=106
- 1<=k<=n1 <= k <= n1<=k<=n
- 输入图是连通图。
解题思路:二分+并查集
中等题。看到最大值最小,优先考虑二分。随着最大值约束,越小删除的边也会尽量多,那么连通分量个数也会越多,而题目要求连通分量个数最多只能 k 个,因此符合单调性。时间复杂度为O(nlog(maxW))O(nlog(maxW))O(nlog(maxW)),其中 maxWmaxWmaxW 为边权最大值。
参考代码(C++)
const int maxn = 50'005;
struct dsu {int p[maxn], siz[maxn];int n, cnt;void init(int n) {this->n = this->cnt = n;for (int i = 0; i < n; ++i)p[i] = i, siz[i] = 1;}int findp(int x) {return p[x] == x ? x : p[x] = findp(p[x]);}bool unionp(int x, int y) {int fx = findp(x);int fy = findp(y);if (fx == fy)return false;--cnt;if (siz[fx] >= siz[fy]) {p[fy] = fx;siz[fx] += siz[fy];} else {p[fx] = fy;siz[fy] += siz[fx];}return true;}bool same(int x, int y) {return findp(x) == findp(y);}int sizep(int x) {return siz[findp(x)];}int count() {return cnt;}
} d;class Solution {
public:int minCost(int n, vector<vector<int>>& edges, int k) {auto check = [&](int lim) -> bool {d.init(n);for (auto& e : edges)if (e[2] <= lim)d.unionp(e[0], e[1]);return d.count() <= k;};int l = 0, r = 0, ans = -1;for (auto& e : edges)r = max(r, e[2]);while (l <= r) {int mid = (l + r) >> 1;if (check(mid)) {ans = mid;r = mid - 1;} elsel = mid + 1;}return ans;}
};
Q3. 用特殊操作处理字符串 II
题意
给你一个字符串 s,由小写英文字母和特殊字符:*、# 和 % 组成。
同时给你一个整数 k。
请根据以下规则从左到右处理 s 中每个字符,构造一个新的字符串 result:
- 如果字符是 小写 英文字母,则将其添加到 result 中。
- 字符 ‘*’ 会 删除 result 中的最后一个字符(如果存在)。
- 字符 ‘#’ 会 复制 当前的 result 并 追加 到其自身后面。
- 字符 ‘%’ 会 反转 当前的 result。
返回最终字符串 result 中第 k 个字符(下标从 0 开始)。如果 k 超出 result 的下标索引范围,则返回 ‘.’。
示例
示例1
输入: s = "a#b%*", k = 1
输出: "a"
说明
i | s[i] | 操作 | 当前 result |
---|---|---|---|
0 | ‘a’ | 添加 ‘a’ | “a” |
1 | ‘#’ | 复制 result | “aa” |
2 | ‘b’ | 添加 ‘b’ | “aab” |
3 | ‘%’ | 反转 result | “baa” |
4 | ‘*’ | 删除最后一个字符 | “ba” |
最终的 result 是 “ba”。下标为 k = 1 的字符是 ‘a’。
示例2
输入: s = "cd%#*#", k = 3
输出: "d"
说明
i | s[i] | 操作 | 当前 result |
---|---|---|---|
0 | ‘c’ | 添加 ‘c’ | “c” |
1 | ‘d’ | 添加 ‘d’ | “cd” |
2 | ‘%’ | 反转 result | “dc” |
3 | ‘#’ | 复制 result | “dcdc” |
4 | ‘*’ | 删除最后一个字符 | “dcd” |
5 | ‘#’ | 复制 result | “dcddcd” |
最终的 result 是 “dcddcd”。下标为 k = 3 的字符是 ‘d’。
示例3
输入: s = "z*#", k = 0
输出: "."
说明
i | s[i] | 操作 | 当前 result |
---|---|---|---|
0 | ‘z’ | 添加 ‘z’ | “z” |
1 | ‘*’ | 删除最后一个字符 | “” |
2 | ‘#’ | 复制字符串 | “” |
最终的 result 是 “”。由于下标 k = 0 越界,输出为 ‘.’。
提示
- 1<=s.length<=1051 <= s.length <= 10^51<=s.length<=105
- s 只包含小写英文字母和特殊字符 ‘*’、‘#’ 和 ‘%’。
- 0<=k<=10150 <= k <= 10^{15}0<=k<=1015
- 处理 s 后得到的 result 的长度不超过 101510^{15}1015。
解题思路:思维
难题。看起来好难,因为长度太长了,结果可能太大了,不好处理啊。但是我们注意到提示的最后一条:处理 s 后得到的 result 的长度不超过 101510^{15}1015。这就说明我们可以模拟算出最终长度为多少,设为 cnt,那么如果 k≥cntk \geq cntk≥cnt,就说明越界了,直接返回 ‘.’。对于不越界的情况怎么处理呢?我们知道随着字符加入,串会越来越长,既然存不下,是不是可以不存直接得到答案?正难则反,我们倒着枚举一下,发现新大陆,可以得到当前字符串长度,又因为字符是从前往后加入的,那么只要我们枚举到某个位置刚好字符串长度为 k+1k + 1k+1,这个字符就是答案。因此我们只需要正序遍历模拟判断无效,倒序遍历模拟找答案即可,时间复杂度为O(n)O(n)O(n)。
参考代码(C++)
class Solution {using ll = long long;
public:char processStr(string s, long long k) {ll cnt = 0;for (char& ch : s) {if (islower(ch))++cnt;else if (ch == '*') {if (cnt > 0)--cnt;} else if (ch == '#')cnt += cnt;}if (k >= cnt)return '.';reverse(s.begin(), s.end());for (char& ch : s) {if (islower(ch)) {if (k == cnt - 1)return ch;--cnt;} else if (ch == '*')++cnt;else if (ch == '#') {ll t = cnt >> 1;k %= t;cnt = t;} elsek = cnt - 1 - k;}return '?';}
};
Q4. 图中的最长回文路径
题意
给你一个整数 n 和一个包含 n 个节点的 无向图 ,节点编号从 0 到 n - 1,以及一个二维数组 edges,其中 edges[i]=[ui,vi]edges[i] = [u_i, v_i]edges[i]=[ui,vi] 表示节点 uiu_iui 和节点 viv_ivi 之间有一条边。
同时给你一个长度为 n 的字符串 label,其中 label[i] 是与节点 i 关联的字符。
你可以从任意节点开始,移动到任意相邻节点,每个节点 最多 访问一次。
返回通过访问一条路径,路径中 不包含重复 节点,所能形成的 最长回文串 的长度。
回文串 是指正着读和反着读相同的字符串。
示例
示例1
输入: n = 3, edges = [[0,1],[1,2]], label = "aba"
输出: 3
说明
- 最长的回文路径是从节点 0 到节点 2,经过节点 1,路径为 0 → 1 → 2,形成字符串 “aba”。
- 这是一个长度为 3 的回文串。
示例2
输入: n = 3, edges = [[0,1],[0,2]], label = "abc"
输出: 1
说明
- 没有超过一个节点的路径可以形成回文串。
- 最好的选择是任意一个单独的节点,构成长度为 1 的回文串。
示例3
输入: n = 4, edges = [[0,2],[0,3],[3,1]], label = "bbac"
输出: 3
说明
- 最长的回文路径是从节点 0 到节点 1,经过节点 3,路径为 0 → 3 → 1,形成字符串 “bcb”。
- 这是一个有效的回文串,长度为 3。
提示
- 1<=n<=141 <= n <= 141<=n<=14
- n−1<=edges.length<=n∗(n−1)/2n - 1 <= edges.length <= n * (n - 1) / 2n−1<=edges.length<=n∗(n−1)/2
- edges[i]==[ui,vi]edges[i] == [u_i, v_i]edges[i]==[ui,vi]
- 0<=ui,vi<=n−10 <= u_i, v_i <= n - 10<=ui,vi<=n−1
- ui!=viu_i != v_iui!=vi
- label.length==nlabel.length == nlabel.length==n
- label 只包含小写英文字母。
- 不存在重复边。
解题思路:状压+BFS/DFS
难题。注意到 1<=n<=141 <= n <= 141<=n<=14,很有可能就是暴力,但是此题是 图 而不是 树,所以重复访问不好处理。考虑状压,点标记是可以了,那么怎么找答案呢?想一想我们之前对于超大回文数是怎么处理的?是不是对半分然后拼接起来?再想想我们之前处理最长回文子串有一种方法是怎么做的?是不是从中心扩展?对于这题,我们同样可以从中心向两边扩展,首先我们只需要将单个点和满足条件的两个点加入,然后开始通过 图 向两边扩展,同时记录已经访问过的点即可。通过已知两个端点和访问过的点进行标记,最多有 14×14×214=321126414 \times 14 \times 2^{14} = 321126414×14×214=3211264 个状态,可以通过此题。具体可以通过 BFS/DFS 来做扩展操作,时间复杂度为O(n2⋅2n)O(n^2 \cdot 2^n)O(n2⋅2n)。
参考代码(C++)
class Solution {using tiii = array<int, 3>;
public:int maxLen(int n, vector<vector<int>>& edges, string label) {vector<vector<int>> adj(n);for (auto& e : edges) {adj[e[0]].push_back(e[1]);adj[e[1]].push_back(e[0]);}int m = 1 << n;vector<vector<vector<bool>>> vis(n, vector<vector<bool>>(n, vector<bool>(m, false)));queue<tiii> q;for (int i = 0; i < n; ++i) {int mask = 1 << i;vis[i][i][mask] = true;q.push({i, i, mask});}for (auto& e : edges)if (label[e[0]] == label[e[1]]) {int mask = (1 << e[0]) | (1 << e[1]);if (!vis[e[0]][e[1]][mask]) {vis[e[0]][e[1]][mask] = true;q.push({e[0], e[1], mask});}}int ans = 0;while (!q.empty()) {auto [u, v, mask] = q.front();q.pop();int cnt = __builtin_popcount(mask);ans = max(ans, cnt);for (int& a : adj[u])if (((mask >> a) & 1) == 0)for (int& b : adj[v])if (((mask >> b) & 1) == 0 && a != b && label[a] == label[b]) {int maskt = mask | (1 << a) | (1 << b);if (!vis[a][b][maskt]) {vis[a][b][maskt] = true;q.push({a, b, maskt});}}}return ans;}
};