375、我们正在玩一个猜数游戏,游戏规则如下:
我从 1 到 n 之间选择一个数字,你来猜我选了哪个数字。
每次你猜错了,我都会告诉你,我选的数字比你的大了或者小了。
然而,当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。直到你猜到我选的数字,你才算赢得了这个游戏。
示例:
n = 10, 我选择了8.
第一轮: 你猜我选择的数字是5,我会告诉你,我的数字更大一些,然后你需要支付5块。
第二轮: 你猜是7,我告诉你,我的数字更大一些,你支付7块。
第三轮: 你猜是9,我告诉你,我的数字更小一些,你支付9块。游戏结束。8 就是我选的数字。
你最终要支付 5 + 7 + 9 = 21 块钱。
给定 n ≥ 1,计算你至少需要拥有多少现金才能确保你能赢得这个游戏。
dp[i][j]:以从i到j的数字作为分割点(猜的数),必定赢的游戏所用钱的最小值。
dp[1][3]指只有三个数字1,2,3
- 以1作为分割点(猜 1):
- 答案是1,花费0元
- 答案是2 / 3,这个时候会进入另一个区间[2,3],花费1+dp[2][3]元
必定赢得游戏,最多花费max(0,1+dp[2][3])元
- 以2作为分割点(猜 2):
- 答案是1,花费2+dp[1][1]=2+0=2元
- 答案是2,花费0元
- 答案是3,花费2+dp[3][3]=2+0=2元
必定赢得游戏,最多花费max(0,2+dp[1][1],2+dp[3][3])元
- 以3作为分割点(猜 3):
- 答案是1 / 2,花费3+dp[1][2]元
- 答案是3,花费0元
必定赢得游戏,最多花费max(0,3+dp[1][2])元
综上,只要进入[1][3]这个区间,我们只要花费min( max(0,1+dp[2][3]) , max(0,2+dp[1][1],2+dp[3][3]) , max(0,3+dp[1][2]) )元必定可以赢的游戏。dp[1][3]也就等于那个min的值。
状态转移方程:
- dp[i][j]:从区间i~j赢得游戏的最小所需金额
- d0 = i + dp[i+1][j]; 选i点
- d1 = max(dp[i][k-1], dp[k+1][j]) + k; 选k点,i<k<j
- d2 = j + dp[i][j-1]; 选j点
- dp[i][j] = min(d0,d1,d2)
// dp[i][j]:从区间i~j赢得游戏的最小所需金额
// d0 = i + dp[i+1][j]; 选i点
// d1 = max(dp[i][k-1], dp[k+1][j]) + k; 选k点,i<k<j
// d2 = j + dp[i][j-1]; 选j点
// dp[i][j] = min(d0,d1,d2)
const int MAX = 1e4;
class Solution {
public:
int getMoneyAmount(int n) {
vector<vector<int>> dp(n + 5, vector<int>(n + 5, MAX));
dp[1][1] = 0;
for(int j = 2; j <= n; j++){
dp[j][j] = 0;
for(int i = j - 1; i >= 1; i--){
for(int k = i + 1; k < j; k++){
dp[i][j] = min(dp[i][j], max(dp[i][k-1], dp[k+1][j]) + k);
}
dp[i][j] = min(dp[i][j], min(i + dp[i+1][j], j + dp[i][j-1]));
}
}
return dp[1][n];
}
};
结果:
执行用时:60 ms, 在所有 C++ 提交中击败了19.88% 的用户
内存消耗:6.7 MB, 在所有 C++ 提交中击败了57.93%
376、如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
示例 1:
输入: [1,17,5,10,13,15,10,5,16,8]
输出: 7
解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。
特殊情况:[0,0] 输出:1
dp[i][j]:以i为结尾的最长序列,j:i属于上扬或者下垂。j=0,i属于下垂;j=1,i属于上扬
状态转移:找i前大于i,dp[i][0] = max(dp[j][1]); 找i前小于id,dp[i][1] = max(dp[j][0]);
// dp[i][j]:以i为结尾的最长序列,j:i属于上扬或者下垂。j=0,i属于下垂;j=1,i属于上扬
// 状态转移:找i前大于i,dp[i][0] = max(dp[j][1]); 找i前小于id,dp[i][1] = max(dp[j][0]);
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n = nums.size();
if(n <= 1) return n;
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][0] = 1;
dp[0][1] = 1;
bool flag = true;
for(int i = 1; i < n; i++){
if(nums[i]) flag = false;
for(int j = 0; j < i; j++){
if(nums[j] < nums[i]) dp[i][1] = max(dp[i][1], dp[j][0] + 1);
if(nums[j] > nums[i]) dp[i][0] = max(dp[i][0], dp[j][1] + 1);
}
}
if(flag) return 1;
return max(dp[n-1][0], dp[n-1][1]);
}
};
结果:
执行用时:24 ms, 在所有 C++ 提交中击败了7.56% 的用户
内存消耗:7.9 MB, 在所有 C++ 提交中击败了5.20% 的用户
修改:优化减少时间复杂度
dp[i][j]:前i个数字的最长序列,j=0,序列结尾为下垂;j=1,序列结尾为上扬
状态转移:若nums[i]>nums[i-1],dp[i][1] = dp[i-1][0]+1;dp[i][0] = dp[i-1][0]
若nums[i]<nums[i-1],dp[i][0] = dp[i-1][1]+1;dp[i][1] = dp[i-1][1]
若nums[i]=nums[i-1], dp[i][0] = dp[i-1][0], dp[i][1] = dp[i-1][1]
// dp[i][j]:前i个数字的最长序列,j=0,序列结尾为下垂;j=1,序列结尾为上扬
// 状态转移:若nums[i]>nums[i-1],dp[i][1] = dp[i-1][0]+1;dp[i][0] = dp[i-1][0]
// 若nums[i]<nums[i-1],dp[i][0] = dp[i-1][1]+1;dp[i][1] = dp[i-1][1]
// 若nums[i]=nums[i-1], dp[i][0] = dp[i-1][0], dp[i][1] = dp[i-1][1]
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n = nums.size();
if(n <= 1) return n;
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][0] = 1;
dp[0][1] = 1;
bool flag = true;
for(int i = 1; i < n; i++){
if(nums[i]) flag = false;
if(nums[i] > nums[i-1]) dp[i][1] = dp[i-1][0] + 1, dp[i][0] = dp[i-1][0];
else if(nums[i] < nums[i-1]) dp[i][0] = dp[i-1][1] + 1, dp[i][1] = dp[i-1][1];
else dp[i][0] = dp[i-1][0], dp[i][1] = dp[i-1][1];
}
if(flag) return 1;
return max(dp[n-1][0], dp[n-1][1]);
}
};
结果:
执行用时:4 ms, 在所有 C++ 提交中击败了57.96% 的用户
内存消耗:7.9 MB, 在所有 C++ 提交中击败了5.20% 的用户
377、给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
示例:
nums = [1, 2, 3]
target = 4所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)请注意,顺序不同的序列被视作不同的组合。
因此输出为 7。
状态转移:步长为nums[i]中元素,
dp[i] = sum(dp[i - nums[j]])
由于数据偏大,需要设置合理的MOD和数据类型long long
// dp[i]:和为i的组合数
// 状态转移:步长为nums[i]中元素,
// dp[i] = sum(dp[i - nums[j]])
class Solution {
public:
long long MOD = 1e12+7;
int combinationSum4(vector<int>& nums, int target) {
int n = nums.size();
vector<long long> dp(target + 1, 0);
dp[0] = 1;
for(int i = 1; i <= target; i++){
for(int j = 0; j < n; j++){
if(i - nums[j] >= 0) dp[i] += dp[i-nums[j]] % MOD;
}
}
return dp[target] % MOD;
}
};
结果:
执行用时:4 ms, 在所有 C++ 提交中击败了65.21% 的用户
内存消耗:6.9 MB, 在所有 C++ 提交中击败了77.92% 的用户