c++動態規劃類算法編程彙總(三)拼爲最小的數|醜數|最長遞增子序列|旅行家問題

目錄

一、數字拼接爲最小的數

1.1 string的比大小

1.2 直接實現

1.3 封裝到類中

二、醜數

2.1 直觀做法

2.2 錯誤代碼

2.3正確代碼

2.4 錯誤代碼及其原因查找

三、最長上升子序列

3.1 題意及分析

3.2 代碼及解析

四、旅行家問題

4.1 題目描述

4.2 解法一、貪心算法

4.3 全排列解法


一、數字拼接爲最小的數

https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking

輸入一個正整數數組,把數組裏所有數字拼接起來排成一個數,打印能拼接出的所有數字中最小的一個。例如輸入數組{3,32,321},則打印出這三個數字能排成的最小數字爲321323。

1.1 string的比大小

字符串可以直接比大小,例如:

#include<iostream>
#include<vector>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;

bool concat_compare(string a, string b){
	return (a + b < b + a);
}

int main(){
	string a = "11136";
	string b = "222";
	cout << "a:" + a << endl;
	cout << "b:" + b << endl;

	if (a < b)cout << "a<b";
	else
		cout << "b<=a";
	cout << endl;
	if (concat_compare(a, b))cout << "a+b < b+a";
	else cout << "b+a < a+b";
	cout << endl;

	int end; cin >> end;
	return 0;
}

輸出:

a:11136
b:222
a<b
a+b < b+a

1.2 直接實現

如果直接對結果進行書寫,是完全可以實現的,並且不會報錯,

#include<iostream>
#include<vector>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;

bool concat_compare(string a, string b){
	return (a + b < b + a);
}

int main(){
	vector<string> result = { "3", "32", "321" };
	sort(result.begin(), result.end(), concat_compare);
	
	for (auto item : result){
		cout << item;
	}
	cout << endl;

	int end; cin >> end;
	return 0;
}

1.3 封裝到類中

不懂下面代碼爲什麼會報錯:

class Solution {
public:
	bool concat_compare(string a, string b){
		return (a + b < b + a);
	}

	string int_to_string(int num){
		string result;
		while (num){
			result += num % 10 + '0';
			num /= 10;
		}
		for (int idx = 0; idx < result.size() / 2 - 1; idx++){
			swap(result[idx], result[result.size() - 1 - idx]);
		}
		return result;
	}
	string PrintMinNumber(vector<int> numbers) {
		vector<string> str_numbers;
		for (auto item : numbers){
			str_numbers.push_back(int_to_string(item));
		}
		std::sort(str_numbers.begin(), str_numbers.end(), Solution::concat_compare);
		string result;
		for (auto item : str_numbers){
			result += item;
		}
		return result;
	}
};

報錯:

錯誤	3	error C2780: “void std::sort(_RanIt,_RanIt)”: 應輸入 2 個參數,卻提供了 3 個	
e:\工作盤\c_program\run\run\run.cpp	30	1	run
錯誤	2	error C3867: “Solution::concat_compare”:  函數調用缺少參數列表;請使用
“&Solution::concat_compare”創建指向成員的指針	
e:\工作盤\c_program\run\run\run.cpp	30	1	run

更改方法:

在函數定義的bool前加上static

下面他人方法簡潔且高效

class Solution {
public:
    string PrintMinNumber(vector<int> numbers) {
        int len = numbers.size();
        if(len == 0) return "";
        sort(numbers.begin(), numbers.end(), cmp);
        string res;
        for(int i = 0; i < len; i++){
            res += to_string(numbers[i]);
        }
        return res;
    }
    static bool cmp(int a, int b){
        string A = to_string(a) + to_string(b);
        string B = to_string(b) + to_string(a);
        return A < B;
    }
};

二、醜數

https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b?tpId=13&tqId=11186&tPage=2&rp=2&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

