算法基礎知識——動態規劃(二)

算法基礎知識——動態規劃(二)

目錄:

  1. 應用實例
    1. 矩陣的最小路徑和【程序員代碼面試指南】
    2. 字符串的排列【劍指offer】
    3. 合唱團【2017校招真題在線編程】
    4. 連續最大和【2017校招真題在線編程】
    5. 藏寶圖【2017校招真題在線編程】
    6. 買蘋果【網易】
    7. 上臺階【京東】
    8. word-break【leetcode】
    9. candy【leetcode】
    10. 罪犯轉移【2016校招真題在線編程】
    11. 跳石板【網易】
    12. 年終獎【京東】
    13. 暗黑的字符串【2017校招真題在線編程】
    14. 直方圖內最大矩形【2016校招真題在線編程】
    15. 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;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章