[leetcode] 动态规划(Ⅰ)
这次按通过率从高到低刷题。
本文完成的题目:{338, 1025, 303, 121, 53, 392, 70, 746, 198}
,带有「面试」Tag 的题目:Interview - {1617, 42, 1716, 0801}
。
大部分题目都是 Simple 难度的水题,可以作为动态规划的入门练习题。
比特位计数
题目[338]:一道位运算和动态规划结合的 题目 。
解题思路
状态定义:dp[i]
表示第 i 个自然数二进制中 1 的个数。
状态转移方程:dp[i] = dp[i >> 1] + (i & 1)
代码实现
class Solution {
public:
vector<int> countBits(int num) {
vector<int> v(num + 1);
if (num == 0) return v;
v[1] = 1;
for (int i = 2; i <= num; i++)
v[i] = v[i >> 1] + (i & 0x1);
return v;
}
};
除数博弈
题目[1025]:点击 此处 查看题目。
解法1:数学方法
规则是 Alice 先手,显然 2 到谁手上谁就是赢家。
- 若 N 是奇数
不管 Alice 选择什么,x 的值必然是奇数(包括 1 在内)。那么交给 Bob 的 N - x
是一个偶数,Bob 只要一直取 x = 2
,把一个奇数交给 Alice,那么最后 2 必然会落到 Bob 的手中。所以 N 为奇数,Alice 必输。
也可以得到一个结论:奇数先手必输。
- 若 N 是偶数
x 的值可奇可偶。在这时 Alice 只需要取 x = 1
把奇数 N - x
交给 Bob,此时对于 Bob 来说是「奇数先手」情况,Bob 必输。因此 N 为偶数,Alice 必赢。
bool divisorGame(int N) { return N % 2 == 0; }
解法2:动态规划
状态方程:dp[i] = true
表示 Alice 赢,否则 Bob 赢。
显然,对于 dp[i]
,只要出现 dp[i-x]
为 false
的情况 ( 0 < x < i
),dp[i]
就为 true
。因为一旦出现这种情况,Alice选择该 x 就能胜出
class Solution {
public:
bool divisorGame(int N) {
// return N % 2 == 0;
return dpSolution(N);
}
bool dpSolution(int N)
{
if (N == 1 || N == 3)
return false;
if (N == 2)
return true;
vector<bool> v(N+1);
v[1] = v[3] = false;
v[2] = true;
for (int i = 4; i <= N; i++)
for (int j = 1; j < i; j++)
if (i % j == 0 && !v[i - j])
{
v[i] = true;
break;
}
return v[N];
}
};
区域和检索 - 数组不可变
题目[303]:Click the Link to see the question.
解题思路
前缀和(这里是一维形式)。
状态定义:
dp[i] = 0 if i == 0
= sum(nums[0, ..., i-1]) if i >= 1
那么:sumRange(i,j) = sum(nums[0, ..., j]) - sum(nums[0, ..., i-1]) = dp[j+1] - dp[i]
.
代码实现
class NumArray
{
public:
vector<int> dp;
NumArray(vector<int> &nums)
{
int n = nums.size();
dp.resize(n + 1, 0);
dp[0] = 0;
for (int i = 1; i <= n; i++)
dp[i] = nums[i - 1] + dp[i - 1];
}
int sumRange(int i, int j) { return dp[j + 1] - dp[i]; }
};
最大子序列和
题目[Interview-1617]:点击 此处 查看题目。
本题与题目 Interview-42 和 题目53 相同。
解题思路
状态定义:定义 dp[i]
表示以 a[i]
结尾的最大连续子序列。
转移方程:dp[i] = max(a[i], dp[i-1] + a[i])
代码实现
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
vector<int> dp(nums);
int maxval = dp[0];
for (int i = 1; i < n; i++)
dp[i] = max(nums[i], dp[i-1] + nums[i]), maxval = max(maxval, dp[i]);
return maxval;
}
};
空间优化后:
int spaceOptimize(vector<int> &nums)
{
int dp = nums.front();
int n = nums.size();
int maxval = dp;
for (int i = 1; i < n; i++)
dp = max(dp + nums[i], nums[i]), maxval = max(maxval, dp);
return maxval;
}
买卖股票的最佳时机
题目[121]:点击 链接 查看题目。
解题思路
只扫描一遍,一边记录当前已找到的「最小的价格」,一边记录目前为止「最大利润」。
代码实现
#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))
class Solution {
public:
int maxProfit(vector<int>& prices) {
int minval = 0x3f3f3f3f;
int maxval = 0;
for (auto x: prices)
{
minval = min(x, minval);
maxval = max(x - minval, maxval);
}
return maxval;
}
};
按摩师
题目[Interview-1716]:这个 按摩师 的名字有点意思。
本题与下面的 198. 打家劫舍 一模一样。
解题思路
状态定义:dp0[i]
表示在不接受 num[i]
情况下的最大预约时间;dp1[i]
表示接受 num[i]
情况下的最大预约时间。
转移方程:
dp0[i] = max(dp0[i-1], dp1[i-1])
:在不接受第 i 个请求的情况下,第 i-1 个请求可以选择接受或者不接受。dp1[i] = nums[i] + dp0[i-1]
:在接受第 i 个请求的情况下,第 i-1 个请求必然不能接受。
代码实现
包括空间优化解法。
class Solution
{
public:
int massage(vector<int> &nums)
{
return spaceOptimize(nums);
}
int commonDP(vector<int> &nums)
{
int n = nums.size();
if (n == 0)
return 0;
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][0] = 0, dp[0][1] = nums[0];
int maxval = nums[0];
for (int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]);
dp[i][1] = dp[i - 1][0] + nums[i];
maxval = max(maxval, max(dp[i][0], dp[i][1]));
}
return maxval;
}
int spaceOptimize(vector<int> &nums)
{
int n = nums.size();
if (n == 0)
return 0;
int dp0 = 0, dp1 = nums[0];
int maxval = dp1;
int t;
for (int i = 1; i < n; i++)
{
t = dp1;
dp1 = dp0 + nums[i];
dp0 = max(dp0, t);
maxval = max(maxval, max(dp0, dp1));
}
return maxval;
}
};
判断子序列
题目[392]:点击 链接 查看题目。
解法1:动态规划
最长公共子序列 (LCS) 的变种题。
找出 s
和 t
的最长公共子序列长度 maxval
,判断 maxval == s.length
。
关于 LCS 的具体解法,看 这里 的第二小节。
class Solution
{
public:
bool isSubsequence(string s, string t)
{
int slen = s.length(), tlen = t.length();
vector<vector<int>> dp(slen + 1, vector<int>(tlen + 1, 0));
int maxval = 0;
for (int i = 1; i <= slen; i++)
{
for (int j = 1; j <= tlen; j++)
{
if (s[i - 1] == t[j - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
else
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
maxval = max(maxval, dp[i][j]);
}
}
return maxval == slen;
}
};
解法2:循环比较
bool loopSolution(string &s, string &t)
{
int i = 0, j = 0;
int slen = s.length(), tlen = t.length();
while (i < slen && j < tlen)
{
if (s[i] == t[j]) i++;
j++;
}
return i == slen;
}
爬楼梯
题目[72]:经典题目 。
解法1:递归
超时。
int recursion(int n)
{
if (n == 1 || n == 2) return n;
return recursion(n-1) + recursion(n-2);
}
解法2:动态规划
类似于斐波那契数列。
int dp(int n)
{
if (n == 1 || n == 2) return n;
int f1 = 1, f2 = 2, f3 = 3;
for (int i = 3; i <= n; i++)
f3 = f1 + f2, f1 = f2, f2 = f3;
return f3;
}
使用最小花费爬楼梯
题目[746]:爬楼梯的加强版 。
解题思路
状态定义:dp[i]
是到达第 i 个阶梯的最小花费(但不包括第 i 个的花费 cost[i]
),因此需要预处理 cost.push_back(0)
。
转移方程:
dp[i] = cost[i] if i==0 or i==1
= min(dp[i-1], dp[i-2]) + cost[i] if i>=2
解析:第 i 个阶梯总是可以通过第 i-1 或 i-2 个直接抵达。
代码实现
int minCostClimbingStairs(vector<int> &cost)
{
cost.push_back(0);
int n = cost.size();
vector<int> dp(n, 0);
dp[0] = cost[0], dp[1] = cost[1];
for (int i = 2; i < n; i++)
dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];
return dp[n - 1];
}
空间优化:
int minCostClimbingStairs(vector<int> &cost)
{
cost.push_back(0);
int n = cost.size();
int f0 = cost[0], f1 = cost[1], f2;
for (int i = 2; i < n; i++)
f2 = cost[i] + min(f0, f1), f0 = f1, f1 = f2;
return f2;
}
打家劫舍
题目[198]:点击查看 题目 。
解法1:与上面的「按摩师」一模一样
包括普通形式与空间优化形式。
class Solution
{
public:
int rob(vector<int> &nums)
{
return spaceOptimize(nums);
}
int commonDP(vector<int> &nums)
{
int n = nums.size();
if (n == 0) return 0;
if (n == 1) return nums[0];
vector<int> dp0(n, 0), dp1(n, 0);
dp0[0] = 0, dp1[0] = nums[0];
int maxval = max(nums[0], nums[1]);
for (int i = 1; i < n; i++)
{
dp0[i] = max(dp0[i - 1], dp1[i - 1]);
dp1[i] = dp0[i - 1] + nums[i];
maxval = max(maxval, max(dp0[i], dp1[i]));
}
return maxval;
}
int spaceOptimize(vector<int> &v)
{
int n = v.size();
if (n == 0) return 0;
if (n == 1) return v[0];
int dp0 = 0, dp1 = v[0];
int maxval = v[0];
int t;
for (int i = 1; i < n; i++)
{
t = dp0;
dp0 = max(dp0, dp1);
dp1 = v[i] + t;
maxval = max(dp0, dp1);
}
return maxval;
}
};
解法2:官方题解
状态定义:dp[i]
表示在 [0, ..., i]
中偷窃的最大收益。
转移方程:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
。如果选择偷窃 nums[i]
那么就只能在 [0,...,i-2]
的条件下进行;如果选择不偷窃 nums[i]
那么可以在 [0,...,i-1]
的范围内选择。
int officialSolution(vector<int> &v)
{
int n = v.size();
if (n == 0) return 0;
if (n == 1) return v[0];
int f0 = v[0], f1 = max(v[0], v[1]), f2 = max(f0, f1);
for (int i = 2; i < n; i++)
{
f2 = max(f1, f0 + v[i]);
f0 = f1, f1 = f2;
}
return f2;
}
三步问题
题目[Interview-0801]:点击查看 题目 。
解题思路
与上面的「爬楼梯」一模一样,是斐波那契数列的变种。需要注意的是:数据溢出,需要使用 uint64_t
作为数据类型。
代码实现
#define MODNUM (1000000007)
class Solution
{
public:
int waysToStep(int n)
{
if (n <= 1) return 1;
if (n == 2) return 2;
uint64_t f0 = 1, f1 = 1, f2 = 2, f3 = 4;
for (int i = 3; i <= n; i++)
{
f3 = (f0 + f1 + f2) % MODNUM;
f0 = f1, f1 = f2, f2 = f3;
}
return f3;
}
};
[leetcode] 动态规划(Ⅰ)的更多相关文章
- 快速上手leetcode动态规划题
快速上手leetcode动态规划题 我现在是初学的状态,在此来记录我的刷题过程,便于以后复习巩固. 我leetcode从动态规划开始刷,语言用的java. 一.了解动态规划 我上网查了一下动态规划,了 ...
- [LeetCode] 动态规划入门题目
最近接触了动态规划这个厉害的方法,还在慢慢地试着去了解这种思想,因此就在LeetCode上面找了几道比较简单的题目练了练手. 首先,动态规划是什么呢?很多人认为把它称作一种"算法" ...
- LeetCode 动态规划
动态规划:适用于子问题不是独立的情况,也就是各子问题包含子子问题,若用分治算法,则会做很多不必要的工作,重复的求解子问题,动态规划对每个子子问题,只求解一次将其结果保存在一张表中,从而避免重复计算. ...
- LeetCode动态规划题总结【持续更新】
以下题号均为LeetCode题号,便于查看原题. 10. Regular Expression Matching 题意:实现字符串的正则匹配,包含'.' 和 '*'.'.' 匹配任意一个字符,&quo ...
- leetcode动态规划题目总结
Hello everyone, I am a Chinese noob programmer. I have practiced questions on leetcode.com for 2 yea ...
- House Robber III leetcode 动态规划
https://leetcode.com/submissions/detail/56095603/ 这是一道不错的DP题!自己想了好久没有清晰的思路,参看大神博客!http://siukwan.sin ...
- Leetcode 动态规划 - 简单
1. 最大子序和 (53) 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和. 示例: 输入: [-2,1,-3,4,-1,2,1,-5,4], 输 ...
- [Leetcode][动态规划] 第935题 骑士拨号器
一.题目描述 国际象棋中的骑士可以按下图所示进行移动: 我们将 “骑士” 放在电话拨号盘的任意数字键(如上图所示)上,接下来,骑士将会跳 N-1 步 ...
- [Leetcode][动态规划] 第931题 下降路径最小和
一.题目描述 给定一个方形整数数组 A,我们想要得到通过 A 的下降路径的最小和. 下降路径可以从第一行中的任何元素开始,并从每一行中选择一个元素.在下一行选择的元素和当前行所选元素最多相隔一列. 示 ...
- [Leetcode][动态规划] 零钱兑换
一.题目描述 给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬币组合能组成总金额,返回 -1. 示例 1: 输入: ...
随机推荐
- php时间输出结果减去一分钟
如: <?=date("m-d H:i",strtotime($rs["kgtime"]));?> 假设结果是09-03-23:30,如何让它变成0 ...
- Nodejs异步编程
异步函数:异步函数是异步编程语法的终极解决方案,它可以把异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得更清晰. const fn = async () =>{}; async f ...
- CF#633 D. Edge Weight Assignment
D. Edge Weight Assignment 题意 给出一个n个节点的树,现在要为边赋权值,使得任意两个叶子节点之间的路径权值异或和为0,问最多,最少有多少个不同的权值. 题解 最大值: 两个叶 ...
- 设计模式之GOF23原型模式01
原型模式prototype 原型模式: - 通过new产生一个对象需要非常繁琐的数据准备或者访问权限,则可以使用原型模式,比如如果new对象所需时间过长,可以通过克隆产生相同的副本 - Java中的克 ...
- [hdu5313]二分图性质,dp
题意:给定二分图,求添加的最多边数,使得添加之后还是二分图 思路:如果原图可以分成X,Y两个点集,那么边数最多为|X||Y|条.由于|X|+|Y|==n,所以需要使|X|与|Y|尽量接近.先对原图进行 ...
- 我的linux学习日记day5
一.vim 编辑器 有三种模式,命令模式,输入模式,末行模式 1.下面是命令模式常用的命令 2.末行模式常用命令 :w 保存 :q 退出 :q! 强制退出 :wq! 强制保存退出 :set nu 显示 ...
- 【WEB自动化】【第一节】【Xpath和CSS元素定位】
目前自动化测试开始投入WEB测试,使用RF及其selenium库,模拟对WEB页面进行操作,此过程中首先面对的问题就是对WEB页面元素的定位,几乎所有的关键字都需要传入特定的WEB页面元素,因此掌握常 ...
- spark机器学习从0到1基本数据类型之(二)
MLlib支持存储在单个机器上的局部向量和矩阵,以及由一个或多个RDD支持的分布式矩阵. 局部向量和局部矩阵是用作公共接口的简单数据模型. 底层线性代数操作由Breeze提供. 在监督学习中使 ...
- 对CSS3中的transform:Matrix()矩阵的一些理解
只要有CSS基础的人肯定都知道,我们可以通过transform中的translate,scale,rotate,skew这些方法来控制元素的平移,缩放,旋转,斜切,其实这些方法呢都是为了便于开发者使用 ...
- Django之ORM配置与单表操作
ORM数据库操作流程: 1. 配置数据库(项目同名包中settings.py和__init__.py) 2. 定义类(app包中models.py),执行建表命令(Tools---> ...