只能被2,3,4整除的數被稱爲醜數,醜數包括1,問第idx大的醜數是多少。

2.1 直觀做法

最直觀的做法就是下面這種,直接遍歷所有的數,判斷是否爲醜數,如果爲醜數則進行自增;

#include<iostream>
#include<vector>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;

class Solution {
public:
	bool if_ugly_num(int num){
		if (num < 1)return false;
		while (num != 1){
			if (num % 2 != 0 && num % 3 != 0 && num % 5 != 0)
				return false;
			if (num % 2 == 0)num = num / 2;
			if (num % 3 == 0)num = num / 3;
			if (num % 5 == 0)num = num / 5;
		}
		return true;
	}
	int GetUglyNumber_Solution(int index) {
		if (index < 1)return 0;
		int num_of_ugly = 0;
		int current_ugly_num;
		for (int idx = 1;; idx++){
			if (if_ugly_num(idx)){
				num_of_ugly++;
				if (num_of_ugly == index)
					return idx;
			}
		}
	}
};

int main(){
	int a = 3;
	Solution s1;
	for (int idx = 1; idx < 20; idx++){
		cout << s1.GetUglyNumber_Solution(idx) << " ";
	}
	
	int end; cin >> end;
	return 0;
}

從小到大丑數爲:

1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32

採用相對優化後的算法,依然可以實現

2.2 錯誤代碼

思路爲,前面的醜數乘以2,3,5中,最小的醜數,即下一個醜數。思考,爲什麼此法行不通?同時此法算法複雜度較高。

#include<iostream>
#include<vector>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;

class Solution {
public:
	int GetUglyNumber_Solution(int index) {
		if (index < 1)return 0;
		if (index == 1)return 1;
		vector<int> ugly_num;
		ugly_num.push_back(1);
		for (int loc = 1; loc < index; loc++){
			int min_plus2, min_plus3, min_plus5;
			int last_ugly_num = ugly_num[ugly_num.size() - 1];
			int max_loc = loc - 1;
			while (max_loc >= 0 && 2 * ugly_num[max_loc] >last_ugly_num){
				min_plus2 = 2 * ugly_num[max_loc];
				max_loc--;
			}
			max_loc = loc - 1;
			while (max_loc >= 0 && 3 * ugly_num[max_loc]>last_ugly_num){
				min_plus3 = 3 * ugly_num[max_loc];
				max_loc--;
			}
			max_loc = loc - 1;
			while (max_loc >= 0 && 5 * ugly_num[max_loc]>last_ugly_num){
				min_plus5 = 5 * ugly_num[max_loc];
				max_loc--;
			}
			int next_ugly_num = min(min_plus2, min_plus3);
			next_ugly_num = min(next_ugly_num, min_plus5);
			ugly_num.push_back(next_ugly_num);
		}
		return ugly_num[index - 1];
	}
};

int main(){
	int a = 3;
	Solution s1;
	for (int idx = 1; idx < 20; idx++){
		cout << s1.GetUglyNumber_Solution(idx) << " ";
	}
	
	int end; cin >> end;
	return 0;
}

只能通過86%左右。

用例:
1500
對應輸出應該爲:
859963392
你的輸出爲:
430467210

爲什麼這種方法會出現類似的錯誤後面我們會看出相應的問題出在哪。

2.3正確代碼

此方法簡潔且高效。相當與針對2,3,4最小的倍數,比大小,最小的存入ugly_num之中。

int GetUglyNumber_Solution(int index) {
		if (index < 1)return 0;
		vector<int> ugly_num(index);
		ugly_num[0] = 1;
		int loc_plus2 = 0;
		int loc_plus3 = 0;
		int loc_plus5 = 0;
		for (int loc = 1; loc < index; loc++){
			ugly_num[loc] = min(ugly_num[loc_plus2] * 2,min( ugly_num[loc_plus3] * 3, ugly_num[loc_plus5] * 5));
			if (ugly_num[loc] == ugly_num[loc_plus2] * 2)loc_plus2++;
			if (ugly_num[loc] == ugly_num[loc_plus3] * 3)loc_plus3++;
			if (ugly_num[loc] == ugly_num[loc_plus5] * 5)loc_plus5++;
		}
		return ugly_num[index - 1];
	}

