依旧晚上没做,后面的才来补题的,听说很简单。
第一题:模拟。
第二题:贪心 + 暴力枚举。
第三题:思维 + 倒退。
第四题:DP。
详细题解如下。
1. 逐步求和得到正数的最小值(Minimum Value to Get Positive Step by Step Sum)
2. 和为 K 的最少斐波那契数字数目(Find the Minimum Number of Fibonacci Numbers Whose Sum is K)
3.长度为 n 的开心字符串中字典序第 k 小的字符串(the K th Lexicographical String of All Happy Strings of Length N)
LeetCode第24场双周赛地址:
https://leetcode-cn.com/contest/biweekly-contest-24/
1. 逐步求和得到正数的最小值(Minimum Value to Get Positive Step by Step Sum)
题目链接
https://leetcode-cn.com/problems/minimum-value-to-get-positive-step-by-step-sum/
题意
给你一个整数数组 nums 。你可以选定任意的 正数 startValue 作为初始值。
你需要从左到右遍历 nums 数组,并将 startValue 依次累加上 nums 数组中的值。
请你在确保累加和始终大于等于 1 的前提下,选出一个最小的 正数 作为 startValue 。
示例 1:
输入:nums = [-3,2,-3,4,2] 输出:5 解释:如果你选择 startValue = 4,在第三次累加时,和小于 1 。 累加求和 startValue = 4 | startValue = 5 | nums (4 -3 ) = 1 | (5 -3 ) = 2 | -3 (1 +2 ) = 3 | (2 +2 ) = 4 | 2 (3 -3 ) = 0 | (4 -3 ) = 1 | -3 (0 +4 ) = 4 | (1 +4 ) = 5 | 4 (4 +2 ) = 6 | (5 +2 ) = 7 | 2
提示:
1 <= nums.length <= 100
-100 <= nums[i] <= 100
解题思路
根据题目意思,我们需要知道一个最小数字 ,使得对整个数组累加的过程中,都满足 >= 1
根据这个数字是多少,我们可以假设一开始是 1
那么在遍历数组的过程中,发现累加 < 1,说明初始数不够大,那么我们要增大初始数,使得这个累加 = 1 (使得初始数增加的值是最少的)
这是因为,我们一开始就设初始值,和在过程中,发现 < 1 了再去增大初始值是一样的。都是保证每次累加 >= 1。
AC代码(C++)
class Solution {
public:
int minStartValue(vector<int>& nums) {
int res = 1; // 一开始初始值
int cur = 1; // 目前的累加值
for(auto num : nums)
{
cur += num;
if(cur < 1) // 发现累加值 < 1,说明初始值要增大,要使得 cur -> 1。
{
res += (1 - cur);
cur = 1;
}
}
return res;
}
};
2. 和为 K 的最少斐波那契数字数目(Find the Minimum Number of Fibonacci Numbers Whose Sum is K)
题目链接
https://leetcode-cn.com/problems/find-the-minimum-number-of-fibonacci-numbers-whose-sum-is-k/
题意
给你数字 k ,请你返回和为 k 的斐波那契数字的最少数目,其中,每个斐波那契数字都可以被使用多次。
斐波那契数字定义为:
- F1 = 1
- F2 = 1
- Fn = Fn-1 + Fn-2 , 其中 n > 2 。
数据保证对于给定的 k ,一定能找到可行解。
示例 1:
输入:k = 7 输出:2 解释:斐波那契数字为:1,1,2,3,5,8,13,…… 对于 k = 7 ,我们可以得到 2 + 5 = 7 。
示例 2:
输入:k = 19 输出:3 解释:对于 k = 19 ,我们可以得到 1 + 5 + 13 = 19 。
提示:
- 1 <= k <= 10^9
解题思路
根据题意,先要知道 整个 斐波那契数列。
然后利用 k ,不断的去找,最接近 k 的最大值,然后 减去。这样子,每加一个数字(同样的开销),得到的收益是最大的。
AC代码(C++)
class Solution {
public:
int findMinFibonacciNumbers(int k) {
vector<int> nums;
nums.push_back(1);
nums.push_back(1);
int a = 1, b = 1, c = a + b;
while(c <= k)
{
nums.push_back(c);
a = b;
b = c;
c = a + b;
}
int res = 0;
for(int i = nums.size() - 1;i >= 0; --i)
{
if(k == 0) break;
if(k >= nums[i])
{
k -= nums[i];
++res;
}
}
return res;
}
};
3.长度为 n 的开心字符串中字典序第 k 小的字符串(the K th Lexicographical String of All Happy Strings of Length N)
题目链接
https://leetcode-cn.com/problems/the-k-th-lexicographical-string-of-all-happy-strings-of-length-n/
题意
一个 「开心字符串」定义为:
- 仅包含小写字母 ['a', 'b', 'c'].
- 对所有在 1 到 s.length - 1 之间的 i ,满足 s[i] != s[i + 1] (字符串的下标从 1 开始)。
比方说,字符串 "abc","ac","b" 和 "abcbabcbcb" 都是开心字符串,但是 "aa","baa" 和 "ababbc" 都不是开心字符串。
给你两个整数 n 和 k ,你需要将长度为 n 的所有开心字符串按字典序排序。
请你返回排序后的第 k 个开心字符串,如果长度为 n 的开心字符串少于 k 个,那么请你返回 空字符串 。
示例 1:
输入:n = 1, k = 3 输出:"c" 解释:列表 ["a", "b", "c"] 包含了所有长度为 1 的开心字符串。按照字典序排序后第三个字符串为 "c" 。
示例 2:
输入:n = 1, k = 4 输出:"" 解释:长度为 1 的开心字符串只有 3 个。
示例 3:
输入:n = 3, k = 9 输出:"cab" 解释:长度为 3 的开心字符串总共有 12 个 ["aba", "abc", "aca", "acb", "bab", "bac", "bca", "bcb", "cab", "cac", "cba", "cbc"] 。第 9 个字符串为 "cab"
提示:
1 <= n <= 10
1 <= k <= 100
解题分析
我们根据题目分析,可以知道
n = 1 时,有三个
n = 2时,在上一个基础上,每个扩展出 2 个。
那么反过来,
比如 我们要得到 最后中的 第 k 个(n),那么根据 n - 1 扩展 到 n 的时候,每一次都有两个,那么从 n 回到 n - 1,那就是 n - 1 中 的 第 (k + 1) / 2。
所以我们根据 n ,计算 n 轮 k 的值
然后从一开始往后推
比如 一开始 = 2,说明是 b,到后面的时候,要注意,因为 b 是对应两个,那么需要把后面的值,变成 0 或者 1。
也就是 (v + 1) % 2。
然后可能是 0 或者 1。那么此时本来应该要取 a,b,c 中第 0 个或第 1 个。(这里是按顺序,但是不一定 a 就是第 0 个。)因为要求是和上一个不同,才开始计数。
那么当计数 == (v + 1) % 2,所以对应的字母,接在后面。
这样子,时间复杂度是 O(n)。
AC代码(C++)
class Solution {
public:
string getHappyString(int n, int k) {
if(3 * (int)pow(2, n - 1) < k) return "";
vector<int> v;
for(int i = 0;i < n; ++i)
{
v.push_back(k);
k = (k + 1) / 2;
}
int i = v.size() - 1;
string ans = "";
ans += char(v[i] - 1 + 'a');
for(i--; i >= 0; --i)
{
int cur = (v[i] + 1) % 2;
int cnt = -1;
for(int j = 0;j < 3; ++j)
{
if(j + 'a' == ans.back()) continue;
else ++cnt;
if(cnt == cur)
{
ans += char(j + 'a');
break;
}
}
}
return ans;
}
};
4.恢复数组(Restore the Array)
题目链接
https://leetcode-cn.com/problems/restore-the-array/
题意
某个程序本来应该输出一个整数数组。但是这个程序忘记输出空格了以致输出了一个数字字符串,我们所知道的信息只有:数组中所有整数都在 [1, k] 之间,且数组中的数字都没有前导 0 。
给你字符串 s 和整数 k 。可能会有多种不同的数组恢复结果。
按照上述程序,请你返回所有可能输出字符串 s 的数组方案数。
由于数组方案数可能会很大,请你返回它对 10^9 + 7 取余 后的结果。
示例 1:
输入:s = "1000", k = 10000 输出:1 解释:唯一一种可能的数组方案是 [1000]
示例 2:
输入:s = "1317", k = 2000 输出:8 解释:可行的数组方案为 [1317],[131,7],[13,17],[1,317],[13,1,7],[1,31,7],[1,3,17],[1,3,1,7]
示例 3:
输入:s = "1234567890", k = 90 输出:34
提示:
1 <= s.length <= 10^5
.s
只包含数字且不包含前导 0 。1 <= k <= 10^9
.
解题分析
看了题目之后,第一想到的就是动态规划。
1)那么就要设状态,根据题目,状态应该有 第 i 个数(字符)的操作,是应该单独自己,还是连接到前面,那么要连接到前面几个,要记录下来,这是因为要知道链接上去得数,会不会超过 k 得范围。
因此,有两个状态要记录,dp[ i ][ j ],即在 第 i 个字符时,单独自己(自己作为一个新得,1),或者和前面链接(记录前面的链接可能 all j,那么当前就为 j + 1)
2)状态转移
有两种情况
- 第一种情况,自己作为一个新数,那么这样子,所有 i - 1 的所有 j 都可以转移过来,因为是成立的。dp[ i ][ 1 ] += dp[ i - 1 ][ j ]
- 注意了,这种情况有一个前提,当前这个数不能是 0,因为 0 不能作为一个新数 的开始
- 第二种情况,链接到前面,那么就遍历 i - 1 的所有可能的 j,如果可以,那么就是 dp[ i ][ j + 1 ] += dp[ i - 1][ j ]。
那么判断 j 的长度,最多 10,因为组成新的 -> 11,就会超过 k 的范围 (10 ^ 9)
那么现在就要判断,第二种情况,什么时候的 j 是可以的
- 其实就是 x .... i - 1 i,[x, i - 1] 总长度 j
- 如果 x 这个位置是 0,那么说明是不可能从这个组成一个数的,不可以
- 从 x 一直到 i 位置,组成的数,要在 k 的范围内。满足 可以,不满足则不可以。
3)初始值,dp[ 1 ][ 1 ] = 1,其他都是 0。一开始,只有一个数的时候,只能自己组成一个新数。
AC代码(方法一、DP C++)
const int MAXN = 1e5 + 50;
const int MOD = 1e9 + 7;
typedef long long LL;
class Solution {
public:
int dp[MAXN][15];
bool check(string& s, int k, int ed, int len)
{
int st = ed + 1 - len;
if(s[st] == '0') return false;
long long val = 0;
for(int i = st;i < st + len + 1; ++i)
{
val = val * 10 + (s[i] - '0');
}
if(val > k) return false;
return true;
}
int numberOfArrays(string s, int k) {
memset(dp, 0, sizeof(dp));
dp[1][0] = 0;
if(s[0] - '0' > k) return 0; // 因为一开始 第一个数,肯定组成一个新数,不能是 0
dp[1][1] = 1;
int n = s.size();
for(int i = 2;i <= n; ++i)
{
if(s[i - 1] - '0' <= k && s[i - 1] - '0' != 0) // 自己组成新数的前提
{
for(int j = 1;j <= 10; ++j)
{
dp[i][1] = (dp[i][1] + dp[i - 1][j]) % MOD;
}
}
for(int j = 1;j <= 10; ++j) // 和前面链接,枚举所有 可能 的 j ,看看那种情况可以转移过来
{
if(i - 1 < j) break;
if(!check(s, k, i - 2, j)) continue;
dp[i][j + 1] = (dp[i][j + 1] + dp[i - 1][j]) % MOD;
}
}
// 最后答案,因为我们要 n 长度,但是最后一个位置,我们不知道他到底是自己,还是和前面链接
// 但是这些都符合要求,所以最后答案,是全部加起来。
int ans = 0;
for(int j = 1;j <= 10; ++j) ans = (ans + dp[n][j]) % MOD;
return ans;
}
};