算法基础知识——动态规划(二)
目录:
- 应用实例
- 矩阵的最小路径和【程序员代码面试指南】
- 字符串的排列【剑指offer】
- 合唱团【2017校招真题在线编程】
- 连续最大和【2017校招真题在线编程】
- 藏宝图【2017校招真题在线编程】
- 买苹果【网易】
- 上台阶【京东】
- word-break【leetcode】
- candy【leetcode】
- 罪犯转移【2016校招真题在线编程】
- 跳石板【网易】
- 年终奖【京东】
- 暗黑的字符串【2017校招真题在线编程】
- 直方图内最大矩形【2016校招真题在线编程】
- palindrome-partitioning-ii(回文分割Ⅱ)【leetcode】
一、应用实例
1、题目描述:给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和。【程序员代码面试指南】
- 输入格式:第一行输入两个整数 n 和 m,表示矩阵的大小。接下来 n 行每行 m 个整数表示矩阵。其中1 ≤ n,m ≤ 2000
1 ≤ ai,j ≤ 100 - 输出格式:输出一个整数表示答案。
- 样例输入:
- 4 4
- 1 3 5 9
- 8 1 3 4
- 5 0 6 1
- 8 8 4 0
- 样例输出:
- 12
示例代码:
#include <iostream>
#include <cstring>
using namespace std;
const int MAX_N = 2001;
int a[MAX_N][MAX_N];
int dp[MAX_N][MAX_N];
int main(){
int n, m;
memset(a, 0, sizeof(a));
memset(dp, 0, sizeof(dp));
while(cin >> n >> m){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> a[i][j];
}
}
dp[1][1] = a[1][1];
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(i == 1){
dp[i][j] = dp[i][j - 1] + a[i][j];
continue;
}else if(j == 1){
dp[i][j] = dp[i - 1][j] + a[i][j];
continue;
}
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + a[i][j];
}
}
cout << dp[n][m] << endl;
}
return 0;
}
2、题目描述:输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
vector<string> Permutation(string str) {
write code here
}【剑指offer】
- 输入格式:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
- 输出格式:按字典序打印出该字符串中字符的所有排列。
- 样例输入:
- abc
- 样例输出:
示例代码:
#include <iostream>
#include <string>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;
set<string> result;
void GetStr(string s, int index){
result.insert(s);
for(int i = index; i < s.size(); i++){
swap(s[i], s[index]);
GetStr(s, index + 1);
swap(s[i], s[index]);
}
}
vector<string> Permutation(string str) {
vector<string> list;
if(str.size() == 0){
return list;
}
GetStr(str, 0);
for(set<string>::iterator iter = result.begin(); iter != result.end(); iter++){
list.push_back(*iter);
}
sort(list.begin(), list.end());
return list;
}
int main(){
string s;
while(cin >> s){
result.clear();
vector<string> list = Permutation(s);
for(vector<string>::iterator iter = list.begin(); iter != list.end(); iter++){
cout << *iter << endl;
}
}
return 0;
}
3、题目描述:有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?【2017校招真题在线编程】
- 输入格式:每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1 <= n <= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
- 输出格式:输出一行表示最大的乘积。
- 样例输入:
- 3
- 7 4 7
- 2 50
- 样例输出:
- 49
示例代码:
#include <iostream>
#include <cstring>
using namespace std;
const int MAX_N = 51;
const long long MIN_INT = -0x7fffffffffffffff;
int ability[MAX_N];
long long dp1[MAX_N][MAX_N]; //dp1[i][j]为选取了i个数后以j结尾时的最大值
long long dp2[MAX_N][MAX_N]; //dp2[i][j]为选取了i个数后以j结尾时的最小值
int main(){
int n;
while(cin >> n){
memset(dp1, 0, sizeof(dp1));
memset(dp2, 0, sizeof(dp2));
memset(ability, 0, sizeof(ability));
for(int i = 1; i <= n; i++){
cin >> ability[i];
}
int k, d;//k为选取k个学生,d为相邻两个学生编号不超过d
cin >> k >> d;
for(int i = 1; i <= n; i++){
dp1[1][i] = ability[i];
dp2[1][i] = ability[i];
for(int j = 2; j <= k; j++){
for(int m = i - 1; m >= 1 && i - m <= d; m--){
dp1[j][i] = max(dp1[j][i],
max(dp1[j - 1][m] * ability[i], dp2[j - 1][m] * ability[i]));
dp2[j][i] = min(dp2[j][i],
min(dp1[j - 1][m] * ability[i], dp2[j - 1][m] * ability[i]));
}
}
}
long long ans = MIN_INT;
for(int i = 1; i <= n; i++){
ans = max(ans, dp1[k][i]);
}
cout << ans << endl;
}
return 0;
}
4、题目描述:一个数组有 N 个元素,求连续子数组的最大和。 例如:[-1,2,1],和最大的连续子数组为[2,1],其和为 3【2017校招真题在线编程】
- 输入格式:输入为两行。 第一行一个整数n(1 <= n <= 100000),表示一共有n个元素 第二行为n个数,即每个元素,每个整数都在32位int范围内。以空格分隔。
- 输出格式:所有连续子数组中和最大的值。
- 样例输入:
- 3
- -1 2 1
- 样例输出:
- 3
示例代码:
#include <iostream>
using namespace std;
const int MAX_N = 100001;
const int MIN_INT = -0x7fffffff;
long long dp[MAX_N];
long long a[MAX_N];
int main(){
int n;
while(cin >> n){
for(int i = 1; i <= n; i++){
cin >> a[i];
}
dp[1] = a[1];
for(int i = 2; i <= n; i++){
dp[i] = max(dp[i - 1] + a[i], a[i]);
}
long long result = MIN_INT;
for(int i = 1; i <= n; i++){
if(dp[i] > result){
result = dp[i];
}
}
cout << result << endl;
}
return 0;
}
5、题目描述:牛牛拿到了一个藏宝图,顺着藏宝图的指示,牛牛发现了一个藏宝盒,藏宝盒上有一个机关,机关每次会显示两个字符串 s 和 t,根据古老的传说,牛牛需要每次都回答 t 是否是 s 的子序列。注意,子序列不要求在原字符串中是连续的,例如串 abc,它的子序列就有 {空串, a, b, c, ab, ac, bc, abc} 8 种。【2017校招真题在线编程】
- 输入格式:每个输入包含一个测试用例。每个测试用例包含两行长度不超过 10 的不包含空格的可见 ASCII 字符串。
- 输出格式:输出一行 “Yes” 或者 “No” 表示结果。
- 样例输入:
- x.nowcoder.com
- ooo
- 样例输出:
- Yes
示例代码:
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1, s2;
while(cin >> s1 >> s2){
int index = 0;
for(int i = 0; i < s1.size(); i++){
if(s2[index] == s1[i]){
index++;
}
}
if(index == s2.size()){
cout << "Yes" << endl;
}else{
cout << "No" << endl;
}
}
return 0;
}
6、题目描述:小易去附近的商店买苹果,奸诈的商贩使用了捆绑交易,只提供6个每袋和8个每袋的包装(包装不可拆分)。 可是小易现在只想购买恰好n个苹果,小易想购买尽量少的袋数方便携带。如果不能购买恰好n个苹果,小易将不会购买。【网易】
- 输入格式:输入一个整数n,表示小易想购买n(1 ≤ n ≤ 100)个苹果
- 输出格式:输出一个整数表示最少需要购买的袋数,如果不能买恰好n个苹果则输出-1
- 样例输入:
- 20
- 样例输出:
- 3
示例代码:
#include <iostream>
#include <cstring>
using namespace std;
const int MAX_N = 101;
const int MAX_INT = 0x7fffffff;
int dp[MAX_N];//dp[i]表示购买i个苹果需要购物袋个数
int main(){
int n;
while(cin >> n){
fill(dp, dp + n + 1, MAX_INT);
dp[6] = 1;
dp[8] = 1;
for(int i = 7; i <= n; i++){
if(i - 8 > 0 && dp[i - 8] != MAX_INT){
dp[i] = min(dp[i - 8] + 1, dp[i]);
}else if(i - 6 > 0 && dp[i - 6] != MAX_INT){
dp[i] = min(dp[i - 6] + 1, dp[i]);
}
}
if(dp[n] == MAX_INT){
cout << -1 << endl;
}else{
cout << dp[n] << endl;
}
}
return 0;
}
7、题目描述:有一楼梯共m级,刚开始时你在第一级,若每次只能跨上一级或者二级,要走上m级,共有多少走法?注:规定从一级到一级有0种走法。给定一个正整数int n,请返回一个数,代表上楼的方式数。保证n小于等于100。为了防止溢出,请返回结果Mod 1000000007的值。
int countWays(int n) {
// write code here
}【京东】
- 输入格式:一个正整数int n
- 输出格式:返回上楼的方式数n
- 样例输入:
- 3
- 样例输出:
- 2
示例代码:
#include <iostream>
using namespace std;
const int MAX_N = 101;
const int MOD = 1000000007;
int dp[MAX_N];//dp[i]表示前i跳的跳法
void Init(){
dp[1] = 0;
dp[2] = 1;
dp[3] = 2;
dp[4] = 3;
for(int i = 5; i < MAX_N; i++){
//当前跳的方法数等于上一跳方法数加上一跳跳两级和跳一级的方法数之和
dp[i] = (dp[i - 2] + dp[i - 1]) % MOD;
}
}
int countWays(int n){
return dp[n];
}
int main(){
int n;
Init();
while(cin >> n){
cout << countWays(n) << endl;
}
return 0;
}
8、题目描述:Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.For example, given s ="leetcode", dict =["leet", "code"].Return true because"leetcode"can be segmented as"leet code".
bool wordBreak(string s, unordered_set<string> &dict) {
//write code here
}【leetcode】
- 输入格式:a string s and a dictionary of words dict
- 输出格式:return true or false
- 样例输入:
- s=“leetcode”;dict=["leet", "code"]
- 样例输出:
- true
示例代码:
#include <iostream>
#include <unordered_set>
#include <string>
#include <vector>
using namespace std;
bool wordBreak(string s, unordered_set<string> &dict) {
int len = s.size();
vector<bool> dp(len + 1, false);//可以从第i个位置开始截取
dp[0] = true;
for(int i = 0; i < len; i++){
for(int j = i; j < len && dp[i]; j++){
if(dict.find(s.substr(i, j - i + 1)) != dict.end()){
dp[j + 1] = true;
if(dp[len]){
return true;
}
}
}
}
return dp[len];
}
int main(){
string s = "cars";
unordered_set<string> dict;
dict.insert("car");
dict.insert("ca");
dict.insert("rs");
if(wordBreak(s, dict)){
cout << "true" << endl;
}else{
cout << "false" << endl;
}
return 0;
}
9、题目描述:There are N children standing in a line. Each child is assigned a rating value.You are giving candies to these children subjected to the following requirements:Each child must have at least one candy.Children with a higher rating get more candies than their neighbors.What is the minimum candies you must give?
int candy(vector<int> &ratings) {
//write code here
}【leetcode】
- 输入格式:a rating value list
- 输出格式:the minimum candies
- 样例输入:
- 无
- 样例输出:
- 无
示例代码:
#include <iostream>
#include <vector>
using namespace std;
int candy(vector<int> &ratings) {
int len = ratings.size();
vector<int> dp(len, 1);
for(int i = 1; i < len; i++){
if(ratings[i] > ratings[i - 1]){
dp[i] = dp[i - 1] + 1;
}
}
for(int i = len - 1; i > 0; i--){
if(ratings[i - 1] > ratings[i] && dp[i - 1] <= dp[i]){
dp[i - 1] = dp[i] + 1;
}
}
int result = 0;
for(int i = 0; i < len; i++){
result += dp[i];
}
return result;
}
int main(){
vector<int> ratings;
ratings.push_back(3);
ratings.push_back(5);
ratings.push_back(7);
ratings.push_back(6);
ratings.push_back(4);
ratings.push_back(2);
ratings.push_back(8);
ratings.push_back(6);
cout << candy(ratings) << endl;
}
附注:
(1)测试样例:
3 5 7 6 4 2 8 6
初始:1 1 1 1 1 1 1 1
从左到右:1 2 3 1 1 1 2 1
从右到左:1 2 4 3 2 1 2 1 = 16
10、题目描述:C市现在要转移一批罪犯到D市,C市有n名罪犯,按照入狱时间有顺序,另外每个罪犯有一个罪行值,值越大罪越重。现在为了方便管理,市长决定转移入狱时间连续的c名犯人,同时要求转移犯人的罪行值之和不超过t,问有多少种选择的方式(一组测试用例可能包含多组数据,请注意处理)?【2016校招真题在线编程】
- 输入格式:第一行数据三个整数:n,t,c(1≤n≤2e5,0≤t≤1e9,1≤c≤n),第二行按入狱时间给出每个犯人的罪行值ai(0≤ai≤1e9)
- 输出格式:一行输出答案。
- 样例输入:
- 3 100 2
- 1 2 3
- 样例输出:
- 2
示例代码:
#include <iostream>
#include <vector>
using namespace std;
int main(){
int n, t, c;
while(cin >> n >> t >> c){
vector<int> criminalList(n);
int sum = 0;
for(int i = 0; i < n; i++){
cin >> criminalList[i];
}
int result = 0;
for(int i = 0; i < c; i++){
sum += criminalList[i];
}
result += sum <= t ? 1 : 0;
for(int i = c; i < n; i++){
sum = sum - criminalList[i - c] + criminalList[i];
result += sum <= t ? 1 : 0;
}
cout << result << endl;
}
return 0;
}
11、题目描述:小易来到了一条石板路前,每块石板上从1挨着编号为:1、2、3.......
这条石板路要根据特殊的规则才能前进:对于小易当前所在的编号为K的 石板,小易单次只能往前跳K的一个约数(不含1和K)步,即跳到K+X(X为K的一个非1和本身的约数)的位置。 小易当前处在编号为N的石板,他想跳到编号恰好为M的石板去,小易想知道最少需要跳跃几次可以到达。
例如:N = 4,M = 24:4->6->8->12->18->24,于是小易最少需要跳跃5次,就可以从4号石板跳到24号石板【网易】
- 输入格式:输入为一行,有两个整数N,M,以空格隔开。 (4 ≤ N ≤ 100000) (N ≤ M ≤ 100000)
- 输出格式:输出小易最少需要跳跃的步数,如果不能到达输出-1
- 样例输入:
- 4 24
- 样例输出:
- 5
示例代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MAX_N = 100001;
const int MAX_INT = 0x7fffffff;
int dp[MAX_N];//跳到编号为i的石板的最少步骤
vector<int> GetDivisor(int n){
vector<int> result;
for(int i = 2; i <= sqrt(n); i++){
if(n % i == 0){
result.push_back(i);
result.push_back(n / i);
}
}
sort(result.begin(), result.end());
return result;
}
int main(){
int n, m;
while(cin >> n >> m){
fill(dp, dp + m + 1, MAX_INT);
dp[n] = 0;
for(int i = n; i <= m; i++){
if(dp[i] == MAX_INT){
continue;
}else{
vector<int> a = GetDivisor(i);
for(int j = 0; j < a.size(); j++){
if(i + a[j] > m){
break;
}
dp[i + a[j]] = min(dp[i + a[j]], dp[i] + 1);
}
}
}
if(dp[m] == MAX_INT){
cout << -1 << endl;
}else{
cout << dp[m] << endl;
}
}
return 0;
}
12、题目描述:小东所在公司要发年终奖,而小东恰好获得了最高福利,他要在公司年会上参与一个抽奖游戏,游戏在一个6*6的棋盘上进行,上面放着36个价值不等的礼物,每个小的棋盘上面放置着一个礼物,他需要从左上角开始游戏,每次只能向下或者向右移动一步,到达右下角停止,一路上的格子里的礼物小东都能拿到,请设计一个算法使小东拿到价值最高的礼物。给定一个6*6的矩阵board,其中每个元素为对应格子的礼物价值,左上角为[0,0],请返回能获得的最大价值,保证每个礼物价值大于100小于1000。
int getMost(vector<vector<int> > board) {
// write code here
}【京东】
- 输入格式:一个6 * 6棋盘
- 输出格式:获得的最大价值
- 样例输入:无
- 样例输出:无
示例代码:
int getMost(vector<vector<int> > board) {
int dp[7][7];
memset(dp, 0, sizeof(dp));
dp[1][1] = board[0][0];
for(int i = 0; i < board.size(); i++){
for(int j = 0; j < board[i].size(); j++){
if(i == 0){
dp[i + 1][j + 1] = dp[i + 1][j] + board[i][j];
}else if(j == 0){
dp[i + 1][j + 1] = dp[i][j + 1] + board[i][j];
}else{
dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]) + board[i][j];
}
}
}
return dp[6][6];
}
13、题目描述:一个只包含'A'、'B'和'C'的字符串,如果存在某一段长度为3的连续子串中恰好'A'、'B'和'C'各有一个,那么这个字符串就是纯净的,否则这个字符串就是暗黑的。例如:BAACAACCBAAA 连续子串"CBA"中包含了'A','B','C'各一个,所以是纯净的字符串。AABBCCAABB 不存在一个长度为3的连续子串包含'A','B','C',所以是暗黑的字符串。你的任务就是计算出长度为n的字符串(只包含'A'、'B'和'C'),有多少个是暗黑的字符串。【2017校招真题在线编程】
- 输入格式:输入一个整数n,表示字符串长度(1 ≤ n ≤ 30)
- 输出格式:输出一个整数表示有多少个暗黑字符串
- 样例输入:
- 2 3
- 样例输出:
- 9 21
示例代码:
#include <iostream>
using namespace std;
const int MAX_N = 31;
long long dp[MAX_N];//dp[i]表示长度为i的暗黑串数量
int main(){
int n;
dp[1] = 3;
dp[2] = 9;
for(int i = 3; i < MAX_N; i++){
dp[i] = 2 * dp[i - 1] + dp[i - 2];
}
while(cin >> n){
cout << dp[n] << endl;
}
return 0;
}
附注:
(1)dp[i - 1] = s(i - 1) + d(i - 1),s表示末尾两个字母的状态相同,d表示末尾两个字母的状态不同
dp[i] = 3s(i - 1) + 2d(i - 1) = 2dp[i - 1] + s(i - 1)
s(i) = 1/3 * 3s(i - 1) + 1/2 * 2d(i - 1) = s(i - 1) + d(i - 1) = dp[i - 1]
d(i) = 2/3 * 3s(i - 1) + 1/2 * 2d(i - 1) = 2s(i - 1) + d(i - 1)
dp[i] = 3s(i - 1) + 2d(i - 1) = 2dp[i - 1] + s(i - 1) = 2dp[i - 1] + dp[i - 2]
14、题目描述:有一个直方图,用一个整数数组表示,其中每列的宽度为1,求所给直方图包含的最大矩形面积。比如,对于直方图[2,7,9,4],它所包含的最大矩形的面积为14(即[7,9]包涵的7x2的矩形)。给定一个直方图A及它的总宽度n,请返回最大矩形面积。保证直方图宽度小于等于500。保证结果在int范围内。
int countArea(vector<int> A, int n) {
// write code here
}【2016校招真题在线编程】
- 输入格式:给定一个直方图A及它的总宽度n
- 输出格式:所给直方图包含的最大矩形面积
- 样例输入:
- [2,7,9,4,1],5
- 样例输出:
- 14
示例代码:(暴力)
int countArea(vector<int> A, int n) {
int left, right, sum;
int tmp, result = 0;
for(int i = 0; i < A.size(); i++){
left = 0, right = 0, sum = 1;
tmp = i;
while(tmp - 1 >= 0 && A[tmp - 1] >= A[i]){
left++;
tmp--;
}
tmp = i;
while(tmp + 1 < A.size() && A[tmp + 1] >= A[i]){
right++;
tmp++;
}
sum += left + right;
result = max(result, sum * A[i]);
}
return result;
}
15、题目描述:给出一个字符串s,分割s使得分割出的每一个子串都是回文串。计算将字符串s分割成回文分割结果的最小切割数。例如:给定字符串s="aab",返回1,因为回文分割结果["aa","b"]是切割一次生成的。
int minCut(string s) {
//write code here
}【leetcode】
- 输入格式:一个字符串s
- 输出格式:最小切割数目
- 样例输入:无
- 样例输出:无
示例代码:
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
const int MAX_N = 200;
int dp[MAX_N];//dp[i]表示以i之前的回文串的最大切割数
int minCut(string s) {
dp[0] = 0;
for(int i = 1; i < s.size(); i++){
dp[i] = dp[i - 1] + 1;
for(int j = 0; j <= i; j++){
string tmp = s.substr(j, i - j + 1), revStr = tmp;
reverse(revStr.begin(), revStr.end());
if(tmp == revStr){
if(j == 0){
dp[i] = 0;
}else{
dp[i] = min(dp[i], dp[j - 1] + 1);
}
break;
}
}
}
return dp[s.size() - 1];
}
int main(){
string s;
while(cin >> s){
cout << minCut(s) << endl;
}
return 0;
}
参考文献:
[1]杨泽邦、赵霖. 计算机考研——机试指南(第2版). [M]北京:电子工业出版社,2019.11;