2.4 錯誤代碼及其原因查找

將兩個代碼進行對比,只要出現不一樣的地方,則輸出位置和對應的參數:

#include<iostream>
#include<vector>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;

class Solution {
public:
	int GetUglyNumber_Solution_error(int index) {
		if (index < 1)return 0;
		if (index == 1)return 1;
		vector<int> ugly_num;
		ugly_num.push_back(1);
		for (int loc = 1; loc < index; loc++){
			int min_plus2, min_plus3, min_plus5;
			int last_ugly_num = ugly_num[ugly_num.size() - 1];
			int max_loc = loc - 1;
			while (max_loc >= 0 && 2 * ugly_num[max_loc] >last_ugly_num){
				min_plus2 = 2 * ugly_num[max_loc];
				max_loc--;
			}
			max_loc = loc - 1;
			while (max_loc >= 0 && 3 * ugly_num[max_loc]>last_ugly_num){
				min_plus3 = 3 * ugly_num[max_loc];
				max_loc--;
			}
			max_loc = loc - 1;
			while (max_loc >= 0 && 5 * ugly_num[max_loc]>last_ugly_num){
				min_plus5 = 5 * ugly_num[max_loc];
				max_loc--;
			}
			int middle_min = min(min_plus2, min_plus3);
			int next_ugly_num = min(middle_min, min_plus5);
			ugly_num.push_back(next_ugly_num);
		}
		return ugly_num[index - 1];
	}
	int GetUglyNumber_Solution(int index) {
		if (index < 1)return 0;
		vector<int> ugly_num(index);
		ugly_num[0] = 1;
		int loc_plus2 = 0;
		int loc_plus3 = 0;
		int loc_plus5 = 0;
		for (int loc = 1; loc < index; loc++){
			ugly_num[loc] = min(ugly_num[loc_plus2] * 2,min( ugly_num[loc_plus3] * 3, ugly_num[loc_plus5] * 5));
			if (ugly_num[loc] == ugly_num[loc_plus2] * 2)loc_plus2++;
			if (ugly_num[loc] == ugly_num[loc_plus3] * 3)loc_plus3++;
			if (ugly_num[loc] == ugly_num[loc_plus5] * 5)loc_plus5++;
		}
		return ugly_num[index - 1];
	}
};

int main(){
	int a = 3;
	Solution s1;
	int index = 1356;
	for (index = 1300; index < 1500; index++){
		if (s1.GetUglyNumber_Solution(index) != s1.GetUglyNumber_Solution_error(index)){
			cout << "location:" << index << ": ";
			cout << s1.GetUglyNumber_Solution(index) << " ";
			cout << s1.GetUglyNumber_Solution_error(index) << " ";
			cout << endl;
		}
	}

	
	int end; cin >> end;
	return 0;
}

最終輸出爲:

location:1366: 432000000 430467210
location:1367: 437400000 430467210
location:1368: 439453125 430467210
location:1369: 442368000 430467210
location:1370: 442867500 430467210
location:1371: 447897600 430467210
location:1372: 450000000 430467210
location:1373: 452984832 430467210
location:1374: 453496320 430467210
location:1375: 455625000 430467210

我們看到,正確代碼每次可以輸出正確的醜數,但是錯誤代碼無法輸出正確的醜數,並且每次值都一樣,430467210,可能與位數溢出有關。次數十進制位數已經達到9位數,非常容易溢出。數字自1346之後便一直保持在同一個值。

設置斷點,看下1363之後的數是如何運算的結果發現,運算都不正確。搞不清楚到底爲何?暫且放着往下進行。

 

