双周赛补题,总体不难,最后一题的 DP,暴力DP也是可以卡着时间过的,不过还是有优化的DP计算。
第一题:模拟。
第二题:模拟 + 最大公约数 gcd。
第三题:树的 DFS。
第四题:暴力DP 或者 优化后DP。
详细题解如下。
LeetCode第26场双周赛地址:
https://leetcode-cn.com/contest/biweekly-contest-26/
1. 连续字符
题目链接
https://leetcode-cn.com/problems/consecutive-characters/
题意
给你一个字符串
s
,字符串的「能量」定义为:只包含一种字符的最长非空子字符串的长度。请你返回字符串的能量。
示例 1:
输入:s = "leetcode" 输出:2 解释:子字符串 "ee" 长度为 2 ,只包含字符 'e' 。
提示:
1 <= s.length <= 500
s
只包含小写英文字母。
解题思路
很简单的题目,其实就是遍历一次字符串,判断,如果当前 字符 == 上一个字符,那么长度 + 1。否则,是新的字符了,那么重置 长度 = 1,然后继续判断。
每一次都取 长度 的最大,那么就可以得到答案了。
AC代码(C++)
class Solution {
public:
int maxPower(string s) {
int n = s.size();
int ans = 1;
int cnt = 1;
for(int i = 1;i < n; ++i)
{
if(s[i] == s[i - 1])
{
++cnt;
}
else
{
cnt = 1;
}
ans = max(ans, cnt);
}
return ans;
}
};
2. 最简分数
题目链接
https://leetcode-cn.com/problems/simplified-fractions/
题意
给你一个整数
n
,请你返回所有 0 到 1 之间(不包括 0 和 1)满足分母小于等于n
的 最简 分数 。分数可以以 任意 顺序返回。示例 1:
输入:n = 2 输出:["1/2"] 解释:"1/2" 是唯一一个分母小于等于 2 的最简分数。
示例 3:
输入:n = 4 输出:["1/2","1/3","1/4","2/3","3/4"] 解释:"2/4" 不是最简分数,因为它可以化简为 "1/2" 。
提示:
- 1 <= n <= 100
解题思路
一开始拿到题目,想到就是,如果我们可以枚举所有情况,也就是枚举 分子 和 分母,这样子时间复杂度是 O(n ^ 2)。然后判断其中那些情况是满足要求的。
满足要求,不能再化简,其实就是,分子和分母的最大公约数是 1,那么我们有计算最大公约数的,gcd,其中可以使用 C++ 自带的 __gcd 函数,也可以自己写,其实很简单的。
gcd 的时间复杂度大概是 O(log max(a, b) ,所以总时间复杂度是 O(n ^ 2 logn),不会超时。
AC代码(C++)
class Solution {
public:
int gcd(int a, int b)
{
if(b == 0) return a;
return gcd(b, a % b);
}
vector<string> simplifiedFractions(int n) {
vector<string> res;
for(int i = 1;i <= n - 1; ++i) // 枚举分子
{
for(int j = i + 1;j <= n; ++j) // 枚举分母
{
if(gcd(i, j) == 1)
{
string tep = to_string(i) + "/" + to_string(j);
res.push_back(tep);
}
}
}
return res;
}
};
3.统计二叉树中好节点的数目
题目链接
https://leetcode-cn.com/problems/count-good-nodes-in-binary-tree/
题意
给你一棵根为
root
的二叉树,请你返回二叉树中好节点的数目。「好节点」X 定义为:从根到该节点 X 所经过的节点中,没有任何节点的值大于 X 的值。
示例 1:
【示例有图,具体看链接】 输入:root = [3,1,4,3,null,1,5] 输出:4 解释:图中蓝色节点为好节点。 根节点 (3) 永远是个好节点。 节点 4 -> (3,4) 是路径中的最大值。 节点 5 -> (3,4,5) 是路径中的最大值。 节点 3 -> (3,1,3) 是路径中的最大值。
示例 2:
【示例有图,具体看链接】 输入:root = [3,3,null,4,2] 输出:3 解释:节点 2 -> (3, 3, 2) 不是好节点,因为 "3" 比它大。
示例 3:
输入:root = [1] 输出:1 解释:根节点是好节点。
提示:
- 二叉树中节点数目范围是
[1, 10^5]
。- 每个节点权值的范围是
[-10^4, 10^4]
。
解题分析
根据题目,是一个 树的操作题。
从题目可以分析,如果我们到了某个节点,如果能知道 其上面的 所有节点的 最大值,那么就可以利用 这个最大值 和当前节点的值进行比较,从而判断是否满足要求。然后,对于下一个节点 的最大值,应该是,最大值 和 当前节点的值,取最大。
因此,这么看的话,其实就是一个 DFS 的题目,从根节点,一直往 叶节点 走下去。同时,我们还需要额外记录一个信息,即 最大值。
AC代码(C++)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int ans;
void dfs(TreeNode* root, int mx)
{
if(root == nullptr) return;
if(root->val >= mx)
{
mx = root->val;
++ans;
}
dfs(root->left, mx);
dfs(root->right, mx);
}
int goodNodes(TreeNode* root) {
ans = 0;
dfs(root, -100000); // 一开始,对于根节点而言,它 上面的最大值,本来是没有的,但是根节点一定满足,所以设的一个最小值。
return ans;
}
};
4.数位成本和为目标值的最大数字
题目链接
https://leetcode-cn.com/problems/form-largest-integer-with-digits-that-add-up-to-target/
题意
给你一个整数数组 cost 和一个整数 target 。请你返回满足如下规则可以得到的 最大 整数:
- 给当前结果添加一个数位(i + 1)的成本为 cost[i] (cost 数组下标从 0 开始)。
- 总成本必须恰好等于 target 。
- 添加的数位中没有数字 0 。
由于答案可能会很大,请你以字符串形式返回。
如果按照上述要求无法得到任何整数,请你返回 "0" 。
示例 1:
输入:cost = [4,3,2,5,6,7,2,5,5], target = 9 输出:"7772" 解释:添加数位 '7' 的成本为 2 ,添加数位 '2' 的成本为 3 。所以 "7772" 的代价为 2*3+ 3*1 = 9 。 "997" 也是满足要求的数字,但 "7772" 是较大的数字。 数字 成本 1 -> 4 2 -> 3 3 -> 2 4 -> 5 5 -> 6 6 -> 7 7 -> 2 8 -> 5 9 -> 5
示例 2:
输入:cost = [7,6,5,5,5,6,8,7,8], target = 12 输出:"85" 解释:添加数位 '8' 的成本是 7 ,添加数位 '5' 的成本是 5 。"85" 的成本为 7 + 5 = 12 。
示例 3:
输入:cost = [2,4,6,2,4,6,4,4,4], target = 5 输出:"0" 解释:总成本是 target 的条件下,无法生成任何整数。
提示:
cost.length == 9
1 <= cost[i] <= 5000
1 <= target <= 5000
解题分析
看到题目,就会想到是 动态规划 DP 的问题。
方法一、暴力 DP
一开始,假设 dp[ i ] 是 消耗了 i 情况下的,最大字符串(长度最长,相同长度,则比较各位大小)
那么这样子的话,时间复杂度大概是 O(target * 9),但是在每一次我们还需要比较,新的字符串和当前保存的字符的大小,那么这里比较,只能逐位比较,那么字符串的最大长度,应该是 target(假设 cost 都是 1 的时候),那么总的时间复杂度是 O(target * 9 * target)。刚好可以时间复杂度卡过。
因此,我们就只要 对于新的 i ,我们找到 dp[ i - cost[ j ] ],然后加上新的位,得到新的字符串 tep,然后和 dp[ i ] 进行比较,取出最大的字符串。
那么最后,如果 dp[ target ] 还是 空字符串,那么说明,无法构建得到相应的字符串,那么 答案返回的是 "0"。否则直接返回 dp[ target ] 即可。
方法二、优化 DP
上面的时间复杂度,高,主要是我们在 得到新的字符串后,都要和当前字符串进行比较,因此这提高了复杂度。
那么我们考虑,为了使得 最后结果字符串 最大,我们希望的是,字符串的长度,尽可能长,同时相同长度的情况下,保证 取得数字 尽可能大。
所以,我们可以先 DP 得到 最大长度,那么得到最大长度后,我们只需要每次,对每一位都取 最大数 即可。
这样子,我们将 字符串比较 分开两部分,这样子,就可以降低复杂度。
首先,我们先计算 DP 得到 最大长度,其实也就是 f[ i ] 是消耗 i 情况下得最大长度。
那么转移方程就是 f[ i ] = max( f[ i - cost[ j ] ] + 1 )
初始化,因为是取最大值,所以一开始都设为 最小值。而 f[ 0 ] = 0,因为没有消耗,长度 == 0。
那么如何判断,是否存在答案呢?
其实,最后 f[ target ] 就是,我们消耗了那么多,得到得 最大长度,那么如果这个长度,<= 0,说明就 不存在 对应得情况,那么此时答案就可以直接返回 "0"
接着,我们已经知道了最大长度,也就是 f [ target ],那么我们每一次,去 取 数得时候,会有对应得 消耗 c,那么我们要求 f[ target] == f[ target - c ] + 1
因为我们要保证每一次,都是按之前 长度慢慢累加得情况转移过来得。因此,需要满足上面得情况。
在满足上面的情况下(也就是长度大家都一样),那么我们只要取当前位的最大数字即可,那么从 9 到 1,如果能取,相当于跳出。
那么此时 target = target - c,我们要一直 tareget 到 0 才结束。
时间复杂度而言,第一步的DP,为 O(target * 9), 第二步,其实也是 O(target * 9),所以总的时间复杂度是 O(target * 9)。
这个就比方法一的时间复杂度降低了。
AC代码(方法一、暴力DP C++)
const int MAXN = 5e3 + 5;
class Solution {
public:
// string dp[i]
string dp[MAXN];
bool check(string& a, string& b)
{
if(a.size() > b.size()) return true;
if(a.size() < b.size()) return false;
for(int i = 0;i < a.size(); ++i)
{
if(a[i] == b[i]) continue;
if(a[i] > b[i]) return true;
else
return false;
}
return false;
}
string largestNumber(vector<int>& cost, int target) {
for(int i = 0;i <= target; ++i) dp[i] = "";
for(int i = 1;i <= target; ++i)
{
for(int j = 0;j < 9; ++j)
{
if(i - cost[j] < 0) continue;
string tep = dp[i - cost[j]];
if(i - cost[j] > 0 && tep == "") continue; // 注意,如果从这个转移来 i 的,原来的字符串是空串,说明这个情况之前不可能有,那么也就不可能从这个状态转移到 i(除了 0 的情况下)
tep += (j + 1 + '0');
if(check(tep, dp[i])) dp[i] = tep; // 比较两个字符串的大小
}
}
if(dp[target] == "") return "0";
return dp[target];
}
};
AC代码(方法二、优化DP C++)
class Solution {
public:
string largestNumber(vector<int>& cost, int target) {
vector<int> f(target + 5, -1e9);
f[0] = 0;
for(int i = 1;i <= target; ++i)
{
for(int j = 0;j < 9; ++j)
{
if(i - cost[j] < 0) continue;
f[i] = max(f[i], f[i - cost[j]] + 1);
}
}
if(f[target] <= 0) return "0";
string ans = "";
int now = target;
while(now)
{
for(int j = 9;j >= 1; --j)
{
int c = cost[j - 1];
if(now - c < 0) continue;
if(f[now] == f[now - c] + 1)
{
ans += (j + '0');
now -= c;
break;
}
}
}
return ans;
}
};