给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
原题链接
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 58
思路
此题是一道DP常规问题,根据题意搞清楚状态表示和状态转移即可;
可以注意到,题目中并没有给出m的上限,但实际上我们也可以不用它来建立状态表示,也就是说一维表示足矣,dp[i]
表示为将长度为i的绳子剪成合法的若干份对应的方案,抽象为最大乘积;
如何进行状态转移?也就是所谓的集合划分,我们可以枚举上述方案中最后一段长度为j的所有情况:
j * (i - j)
j * dp[i - j]
class Solution {
public:
int cuttingRope(int n) {
// dp[i][j] 状态表示:长度为i的绳子对应的所有方案
// 状态转移:枚举最后一段的长度 max(j * (i - j), j * dp[i - j])
vector<int> dp(n + 1, 0);
for(int i = 2; i <= n; i ++)
for(int j = 1; j < i; j ++)
dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]));
return dp[n];
}
};
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
原题链接
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 1000
思路
此题在剪绳子I
的基础上扩充了数据范围,用DP做的话会超时,这里给出一种数学证明的方式解决此题:
下面我们给出证明:
首先把一个正整数 NN 拆分成若干正整数只有有限种拆法,所以存在最大乘积。
假设 N=n1+n2+…+nk
,并且 n1×n2×…×nk
是最大乘积。
显然1不会出现在其中;
ni≥5
,那么把 ni
拆分成 3+(ni−3)
,我们有3(ni−3)=3ni−9>ni
;ni=4
,拆成 2+2
乘积不变,所以不妨假设没有4;3×3>2×2×2
,所以替换成3乘积更大;时间复杂度分析:当 n 比较大时,n 会被拆分成 n/3
个数,我们需要计算这么多次减法和乘法,所以时间复杂度是 O(n)
。
class Solution {
public:
int cuttingRope(int n) {
if(n <= 3) return 1 * (n - 1);
long res = 1;
if(n % 3 == 1) res = 4, n -= 4;
else if(n % 3 == 2) res = 2, n -= 2;
while(n) {
res *= 3;
res %= 1000000007;
n -= 3;
}
return (int)res;
}
};
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
原题链接
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100
思路
此题是可以用一个变量表示状态的 DP 问题,设变量为s
:
状态表示
s
表示为:以前一个数结尾的连续子数组的最大和;
状态转移
s < 0
以当前数x
结尾的连续子数组最大和为 x;s >= 0
以当前数x
结尾的连续子数组最大和为 s + x;
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res = INT_MIN, s = 0;
for(auto num : nums) {
if(s < 0) s = 0;
s += num; //更新(转移)
res = max(res, s);
}
return res;
}
};
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
原题链接
示例 1:
输入:n = 12
输出:5
示例 2:
输入:n = 13
输出:6
限制:
1 <= n < 2^31
思路
此题的主要思路是:遍历 n
的每一位,每轮迭代求出该位为1时的所有合法数字的个数,因此需要对各种情况进行讨论。
n为abcdef,假设此时遍历到了第2位(从左到右0算起),下面求第2位是1在1~n中的数字个数:
- 当前两位是
00 ~ ab - 1
: 后三位000 ~ 999
->ab ✖️ 1000
;- 当前两位是
ab
:
- c = 0: 0
- c = 1: 0 ~ def -> def + 1
- c > 1: 000 ~ 999 -> 1000
综上,
res
=ab * 1000
+0
+def + 1
+1000
.
class Solution {
public:
int countDigitOne(int n) {
if(n <= 0) return 0;
vector<int> num;
// 将n的每一位都提取到num中
while(n) {
num.push_back(n % 10);
n /= 10;
}
long long res = 0;
for(int i = num.size() - 1; i >= 0; i --) {
// left存第i位左边的数,right存第i位右边的数,t用来存10的整数幂
int left = 0, right = 0, t = 1;
for(int j = num.size() - 1; j > i; j --)
left = left * 10 + num[j];
for(int j = i - 1; j >= 0; j --) {
right = right * 10 + num[j];
t *= 10;
}
res += left * t;
if(num[i] == 1)
res += right + 1;
else if(num[i] > 1)
res += t;
}
return res;
}
};
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
原题链接
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
提示:
0 <= num < 231
思路
此题求方案数,应该马上想到使用 dfs
或 dp
,一般情况下前者比后者的复杂度要高而且递归函数也比较难构造 ,所以这题我们就用 dp
来做(事实上 dfs
也确实超时)。
- 状态表示
f [i]: 以第 i 个数为结尾的方案数- 状态转移
f [i] = f[i-1] + f[i - 2]
f[i - 1]: 表示第 i 个数字对应一个字符;
f[i - 2]: 表示第 i/i - 1 两个数字对应一个字符;
这个 dp
很容易构造,但有几个问题需注意:
i/i-1
两个数字 (设此数为t
) 构成了一个字符,由于合法字符对应的数字范围在0 ~ 25之间,因此 t<=25
。同时,类似07
这样的数是无法对应一个字符的,因此t>=10
;i-1/i-2
这样的下标,我们必须处理边界。class Solution {
public:
int translateNum(int num) {
vector<int> s;
while(num) s.push_back(num % 10), num /= 10;
reverse(s.begin(), s.end());
int n = s.size();
vector<int> f(n + 1);
f[0] = 1;
for(int i = 1; i <= n; i ++) {
f[i] = f[i - 1];
if(i > 1) {
int t = s[i - 2] * 10 + s[i - 1];
if(t >= 10 && t <= 25) f[i] += f[i - 2];
}
}
return f[n];
}
};
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
原题链接
示例 1:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
提示:
0 < grid.length <= 200
0 < grid[0].length <= 200
思路
此题是经典的dp
问题:
- 状态表示
f[i][j]
: 表示走到第(i,j)
个格子能得到的最大价值;- 状态转移
f[i][j] = max(f[i-1][j], f[i][j-1]) + grid[i-1[j-1]
从上边来的:f[i-1][j]
从左边来的:f[i][j-1]
最后别忘了加上(i,j)
当前格子对应的价值。
class Solution {
public:
int maxValue(vector<vector<int>>& grid) {
int n = grid.size(), m = grid[0].size();
vector<vector<int>> f(n + 1, vector<int>(m + 1));
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
f[i][j] = max(f[i - 1][j], f[i][j - 1]) + grid[i - 1][j - 1];
return f[n][m];
}
};
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
原题链接
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1 是丑数。
n 不超过1690。
思路
此题的做法很具技巧性,我们考虑将原丑数序列拆解成四部分:
其中
s
是我们需要求的丑数序列,s1
s2
s3
分别是包含质因子2、3、5的子序列;
s
: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 … …
s1
: 2, 4, 6, 8, 10, 12 … …
s2
: 3, 6, 9, 12 … …
s3
: 5, 10 … …
那么,原丑数序列应该是1和上面三个子序列的并集,也就是所谓的四部分。
看完上面的思路你肯定会提出疑问,s1
s2
s3
并不是只包含质因子2、3、5的子序列啊,难道取并集不会重复吗?确实会重复的,注意这里的并集不是标准意义上的并集,我们可以在代码中略施小计将这些重复元素过滤掉。
那你又该问了,直接将原序列分解成只包含2、3、5的子序列不就好了吗,这就是此题的关键所在,如果带上了这个“只”字,那么就无法得到下面这个最关键的性质:
如果
s1
s2
s3
分别为包含2、3、5质因子的子序列,那么它们与原序列s
存在下面的关系:
s1 / 2 = s
s2 / 3 = s
s3 / 5 = s
上面这三个表达式什么意思呢,就是对于s1
s2
s3
三个子序列而言,它们的元素分别除以2、3、5会得到原丑数序列,反之原丑数序列分别乘以2、3、5也会得到这三个子序列。
这样一来就将此问题转化成了一个三路归并问题,可以分别设三个指针i
j
k
分别指向这三个子序列的头,实际上就是指向原序列的头,因为根据上面的分析原序列可以转化为这三个子序列,而且在原序列s
的扩充过程中,i
j
k
是不会访问越界的,因为这三个指针从0
开始扫描每次最多移动一位,而原序列每轮循环一定会增加一个元素且在开始时就存在1
这个元素。
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> q(1, 1);
int i = 0, j = 0, k = 0;
while(-- n) {
int t = min(q[i] * 2, min(q[j] * 3, q[k] * 5));
q.push_back(t);
if(t == q[i] * 2) i ++;
if(t == q[j] * 3) j ++;
if(t == q[k] * 5) k ++;
}
return q.back();
}
};
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
原题链接
示例 1:
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
限制:
1 <= n <= 11
思路
此题属于典型的dp
问题,注意扔n个骰子和一个骰子扔了n次是等价的。
- 状态表示
dp[i][j]
: 扔i
个骰子相应的点数之和为j
的投法- 状态转移
根据最后一个骰子对应的点数k
进行集合划分:
dp[i][j] += dp[i - 1][j - k]
枚举k (1 <= k <= 6)
class Solution {
public:
vector<double> dicesProbability(int n) {
vector<double> res;
vector<vector<int>> f(n + 1, vector<int>(6 * n + 1, 0));
f[0][0] = 1;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= 6 * i; j ++)
for(int k = 1; k <= 6; k ++)
if(j >= k)
f[i][j] += f[i - 1][j - k];
vector<int> nums(f[n].begin() + n, f[n].end());
int sum = 0;
for(auto x : nums)
sum += x;
for(auto x : nums)
res.push_back((double)x / sum);
return res;
}
};
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
原题链接
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
限制:
0 <= 数组长度 <= 10^5
思路
此题可以用双指针解决,i
j
的目标是寻找序列中最小和最大的数字,其中这个最大数字一定要在最小数字的右侧才符合题目要求。
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.empty()) return 0;
// 分别找到最低和最高股票价格
int minv = nums[0], res = 0;
for(int i = 1; i < prices.size(); i ++) {
res = max(res, nums[i] - minv);
minv = min(nums[i], minv);
}
}
};
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
原题链接
示例 1:
输入:n = 2
输出:1
示例 2:
输入:n = 5
输出:5
提示:
0 <= n <= 100
class Solution {
public:
int fib(int n) {
if(n == 0) return 0;
if(n == 1) return 1;
vector<int> dp(n + 1, 0);
dp[1] = 1;
for(int i = 2; i <= n; i ++) {
dp[i] = dp[i - 1] + dp[i - 2];
dp[i] %= 1000000007;
}
return dp[n];
}
};