三、最長上升子序列

3.1 題意及分析

longest increase subsequence(LIS)

即求解一個數列,最長的上升的子序列是多少。比如2,11,4,12,6,1的最長上升子序列是2,4,6或者2,11,12

動態規劃相關問題。如果一個子序列是LIS,其右邊的值A大於LIS中左右邊的值,則這個值A可以加入其中。

例如 2,11,4,12,6,1中

  • 以11的結尾的LIS可以加入到以2結尾的LIS
  • 以4結尾的LIS可以加入到以2結尾的LIS
  • 以12結尾的LIS可以加入以2,11,4結尾的LIS
  • 以6結尾的LIS可以加入到以2,4結尾的LIS

相當於右邊每加入一個元素,則可以append到左邊比它小的元素後面。

3.2 代碼及解析

class Solution
{
public:
	int lengthOfLIS(vector<int> &nums)
	{
		const int size = nums.size();
		if (size < 1)
			return 0;
		int max_length = 1;
		// lengthOfLISEndAtI[i]存儲了:以nums[i]結尾的LIS的長度。
		vector<int> lengthOfLISEndAtI(size, 1);

		for (int i = 1; i < size; i++)//最右邊的元素i
		{
			for (int j = 0; j < i; j++)//i左邊的元素j
			{
				// 找出那些在nums[i]左邊且比nums[i]小的元素
				if (nums[j] >= nums[i])
					continue;
				// 以nums[j]結尾的LIS與nums[i]組合,是否能產生更長的LIS(以nums[i]結尾)
				if (lengthOfLISEndAtI[i] < lengthOfLISEndAtI[j] + 1)
				{
					lengthOfLISEndAtI[i] = lengthOfLISEndAtI[j] + 1;
				}
			}
			// 以哪個元素結尾的LIS最長
			if (max_length < lengthOfLISEndAtI[i])
			{
				max_length = lengthOfLISEndAtI[i];
			}
		}
		return max_length;
	}
};
  • i用於模擬從左往右掃描的過程
  • j是i左邊的元素
  • num[j]>num[i]則說明j可以與i的最長子序列構成最長子序列
  • lengthOfLISEndAtI是size維,每個下標對應於以當前結尾的最長子序列的長度

四、旅行家問題

旅行家問題,是典型的動態規劃問題。

4.1 題目描述

用戶出行旅遊通常都是一個閉環,即從某地出發遊覽各個景點,最終回到出發地。已知各個景點間的通行時間。請你設計一套算法,當用戶給定出發地以及景點後,給出一條遊覽路線,使得用戶能夠不重複的遊歷每個景點並返回出發地的總通行時間最短,計算該最短通行時間。假設所有用戶的出發地都是0

輸入

第一行:出發地與景點的總個數 n

第二行:景點間的直達路線的個數m

其後m行:各個景點的通行時間a   b   t

表示a地與b地之間的通行時間是t。

輸出

不重複遊覽完所有景點並返回出發地的最短遊覽時間T

若不存在這樣的遊覽路線,返回-1

樣例輸入

4
6
0 1 4
0 2 3
0 3 1
1 2 1
1 3 2
2 3 5

樣例輸出

7

4.2 解法一、貪心算法(非完備,局部最優解)

正確率只有63%

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<set>
#include<stdio.h>
using namespace std;

