这次按通过率从高到低刷题。

本文完成的题目:{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) 的变种题。

找出 st 的最长公共子序列长度 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] 动态规划(Ⅰ)的更多相关文章

  1. 快速上手leetcode动态规划题

    快速上手leetcode动态规划题 我现在是初学的状态,在此来记录我的刷题过程,便于以后复习巩固. 我leetcode从动态规划开始刷,语言用的java. 一.了解动态规划 我上网查了一下动态规划,了 ...

  2. [LeetCode] 动态规划入门题目

    最近接触了动态规划这个厉害的方法,还在慢慢地试着去了解这种思想,因此就在LeetCode上面找了几道比较简单的题目练了练手. 首先,动态规划是什么呢?很多人认为把它称作一种"算法" ...

  3. LeetCode 动态规划

    动态规划:适用于子问题不是独立的情况,也就是各子问题包含子子问题,若用分治算法,则会做很多不必要的工作,重复的求解子问题,动态规划对每个子子问题,只求解一次将其结果保存在一张表中,从而避免重复计算. ...

  4. LeetCode动态规划题总结【持续更新】

    以下题号均为LeetCode题号,便于查看原题. 10. Regular Expression Matching 题意:实现字符串的正则匹配,包含'.' 和 '*'.'.' 匹配任意一个字符,&quo ...

  5. leetcode动态规划题目总结

    Hello everyone, I am a Chinese noob programmer. I have practiced questions on leetcode.com for 2 yea ...

  6. House Robber III leetcode 动态规划

    https://leetcode.com/submissions/detail/56095603/ 这是一道不错的DP题!自己想了好久没有清晰的思路,参看大神博客!http://siukwan.sin ...

  7. Leetcode 动态规划 - 简单

    1. 最大子序和 (53) 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和. 示例: 输入: [-2,1,-3,4,-1,2,1,-5,4], 输 ...

  8. [Leetcode][动态规划] 第935题 骑士拨号器

    一.题目描述 国际象棋中的骑士可以按下图所示进行移动:                           我们将 “骑士” 放在电话拨号盘的任意数字键(如上图所示)上,接下来,骑士将会跳 N-1 步 ...

  9. [Leetcode][动态规划] 第931题 下降路径最小和

    一.题目描述 给定一个方形整数数组 A,我们想要得到通过 A 的下降路径的最小和. 下降路径可以从第一行中的任何元素开始,并从每一行中选择一个元素.在下一行选择的元素和当前行所选元素最多相隔一列. 示 ...

  10. [Leetcode][动态规划] 零钱兑换

    一.题目描述 给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬币组合能组成总金额,返回 -1. 示例 1: 输入: ...

随机推荐

  1. ztree根据参数动态控制是否显示复选框/单选框(静态JSON数据)

    本文不再更新,可能存在内容过时的情况,实时更新请访问原地址:ztree根据参数动态控制是否显示复选框/单选框(静态JSON数据): 现有全省各地区静态JSON数据,现在想通过Url参数,动态控制是否显 ...

  2. 【Hadoop离线基础总结】linux基础增强

    linux基础增强 查找命令 grep命令  (print lines matching a pattern) 概述: grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打 ...

  3. 使用 Visual Studio Code 搭建 C/C++ 开发和调试环境

    文章目录 1. 安装 C/C++ 插件 2. 安装 MinGW-w64 并配置好环境变量 3. 测试环境变量是否配置正确 4. 创建和设置 C 语言开发工作区 5. 编写你的第一个 C 语言程序 6. ...

  4. 【Docker】在本地打包maven程序为docker镜像报错: Connect to localhost:2375 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1]

    错误信息: [ERROR] Failed to execute goal com.spotify:docker-maven-plugin:1.0.0:build (default-cli) on pr ...

  5. DevOps vs. Agile:它们有什么共同点?

    DevOps与Agile有很多不同,但它们之间仍可发现很多共同点,这篇文章为读者揭晓. DevOps和Agile之间有着明显的关系.Agile是方法论,Scrum是框架,并DevOps随着看板也落在了 ...

  6. 【转】46个Linux常用命令

    转:https://www.cnblogs.com/passzhang/p/8552757.html 问题一: 绝对路径用什么符号表示?当前目录.上层目录用什么表示?主目录用什么表示? 切换目录用什么 ...

  7. 基于Nettty打造自己的MVC服务器

    最近开始折腾Netty,体验下NIO编程.既然学习了,就要做点东西出来,要不然不容易掌握学到的东西.在Netty的官方demo上都有各种case的sample,打造Http服务器的核心代码就是从Sam ...

  8. The Apache Tomcat Connector

    http://tomcat.apache.org/connectors-doc/generic_howto/quick.html 搭建最简单的tomcat connector 用到了apapche 的 ...

  9. Raft翻译

    英文原文:https://web.stanford.edu/~ouster/cgi-bin/papers/raft-atc14 In Search of an Understandable Conse ...

  10. sound of the genuine

    “There is something in every one of you that waits and listens for the sound of the genuine in yours ...