赛题传送门
赛题
给你一个字符串
num
,表示一个大整数。如果一个整数满足下述所有条件,则认为该整数是一个 优质整数 :1) 该整数是num
的一个长度为3
的 子字符串 。2) 该整数由唯一一个数字重复3
次组成。以字符串形式返回 最大的优质整数 。如果不存在满足要求的整数,则返回一个空字符串 “” 。
这题两种思路,第一种就是遍历字符串,把长度为 3
的子字符串都拿出来对比,看是不是优质整数。当然,因为优质整数总共就 10
种,分别是 000, 111, 222, ...
这些,所以我们直接在原字符串中查找这些子字符串是否存在即可。
因为要找的是 最大的,所以我们沿着 999 -> 000
的方向找,一旦找到即可退出循环返回答案。
for (int i = 9; i >= 0; i--) {
string sub;
for (int j = 0; j < 3; j++) {
sub.push_back(i + '0');
if (num.find(sub) != string::npos) // 找到了一个,就直接返回
return sub;
};
return ""; // 没有优质整数返回空字符串
不论上述哪种方式,时间复杂度都是 O ( n ) O(n) O(n),空间复杂度都是 O ( 1 ) O(1) O(1)。
完整代码见 GitHub。
赛题
给你一棵二叉树的根节点 root ,找出并返回满足要求的节点数,要求节点的值等于其 子树 中值的 平均值 。
n
个元素的平均值可以由n
个元素 求和 然后再除以n
,并 向下舍入 到最近的整数。root
的 子树 由root
和它的所有后代组成。
简单来讲,遇到 树 的题目,大概率都是往 深度优先搜索 和 中序遍历 这些方向上想。
比如这道题,我们发现,一个节点要满足要求,需要依赖于子树上的节点。因此,我们在递归遍历每个节点的时候,都需要先递归其左右子树,并且根据左右子树的信息(包括节点总数和节点值之和),来判断当前节点是否满足条件。
所以我们引用传递 ans
来记录满足条件的节点总数,递归函数的返回值是一个数组 vector
,第一个元素代表当前节点及所有子树节点的 值总和,第二个元素代表当前节点及所有子树节点的 数量总和。
根据要求,我们遍历当前节点 root
时,首先遍历其左右子树并获得了左右子树的值总和和数量总和,于是再加上 root->val
求平均便可以判断是否满足要求。
vector _averageOfSubtree(TreeNode *root, int& ans) {
if (root == NULL) return {0, 0};
else {
vector left = _averageOfSubtree(root->left, ans);
vector right = _averageOfSubtree(root->right, ans);
int tot = left[0] + right[0] + root->val; // 值总和
int cnt = left[1] + right[1] + 1; // 数量总和
ans += tot / cnt == root->val; // 如果满足条件,就 + 1
return {tot, cnt};
};
};
然后在主函数中调用递归函数 _averageOfSubtree
即可,注意 ans
才是最终结果而不是递归函数的返回值。
int averageOfSubtree(TreeNode* root) {
int ans = 0;
_averageOfSubtree(root, ans);
return ans;
}
这种做法会遍历且仅遍历一次每个子节点,时间复杂度与子节点数量成正比。
完整代码见 GitHub。
赛题
题目太长,概括就是,老式手机输入字母的时候,要根据字母在对应数字键下的排序位置按多次,比如 2
键就对应 abc
三个字母,那么要打出 c
来就要按 3
次 2
键。现在给你一串数字比如 222
,问有多少种对应的字母可能性,比如 222
就能对应 4 种: c, ab, ba, aaa
。
一看给出的字符串长度是 1 0 5 10^5 105 数量级,再加上每个按出的数字都可能和前一次的数字结合形成新的字母,所以,肯定是要用 动态规划 的啦。
具体地,对于每个数字 pressKeys[i]
,如果它不等于 pressKeys[i - 1]
,那么考虑截止下标 i
的子字符串,必然是截止到 i - 1
的子字符串的数字可能排列,再加上 pressKeys[i]
一次对应的字母,所以可能性总和没有发生变化。
如果 pressKeys[i] == pressKeys[i - 1]
,那么就要考虑它和前一个数字共同形成一个字母,同理,还得考虑 pressKeys[i]
和前 2,3 个数字共同形成一个字母的情况。
int n = pressedKeys.size(), mod = 1e9 + 7;
vector dp(n, 0); //代表截止到当前下标的方案数
dp[0] = 1; // 只有一个数字就只有一种可能
for (int i = 1; i < pressedKeys.size(); i++) {
int last = i;
while (last >= 0 && pressedKeys[i] == pressedKeys[last]) {
int same = i - last + 1; // 相同字符个数
if (same > 3 && (pressedKeys[i] == '2' || pressedKeys[i] == '3' || pressedKeys[i] == '4' || pressedKeys[i] == '5' || pressedKeys[i] == '6' || pressedKeys[i] == '8')) break; // 注意有些数字只对应 3 个字母
else if (same > 4) break; // 数字最多对应 4 个字母
else {
dp[i] += last == 0 ? 1 : dp[last - 1]; // 注意考虑边界情况
dp[i] %= mod;
last--;
};
};
};
return dp[n - 1];
这种做法的时间复杂度是 O ( n ) O(n) O(n),空间复杂度也是 O ( n ) O(n) O(n)。
完整代码见 GitHub。
赛题
题目太长,概括如下:从 (0, 0)
走到 (m - 1, n - 1)
,每一步只能往下或者往右走。每个格子都有值 (
或者 )
,需要判断是否存在至少一条路径,能够满足所经过的格子中括号值(按照经过顺序排列)是合法的,合法意味着匹配,就像我们日常打括号一样,()
是匹配的,((()))()(())
也是匹配的,但 (()))
就多了一个右括号不是匹配的。
周赛能看到这么 easy 的 hard 题,实属不易。很明显,动态规划 摆上台面,由于限制了行走方向只能 向右 或者 向下,所以子状态只能从左侧一格或者上面一格来。那么我们用什么来作为子状态呢?
答案是 未匹配的左括号 (
数量。我们可以把右括号 )
看成消除左括号的工具,因此如果当前格子是右括号,那么它就可以消除一个未匹配左括号,如果当前格子是左括号,那么就增加一个未匹配的左括号,最后我们只需要判断终点处是否存在未匹配的左括号数量为 0
。
因为每个格子都会有多种 未匹配的左括号 数量,因此每个状态都需要用 set
来记录。
int n = grid.size(), m = grid[0].size();
vector>> dp(n, vector>(m));
每次状态转移都根据左侧格子状态和右侧格子状态和当前格子的括号类型来做。
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++) {
if (i == 0 && j == 0) { // 特殊处理
if (grid[0][0] == ')') return false;
else dp[0][0].insert(1);
} else {
if (i > 0) {
if (grid[i][j] == '(')
for (const int& num : dp[i - 1][j])
dp[i][j].insert(num + 1);
if (grid[i][j] == ')')
for (const int& num : dp[i - 1][j])
if (num > 0)
dp[i][j].insert(num - 1);
};
if (j > 0) {
if (grid[i][j] == '(')
for (const int& num : dp[i][j - 1])
dp[i][j].insert(num + 1);
if (grid[i][j] == ')')
for (const int& num : dp[i][j - 1])
if (num > 0)
dp[i][j].insert(num - 1);
};
};
};
这种做法的时间复杂度是 O ( m n ⋅ ( m + n ) ) O(mn\cdot (m + n)) O(mn⋅(m+n)),因为每个状态最多有 ( m + n ) (m + n) (m+n) 种未匹配左括号数量的可能性。空间复杂度的话,因为用到了集合来存储每个状态,所以也是 O ( m n ⋅ ( m + n ) ) O(mn\cdot(m + n)) O(mn⋅(m+n))
完整代码见 GitHub。
这场周赛是我在 2022-5-8
号上午准时打的,题目实在是、真的是太简单了,我甚至都只花了 30 30 30 分钟就全部 AC
了,还是很快乐的!最近在钻研 k8s 的网络实现,冲冲冲!
好啦,以上就是力扣第 292 场周赛的全部思路啦。最后,欢迎关注我的 GitHub 账号。