int main(){
	int n, lines; cin >> n >> lines;
	
	vector<int> each_row(n, -1);
	vector<vector<int>> matrix(n, each_row);
	for (int idx = 0; idx < lines; idx++){
		int start, dest, cost; cin >> start >> dest >> cost;
		matrix[start][dest] = cost;
		matrix[dest][start] = cost;
	}

	int edge_count = 0;
	int min;
	int start_city = 0;
	int next_city;
	int final_length = 0;
	vector<bool> reached(n, false);
	reached[start_city] = true;
	while (edge_count < n - 1){
		min = 0x7fffffff;
		for (int city_idx = 0; city_idx < n; city_idx++){
			if (!reached[city_idx] && matrix[start_city][city_idx] != -1 && matrix[start_city][city_idx] < min){
				next_city = city_idx;
				min = matrix[start_city][city_idx];
			}
		}
		if (next_city == start_city){
			cout << -1 << endl;
			return 0;
		}
		final_length += matrix[start_city][next_city];
		edge_count++;
		start_city = next_city;
		reached[start_city] = true;
	}
	final_length += matrix[start_city][0];
	cout << final_length << endl;

	int ee; cin >> ee;
	return 0;
}

4.3 全排列解法

生成全排列,然後統計出每個排列的cost。

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<set>
#include<stdio.h>
using namespace std;
set<vector<int>> all_paths;

void all_perm(vector<int> path, int idx){
	int length = path.size();
	all_paths.insert(path);
	if (idx == length - 1){
		return;
	}
	for (int change = idx; change < length; change++){
		swap(path[change], path[idx]);
		all_perm(path, idx + 1);
		swap(path[change], path[idx]);
	}
}


int main(){
	int n, lines; cin >> n >> lines;
	
	vector<int> each_row(n, -1);
	vector<vector<int>> matrix(n, each_row);
	for (int idx = 0; idx < lines; idx++){
		int start, dest, cost; cin >> start >> dest >> cost;
		matrix[start][dest] = cost;
		matrix[dest][start] = cost;
	}

	vector<int> path;
	for (int idx = 0; idx < n; idx++){
		path.push_back(idx);
	}
	all_perm(path, 1);

	bool exist = false;
	int min_way = 0x7fffffff;
	for (auto item : all_paths){
		for (auto it : item){
			cout << it << ',';
		}
		cout << endl;
		int distance = 0;
		bool done = true;
		for (int idx = 0; idx < n - 1; idx++){
			if (matrix[item[idx]][item[idx + 1]] == -1){
				break;
				done = false;
			}
			else{
				distance += matrix[item[idx]][item[idx + 1]];
			}

		}
		distance += matrix[item[n - 1]][0];
		if (done){
			exist = true;
			min_way = min(min_way, distance);
		}
	}
	if (exist)cout << min_way << endl;
	else cout << -1 << endl;
	int ee; cin >> ee;
	return 0;
}

 

五、相對簡單的路徑問題

leetcode 64,OJ:

https://leetcode-cn.com/problems/minimum-path-sum/submissions/

給定一個包含非負整數的 m x n 網格,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和爲最小。每次只能向下或者向右移動一步。

思路:這種思路很簡單,因爲相應的路徑只能從左邊或者上邊來,所以只要對比左邊和上邊最小的,加上本路徑即可。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

class Solution {
public:
	int minPathSum(vector<vector<int>>& grid) {
		int row = grid.size();
		if (row < 1)return 0;
		int col = grid[0].size();
		if (col < 1)return 0;
		for (int idx_r = 1; idx_r < row; idx_r++){
			grid[idx_r][0] += grid[idx_r - 1][0];
		}
		for (int idx_c = 1; idx_c < col; idx_c++){
			grid[0][idx_c] += grid[0][idx_c-1];
		}
		for (int idx_r = 1; idx_r < row; idx_r++){
			for (int idx_c = 1; idx_c < col; idx_c++){
				grid[idx_r][idx_c] += min(grid[idx_r-1][idx_c], grid[idx_r][idx_c-1]);
			}
		}
		return grid[row-1][col-1];
	}
};

int main(){
	vector < vector < int >>grid = { { 1, 3, 1 },
									{ 1, 5, 1 },
									{4, 2, 1 }};
	Solution s1;

	cout << s1.minPathSum(grid) << endl;

	int end; cin >> end;
	return 0;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

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