常见动态规划题目详解
1.爬楼梯
题目描述:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
实现代码:
class Solution {
public:
int climbStairs(int n) {
vector<int> a(n);
a[0] = 1;
a[1] = 2;
if(n == 1){
return 1;
}
if(n == 2){
return 2;
}
for(int i = 2; i < n;i++){
a[i] = a[i - 1] + a[i - 2];
}
return a[n - 1];
}
};
2.变态跳台阶
题目描述:
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
实现代码:
class Solution {
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
实现代码:
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
vector<vector<int>>dp(m,vector<int>(n));
dp[0][0] = grid[0][0];
for(int i = 1;i < m;i++){
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for(int j = 1;j < n;j++){
dp[0][j] = grid[0][j] + dp[0][j - 1];
}
for(int i = 1;i < m;i++){
for(int j = 1;j < n;j++){
int tmp = dp[i - 1][j] < dp[i][j - 1] ? dp[i - 1][j]:dp[i][j - 1];
dp[i][j] = tmp + grid[i][j];
}
}
return dp[m - 1][n - 1];
}
};
6.机器人到达指定位置的方法数
题目描述:
N个位置,1~N,N大于等于2,开始时机器人在其中的M位置,它可以向左或者右走。如果到了位置1,下一步只能是位置2;如果到了位置N,下一步只能是位置 N-1 。机器人走 K 步,最终来到位置 P 的总方法一共有多少种?
实现代码:
(1).暴力递归
class Solution {
public:
int ways(int N,int M,int K,int P){
if(N < 2 || K < 1|| M < 1|| M > N||P < 1||P > N){
return 0;
}
return walk(N,M,K,N);
}
int walk(int N,int cur,int rest,int P){
if(rest == 0){
return cur == p ? 1 : 0;
}
if(cur == 1){
return walk(N,2,rest - 1,P);
}
if(cur == N){
return walk(N,N - 1,rest - 1,P);
}
return walk(N,cur + 1,rest - 1,P) + walk(N,cur - 1,rest - 1,P);
}
};
(2)动态规划:
class Solution {
public:
int way(int N,int M,int K,int P){
if(N < 2|| K < 1||M < 1||M > N||P < 1|| P > N){
return 0;
}
vector<vector<int>>dp(K + 1,(vector<int>(N + 1)));
dp[0][p] = 1;
for(int i = 1;i <= K;i++){
for(int int j = 1;j < N;j++){
if(j == 1){
dp[i][j] = dp[i - 1][2];
}
else if(j == N){
dp[i][j] = dp[i - 1][N - 1];
}
else{
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
}
}
}
return dp[K][M];
}
};
7.不同路径
题目描述:
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
说明:m 和 n 的值均不超过 100。
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
示例 2:
输入: m = 7, n = 3
输出: 28
代码实现:
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>>dp(m,vector<int>(n));
for(int i = 0;i < m;i++){
dp[i][0] = 1;
}
for(int i = 0;i < n;i++){
dp[0][i] = 1;
}
for(int i = 1;i < m;i++){
for(int j = 1;j < n;j++){
dp[i][j] = dp[i-1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
};
8.不同路径Ⅱ
题目描述:
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1 和 0 来表示。
说明:m 和 n 的值均不超过 100。
示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
实现代码:
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size(); //行数
int n = obstacleGrid[0].size();//列数
long p[m][n];
//第一列赋值
int k = 0;
while(k < m && obstacleGrid[k][0] != 1)
p[k++][0] = 1;
//如果遇到了障碍物则它及其后面的值都为0
while(k < m)
p[k++][0] = 0;
//第一行赋值
k = 0;
while(k < n && obstacleGrid[0][k] != 1)
p[0][k++] = 1;
while(k < n)
p[0][k++] = 0;
for(int i = 1; i < m; i++)
for(int j = 1; j < n; j++){
if(obstacleGrid[i][j] == 1) //如果遇到障碍物,则该位置的值为0
p[i][j] = 0;
else
p[i][j] = p[i - 1][j] + p[i][j - 1];
}
return p[m-1][n-1];
}
};
9.买卖股票的最佳时机
题目描述:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 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。
实现代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size() < 2){
return 0;
}
int max_value = 0;
int mini = prices[0];
for(int i = 1;i < prices.size();i++){
max_value = max(prices[i] - mini,max_value);
mini = min(prices[i],mini);
}
return max_value;
}
};
10.打家劫舍
题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
实现代码:
(1).递归+记忆化搜索
class Solution {
public:
vector<int>tmpt;
int tryrob(vector<int>& nums,int index){
if(index >= nums.size()){
return 0;
}
if(tmpt[index] != -1){
return tmpt[index];
}
int tmp = -1;
for(int i = index;i < nums.size();i++){
tmp = max(tmp,nums[i] + tryrob(nums,i + 2));
}
tmpt[index] = tmp;
return tmp;
}
int rob(vector<int>& nums) {
tmpt = vector<int>(nums.size(),-1);
return tryrob(nums,0);
}
};
动态规划:
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() == 0){
return 0;
}
int n = nums.size() - 1;
vector<int>memo(n + 1,-1);
memo[n] = nums[n];
for(int i = n - 1;i >= 0;i--){
for(int j = i;j <= n;j++){
memo[i] = max(memo[i],nums[j] + (j + 2 <= n ? memo[j + 2]:0));
}
}
return memo[0];
}
};
11.整数拆分
题目描述:
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
说明: 你可以假设 n 不小于 2 且不大于 58。
实现代码:
递归+记忆化搜索
class Solution {
private:
vector<int>tmpt;
int res(int n){
if(n == 1){
return 1;
}
if(tmpt[n] != -1){
return tmpt[n];
}
int tmp = -1;
for(int i = 1;i < n;i++){
tmp = max(max(tmp,i * (n - i)),i * res(n - i));
}
tmpt[n] = tmp;
return tmp;
}
public:
int integerBreak(int n) {
tmpt = vector<int>(n + 1,-1);
return res(n);
}
};
动态规划:
class Solution {
public:
int integerBreak(int n) {
vector<int>tmp(n+1,-1);
tmp[1] = 1;
for(int i = 2;i <= n;i++){
for(int j = 1;j < i;j++){
tmp[i] = max(max(tmp[i],j * (i - j)),j * tmp[i - j]);
}
}
return tmp[n];
}
};
12.三角形最小路径和
题目描述:
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
说明:
如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。
实现代码:
(1).递归+记忆化搜索
class Solution {
public:
vector<vector<int>> tmp;
int min_value(vector<vector<int>>& triangle,int i,int j){
if(i == triangle.size() - 1){
return triangle[i][j];
}
if(tmp[i][j] == -1)
tmp[i][j] = triangle[i][j] + min(min_value(triangle,i + 1,j),min_value(triangle,i + 1,j + 1));
return tmp[i][j];
}
int minimumTotal(vector<vector<int>>& triangle) {
int res = 0;
tmp = vector<vector<int>> (1000,vector<int>(1000,-1));
res = min_value(triangle,0,0);
return res;
}
};
(2).动态规划
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
if(triangle.size() == 0){
return 0;
}
vector<int>res;
int tmp = 0;
for(int i = triangle.size() - 2;i >= 0;i--){
for(int j = 0;j < triangle[i].size();j++){
triangle[i][j] = min(triangle[i + 1][j],triangle[i + 1][j + 1]) + triangle[i][j];
}
}
return triangle[0][0];
}
};
13.最长回文子串
题目描述:
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
实现代码:
(1).暴力
class Solution {
public:
string longestPalindrome(string s)
{
if(s.empty()) return "";
if(s.size()==1) return s;
int start=0,maxlength=1;//记录最大回文子串的起始位置以及长度
for(int i=0;i<s.size();i++)
for(int j=i+1;j<s.size();j++)//从当前位置的下一个开始算
{
int temp1,temp2;
for(temp1=i,temp2=j;temp1<temp2;temp1++,temp2--)
{
if(s[temp1]!=s[temp2])
break;
}
if(temp1>=temp2 && j-i+1>maxlength)//这里要注意条件为temp1>=temp2,因为如果是偶数个字符,相邻的两个经上一步会出现大于的情况
{
maxlength = j-i+1;
start=i;
}
}
return s.substr(start,maxlength);//利用string中的substr函数来返回相应的子串,第一个参数是起始位置,第二个参数是字符个数*/
}
};
(2).动态规划
class Solution {
public:
string longestPalindrome(string s)
{
if (s.empty()) return "";
int len = s.size();
if (len == 1)return s;
int longest = 1;
int start=0;
vector<vector<int> > dp(len,vector<int>(len));
for (int i = 0; i < len; i++)
{
dp[i][i] = 1;
if(i<len-1)
{
if (s[i] == s[i + 1])
{
dp[i][i + 1] = 1;
start=i;
longest=2;
}
}
}
for (int l = 3; l <= len; l++)//子串长度
{
for (int i = 0; i+l-1 < len; i++)//枚举子串的起始点
{
int j=l+i-1;//终点
if (s[i] == s[j] && dp[i+1][j-1]==1)
{
dp[i][j] = 1;
start=i;
longest = l;
}
}
}
return s.substr(start,longest);
}
};
14.最大字序和
题目描述:
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
实现代码:
非动态规划
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int sum = 0;
int max_sum = nums[0];
for(int i = 0;i < nums.size();i++){
if(sum <= 0){
sum = nums[i];
}
else
sum = sum + nums[i];
if(max_sum < sum){
max_sum = sum;
}
}
return max_sum;
}
};
15.最长上升子序列
题目描述:
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
实现代码:
递归:
class Solution {
private:
int dp[100000];
int LIS(vector<int> &nums,int n){
if(n == 0){
dp[n] = 1;
return dp[n];
}
for(int i = n;i>= 0;i--){
for(int j = i - 1;j >= 0;j--){
if(nums[i] <= nums[j]){
dp[n] = max(LIS(nums,n - 1),;
}
if(nums[n] > nums[n - 1]){
dp[n] = LIS(nums,n-1);
}
}
}
return dp[n];
}
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 0){
return NULL;
}
int n = nums.size() - 1;
return LIS(nums,n);
}
};
动态规划:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 0){
return NULL;
}
int *dp = new int[nums.size()];
int len = nums.size();
int res = 0;
for(int i = 0;i < len;i++){
dp[i] = 1;
for(int j = 0;j < i;j++){
if(nums[i] > nums[j]){
dp[i] = max(dp[j] + 1,dp[i]);
}
}
res = max(dp[i],res);
}
return res;
}
};
16.最长公共子序列
题目描述:
给定两个字符串str1,str2返回两个字符串的最长公共子序列
实现代码:
class Solution {
public:
int maxlength(string str1,string str2){
int m = str1.length();
int n = str2.length();
vector<vector<int>>dp(m,vector<int>(n));
dp[0][0] = str1[0] == str2[0]?1:0;
for(int i = 0;i < m;i++){
dp[i][0] = max(dp[i - 1][0],str1[i] == str2[0]?1:0);
}
for(int j = 0;j < n;j++){
dp[0][j] = max(dp[0][j - 1],str1[0] == str[j] ? 1: 0);
}
for(int i = 1; i < m;i++){
for(int j = 1;j < n;j++){
dp[i][j] = max(dp[i - 1][j],dp[i][j - 1]);
if(str1[i] == str2[j]){
dp[i][j] = max(dp[i][j],1 + dp[i - 1][j - 1]);
}
}
}
return dp[m - 1][n - 1];
}
};
17.最长公共字串
题目描述:
给定两个字符串str1,str2,返回两个字符串的最长公共字串
实现代码:
class Solution {
public:
int maxlength(string str1,string str2){
int m = str1.length();
int n = str2.length();
vector<vector<int>>dp(m,vector<int>(n));
for(int i = 0;i < m;i++){
if(str1[i] == str2[0]){
dp[i][0] = 1;
}
}
for(int j = 0;j < n;j++){
if(str2[j] == str1[0]){
dp[0][j] = 1;
}
}
for(int i = 1;i < m;i++){
for(int j = 1;j < n;j++){
if(str1[i] == str2[j]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}
}
}
return dp[m - 1][n - 1];
}
};
18.连续子数组和
题目描述:
给定一个包含非负数的数组和一个目标整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。
示例 1:
输入: [23,2,4,6,7], k = 6
输出: True
解释: [2,4] 是一个大小为 2 的子数组,并且和为 6。
示例 2:
输入: [23,2,6,4,7], k = 6
输出: True
解释: [23,2,6,4,7]是大小为 5 的子数组,并且和为 42。
说明:
数组的长度不会超过10,000。
你可以认为所有数字总和在 32 位有符号整数范围内。
实现代码:
class Solution {
public:
bool checkSubarraySum(vector<int>& nums, int k) {
int m = nums.size();
if(m < 2){
return false;
}
int sum[m];
sum[0] = nums[0];
for(int i = 1;i < m;i++){
sum[i] = sum[i - 1] + nums[i];
}
for(int i = 0;i < m;i++){
for(int j = i + 1;j < m;j++){
int res = sum[j] - sum[i] + nums[i];
if(res == k || (k != 0 && res % k == 0)){
return true;
}
}
}
return false;
}
};