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" }; //321323
	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之后的数是如何运算的结果发现,运算都不正确。搞不清楚到底为何?暂且放着往下进行。

三、最长上升子序列

OJ:https://leetcode-cn.com/problems/longest-increasing-subsequence/submissions/

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 代码及解析O(N*N)

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维,每个下标对应于以当前结尾的最长子序列的长度

3.3 更简单的方法N*logN

对于此问题,完全可以采用更加简便的方法。动态规划加上二分搜索

  • 首先创建一个数组lis,专门用于存放最长子序列。
  • 将输入数组从左往右遍历,current为数组中每次向右一个的数字
  • 如果新入的值current>lis中最大的值,即current>lis[lis.size()-1],则一定可以进行更新,因此lis.push_back(current)
  • 如果current在的值介于lis中间,即可能是另一个最长子序列,则更新lis中刚好大于current的值。找到这个刚好大于current的值这个用二分查找法实现,用lis[lower] = current_num;来实现更新
  • lis[lower] = current_num这个更新过程的意义在于,如果后续可以有值append在current_num之后,则只用更新lis[lower+1],直到append的比lis更长。这是对于新序列而言,同时,如果对于旧的最长子序列有更好的更新,则在未更新在最后部分即可。
  • 核心在于等价,即把Lis其中的值替换为更小的值,依然与之前等价。
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

class Solution {
public:
	int lengthOfLIS(vector<int>& nums) {
		int length = nums.size();
		if (length < 1) return 0;
		vector<int>lis;
		lis.push_back(nums[0]);
		int lower = 0; int upper = 1;
		for (int idx = 1; idx < length; idx++){
			int current_num = nums[idx];
			if (current_num>lis[lis.size() - 1]){
				lis.push_back(current_num);
			}
			else{
				lower = 0;
				upper = lis.size()-1;
				while (lower < upper){
					if (current_num == 3)int b;
					int mid = (upper + lower) / 2;
					if (current_num>lis[mid])lower = mid+1;
					else upper = mid;
					if (current_num == 3)int a;
				}
				lis[lower] = current_num;
			}
		}
		return lis.size();
	}
};

int main(){
	vector<int>gas = { 10, 9, 2, 5, 3, 7, 101, 18 }; //4
	vector<int> out_2 = { 10, 9, 2, 5, 3, 4 };//2

	Solution s1;

	cout << s1.lengthOfLIS(gas) << endl;
	cout << s1.lengthOfLIS(out_2) << endl;


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

四、旅行家问题

旅行家问题,是典型的动态规划问题。

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;
}

4.4 动态规划(待补充)

五、相对简单的路径问题

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;
}
发布了210 篇原创文章 · 获赞 584 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/weixin_36474809/article/details/100401576