递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存了子问题的解,避免重复计算。
斐波拉契类
- 爬楼梯
答案里面还提到了,矩阵迭代的方法,二刷的时候可以看看。 - 打家劫舍 I
f(n) = max(num[n]+f(n-2), num[n-1]+f(n-3)) 因为num[n] 和 nums[n-1] 必有一个要被算入
一开始还是想到的是递归。
想到递归离动态规划就不远了。建立递推公式,用变量表示出递推公式中等式右边的变量,再写循环。 - 打家劫舍 II
这道题和上一题很相似,但是我没有想出来。看了答案,发现即使是上一题,答案的解法也比我的解法要优雅。
状态定义:
转移方程:
初始状态:
返回值:
class Solution {
public:
int robCore(vector<int>& nums, int i, int j)
{
int preMax = 0, curMax = 0;
for(int k=i;k<=j;k++)
{
int temp = curMax;
curMax = max(nums[k]+preMax, curMax);
preMax = temp;
}
return curMax;
}
int rob(vector<int>& nums) {
if(nums.size()==0)
return 0;
if(nums.size()==1)
return nums[0];
return max(robCore(nums, 1, nums.size()-1), robCore(nums, 0, nums.size()-2));
}
};
矩阵路径问题
5. 矩阵的最小路径和
这正是前天晚上蔚来二面的算法题目。当时第一反应竟然是dfs…
奇怪的是,我今天竟然在没有看答案的情况下独立做出来了??
int minPathSum(vector<vector<int>>& grid) {
if(grid.size()==0 || grid[0].size()==0)
return 0;
vector<int> temp(grid[0].size(), 0);
vector<vector<int>> dp(grid.size(), temp); // 两条语句可以合并为 vector<vetor<int>> dp(grid.size(), vector<int> (grid[0].size()), 相当于起了一个无名变量
dp[0][0] = grid[0][0];
for(int i=1;i<grid[0].size();i++) //for loop 1
dp[0][i] = dp[0][i-1] + grid[0][i];
for(int j=1;j<grid.size();j++) // for loop 2
dp[j][0] = dp[j-1][0] + grid[j][0];
// actually for loop 1 and loop 2 above can be merged into one loop by if sentence
for(int i = 1;i<grid.size();i++)
for(int j=1;j<grid[0].size();j++)
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
return dp[grid.size()-1][grid[0].size()-1];
- 不同路径
yeah~ one submit, one 100%
数组区间类问题
7. 区域和检索
前两天每日一题中做过,此处就不做记录了。
8. 数组中等差递增子区间的个数
通过这道题有两点收获:
1)一开始我就写出了递推公式(状态转移方程),dp[i]=dp[i-1]+f(i),这不知道是哪根筋不对,就创建了一个vector int dp。简直是违背动态规划的初心。
2)对于(1)中这类等式右边只有一个dp变量的时候,我们也只需要 创建一个变量来做迭代。同理,如果等式右边有两个变量,比如:dp[i]=dp[i-1]+dp[i-2]这种,显然需要两个两个变量。
class Solution {
public:
int numOfAri(vector<int>& nums, int end)
{
int gap = nums[end] - nums[end-1];
int ans = 0;
for(int i=end-1;i>=1;i--)
{
if(nums[i]-nums[i-1] != gap) //这个地方一开始写反了:nums[i-1]-nums[i] != gap,
//显然忘记了
break;
ans++;
}
return ans;
}
int numberOfArithmeticSlices(vector<int>& nums) {
if(nums.size()<3)
return 0;
int n = nums.size();
int pre = numOfAri(nums, 2);
for(int i=3; i<n; i++)
{
pre = pre + numOfAri(nums, i);
}
return pre;
}
};
- 整数拆分
第一遍:自己不会做,答案看不懂。
第二遍:
class Solution {
public:
int integerBreak(int n) {
vector<int> dp(n+1,0);
dp[1] = 1;
for(int i=2;i<=n;i++)
{
for(int j=1; j<=i-1;j++)
{
dp[i] = max(dp[i], j * (i-j));
dp[i] = max(dp[i], j * dp[i-j]);
dp[i] = max(dp[i], dp[j] * (i-j));
dp[i] = max(dp[i], dp[j] * dp[i-j]);
//这个地方其实不需要分四种情况讨论,
//一下一种就足以
//curMax = Math.max(curMax, Math.max(j * (i - j), j * dp[i - j]));
//只需要分解其中一个,因为若两个都需要分解的话,定然在扫描之前更小数的时候就已经考虑过了
}
}
return dp[n];
}
};
- 完全平方数
这道题和第9题类似。
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n+1, INT_MAX);
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i<=n; i++)
{
if((int)sqrt(i)==sqrt(i))
{
dp[i] = 1;
continue;
}
for(int j = 1; j*j<=i/2; j++) //最终结果肯定是 一个平方数加另一个平方数,所以对于非平方数,可以直接跳过
{
dp[i] = min(dp[i], dp[j*j] + dp[i-j*j]);
}
}
return dp[n];
}
};
- 数字解码
答案牛逼!