【努力刷力扣】第三十六天 --- 基于递归算法解决0/1背包问题、归并分类算法、地牢逃生问题、自然数拆分问题
引言
本人初次尝试写博客,希望各位看官大佬多多包容
有错误希望巨巨们提出来,我一定会及时改正,谢谢大家
在自己最好的年纪,找到了未来的目标
还有1年奋斗刷题,明年去面试实习,加油!
博主今天学校上机实验,给大家分享一下
题目一要求:
注:
1、本体由于老师问题,导致题目不严谨,只能采用从后向前的顺序才能解出来,从前到后顺序会错
2、题目没说明白如果多个答案情况,是怎么取舍问题,所以暂时不理会
整体思路
1、我们类比一个场景:一辆汽车排队进入,要求恰好进入一些人之后重量为m,给了n个排队的人的重量
第一:这么一堆人,为了让我的处理更具有条理性,所以我们必须按照先后次序依次处理,产生了一个类似于链表的递归链条,。
第二:我们随便来一个人,体重大于要求了,他则必定不行,看下一个(前提得判断有下一位),这个人一上,恰好达到标准重量,则之前选中的人和他自己都是目标了,则返回true,告诉他们的前辈们,若体重小于要求,由于01背包要求恰好装入,所以可能之后恰好装入,也可能不装入,,所以这样,一旦发现恰好装入,就集体返回true,所有这条路上的接到true就代表满足题意,接着返回true。
第三:大家都在一个递归的大结构上,就算你不是要的节点,但是既然在这条路上了,就要担负起传递信息的任务。
2、结合上面的场景,一置换就是我们的01背包问题!
具体代码(内附注释):
注:
1、返回true的路径就是能装进去的,不能装的也要帮着传递信息
2、由于题目缺陷只能从后向前处理。
3、回溯法的一个核心思想就是要把底层的信息带到上面去,每一个人都是传话筒,把底下信息传上去,达到回溯效果。
//本算法是从后向前处理
#include <iostream>
#include <vector>
using namespace std;
vector<int >ans, item;
bool find(int m, int n) {//核心算法,m为重量,n是处理的是谁
int temp = item[n];//获得当前重量,之后可能有三种情况出现。
if (temp > m) {//情况一
if (n <= 0) {//到了最后一个还是装不进去,一定错了
return false;
}
return find(m, n - 1);//没到最后,既然他自己不行,就按顺序处理下一个,但他要把之后处理的啥样向上传递
//这就是所谓的回溯法,要把底下的信息带到上面去。
}
if (temp == m) {//情况二,恰好装入
ans.push_back(temp);
return true;//所有返回true就代表了可以装入
}
if (temp < m) {
if (n <= 0) {//到最后了不能装入就是错
return false;
}
//看看他装进去了,后面能不能恰好装入
if (find(m - temp, n - 1)) {//能的话,后面给他true,他就知道了自己装进去是对的,接着返回true,告诉上面
ans.push_back(temp);
return true;
}
else {
return find(m, n - 1);//说明这个点不能放进去,接着处理下一个
}
}
}
int main() {
int n, m;
scanf("%d%d", &m, &n);
for (int i = 0; i < n; i++) {
int temp;
scanf("%d", &temp);
item.push_back(temp);
}
vector<int> path;
find(m, n - 1);
if (ans.size() == 0) {//以下是控制输出,可不看
cout << 0;
return 0;
}
int i = 0;
for (; i < ans.size() - 1; i++) {
printf("%d ", ans[i]);
}
printf("%d", ans[i]);
return 0;
}
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短-时间):
题目二要求:
整体思路
就是归并排序,具体解析见我另一篇博客归并排序
具体代码(内附注释):
#include <iostream>
#include <vector>
using namespace std;
void apart(vector<int>& A, int i, int j) {//典型的回溯合并,向下分解
if (i >= j) {//终止条件
return;
}
int mid = (i + j) / 2;
apart(A, i, mid);//后根,这个函数一完事,i-mid排完了
apart(A, mid + 1, j);//这个函数一完事,mid+1-j排完了
//写之后的融合代码的时候,可以找一个简单的样例写,再拿边界情况验证
//辅助vector
//走到这里的代表i-mid 和 mid+1-j 都已经整好了,剩下的就是把这个两个整体合并一下就行了,先存到辅助数组里,在复制即可
//上下级传递的是指针
//下面就是排i-j
vector<int> temp(j - i + 1, 0);
int left = i;//left从i到mid,right从mid+1到j
int right = mid + 1;
int point = 0;
while (left <= mid && right <= j) {
if (A[left] <= A[right]) {
temp[point++] = A[left++];//看谁小谁就下去放进辅助数组
}
else {
temp[point++] = A[right++];
}
}
while (left <= mid) {//把剩下的放进去
temp[point++] = A[left++];
}
while (right <= j) {
temp[point++] = A[right++];
}
left = i;
point = 0;
while (left <= j) {
A[left++] = temp[point++];
}
}
int main() {
vector<int> A;
int num;
scanf("%d", &num);
for (int i = 0; i < num; i++) {
int temp;
scanf("%d", &temp);
A.push_back(temp);
}
apart(A, 0, A.size() - 1);
int i = 0;
for (; i < A.size() - 1; i++) {
printf("%d ", A[i]);
}
printf("%d", A[i]);
}
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短-时间):
题目三要求:
整体思路
明确目标:这个题其实我们要抓住题目,题目说t时刻t水深,我们就看这个水深能不能让我们游到终点,能的话这一定是一个解,不能的话就说明太浅了,所以总体思路就是给水深,看能不能到终点就行了。
具体代码(内附注释):
注:
1、一定不要忽视原点处水深,根据题意“ T=t 时想从(i,j)位置移动到(i,j+1)位置,需要满足t≥dungeon[i][j]且t≥dungeon[i][j+1]”说明想从源点离开,水深必须得达到原点的深度,所以将答案的初始值设置为原点水深。
2、一定注意输入格式,像我采用的方法直接一个string读入,那么我们就要想到特别处理10,188,等位数多得数
3、对于find函数再次说明:一定要清楚,我这里是并没有考虑原点的深度的,假设一定可以从原点出去。看当前节点能不能到终点,能,则前驱节点更新为该节点,向前再找一个节点作为当前节点,不能,说明前驱节点是最小深度。并且得到的答案一定要反过来考虑这个深度能不能游出原点,所以和初始值二者选大者是答案。注意:此时temp是深度非递减序列,实验过后发现就算循环走完了发现任意深度都能走到终点,那么就意味着假设能从原点走出来,任意深度都能走到终点,那么答案就是初始值,即保证能走出原点。
4、对全局变量"bool isVisted[10000][10000];"说明:他记录任意一次dfs经过的节点的数组,经过了是true,但一定要注意一旦这个点处理完了就必须把这个点的状态复原,一定要记住这个操作,因为状态数组是全局的!!!!,并且记住一个技巧,在设置状态时,只设置当前层的状态,不跨层设置。
#include <iostream>
#include <vector>
#include <string>
#include <cmath>
#include <algorithm>
using namespace std;
vector<int > temp;
int item[10000][10000];//存储各个点水深
bool isVisted[10000][10000];//存储每条路径经过情况,一条路径相同的点不可重复走
int ans_time = 0;//答案
int hlong = 0;//规模,也就是N*N中的N
int dx[] = { -1,0,1,0 };//二者共同组成4方向
int dy[] = { 0,-1,0,1 };
int max(int a, int b) {//自己写的找最大函数
return a > b ? a : b;
}
bool time(int i, int j, int max) {//看能不能到终点
isVisted[i][j] = true;//上来就要标记状态
bool flag = false;
if (i == hlong - 1 && j == hlong - 1) {//已经找到终点
isVisted[i][j] = false;//复原状态
return true;//回溯传递信息
}
for (int k = 0; k < 4; k++) {
int x = i + dx[k];
int y = j + dy[k];
if ((x < 0 || x >= hlong) || (y < 0 || y >= hlong) || isVisted[x][y] == true || item[x][y] > max) {
continue;
}//判断条件:不可越界+这个点没走过+深度不可以超过我给的最大值
flag = time(x, y, max);//走到这里说明x,y可以走,可以继续判断
if (flag) {//快速退出通道
isVisted[i][j] = false;//快速退出就把状态复原
return true;
}
}
isVisted[i][j] = false;//处理完退出就把状态复原
return false;//一定要手动返回值,要不默认返回true
}
void find(int i, int j) {
int num = temp.size();//初始化前驱
int last = temp[num - 1];//从倒数第二个开始判断。
//一旦当前的深度走不了,说明前驱就是答案,要不然前驱节点就是本节点,处理的节点向前一个
int k = num - 2;
for (; k >= 0; k--) {//这里并没有处理原点值
if (time(i, j, temp[k])) {
last = temp[k];
}
else {
ans_time = max(ans_time, last);//最后得到的值和原点值取最大,保证能从原点走出去。
break;
}
}
}
int main() {
string str1;
std::cin >> str1;
int temp_integer = 0;//记录数字数值
bool flag = false;//代表开始拼树字
for (int i = 0; i < str1.size(); i++) {//处理输入,说白了就是一个字符一个字符处理就行
if (str1[i] >= '0'&&str1[i] <= '9') {
int tmp = str1[i] - '0';
temp_integer = temp_integer * 10 + tmp;//拼树字
flag = true;
}
else {
if (flag) {
temp.push_back(temp_integer);
temp_integer = 0;//在遇到数字后第一次遇到字符,代表这个数字拼完了,可以压入
flag = false;
}
}
}
ans_time = temp[0];//做初始化,因为我找水深时候没考虑原点,所以初始化就把原点考虑进去,
//在之后后面的最深深度和原点取最大值即可
hlong = sqrt(temp.size());
for (int i = 0; i < temp.size(); i++) {
int x = i / hlong;//找位置
int y = i % hlong;
item[x][y] = temp[i];
}
sort(temp.begin(), temp.begin() + temp.size());//排序,方便从后面找最深水深
find(0, 0);
printf("%d", ans_time);
}//[[8,3,4],[5,6,8],[9,7,3]]
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短-时间):
题目四要求:
整体思路
1、这道题,我的理解里就是一个分治回溯过程,将一个大的任务一步一步的分解成若干小任务,等到小任务都完事了,大任务再换一种分解方法,这里的限制就是要求的分解队列必须非递减,这个限制了我们分解方法
2、先打印再分解。
3、具体过程如图所示:
具体代码(内附注释):
注:一定要注意防止越界,因为我要的序列要保证非递减,所以所以我再分解的时候我分解出来的数就要和他上一位比,所以在开始的时候就一定要保证-1不越界,所以一切从1开始!!!!!!!
#include <iostream>
#include <vector>
using namespace std;
int a[10000];//存储数组
void print(int t) {//给位置直接打印就行
int i = 1;
for (; i < t; i++) {
printf("%d+", a[i]);
}
printf("%d", a[i]);
printf("\n");
}
void dfs(int N, int t) {
for (int i = 1; i <= N / 2; i++) {
if (i >= a[t - 1]) {//保证了非递减序列
a[t] = i;//把这一种分解方式放进自己对应在a的位置
a[t + 1] = N - i;
print(t + 1);//打印,每次把t,t+1放进去,前面是已经排好了的,所以直接从1到t+1打印就行
dfs(N - i, t + 1);//然后把分解后的数接着发给下一个函数去分解
//等下面的数都分解好了,再回溯过来,把这个函数的N换一种方式再次分解
//等分好了,那么这个数就完事返回告诉他的父亲节点就行了,他父节点就接着按照别的方法分解
}
}
}
int main()
{
int n;
scanf("%d", &n);
dfs(n, 1);
return 0;
}
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短-时间):