cout<<ans[i]<<" ";
}
cout<<endl;
return;
}
for(int i=start;i<=n;i++){
ans[u] = i;;
dfs(u+1,i+1);
ans[u] = 0;
}
}
int main(){
cin>>n>>m;
dfs(0,1);
return 0;
}
#### 2.带分数
![](https://img-blog.csdnimg.cn/direct/601d5e80c7d845989b6a12b7d50e7875.png)
解题思路:
(1)暴力枚举所有排列,用两个隔板划分出a,b,c。再判断。复杂度为9!\*9\* ![$\binom{2}{8}$](https://latex.csdn.net/eq?%24%5Cbinom%7B2%7D%7B8%7D%24)。差不多是![9*10^{_{7}}](https://latex.csdn.net/eq?9*10%5E%7B_%7B7%7D%7D),刚好满足时间复杂度。
(2)上一个思路相当于暴力枚举了三个数a,b,c。并且没有用上题目给的n,样例中n=100。根据题目中包含的等式a+b/c = n -> a\*c + b = n\*c。只需要枚举a和c两个数,根据n可以将b算出来。这样枚举的数字减少,可以有效减少时间复杂度。
先枚举a,在a的叶子节点枚举c,再判断等式是否成立,若成立则增加计数。
代码:
有几个需要注意的点,这种方式代码很容易出错。dfs\_a函数中递归边界为a>=n。dfs\_c的递归边界为位置是否占满。check函数中主要判断1-9的数字是否全部使用,此时不应该使用原来的st数组,应该新建backup数组,因为st数组用来保持回溯,**check函数中对bool数组的操作无法保证恢复现场**。
#include<bits/stdc++.h>
using namespace std;
const int N = 20;
int n;
bool st[N], backup[N];
int cnt = 0;
bool check(int a,int c){
int b = nc - ac;
memcpy(backup,st,sizeof st);
if(!a || !b || !c) return false;
while(b){
int i = b%10;
b/=10;
if(!i||backup[i]) return false;
backup[i] = true;
}
for(int i=1;i<=9;i++){
if(!backup[i]) return false;
}
return true;
}
void dfs_c(int u,int a, int c){
if(u == 9) return;
if(check(a,c)) cnt++;
for(int i=1;i<=9;i++){
if(!st[i]){
st[i] = true;
dfs_c(u+1,a,c*10+i);
st[i]= false;
}
}
}
void dfs_a(int u,int a){ //u表示已经填了几个位置
if(a>=n) return;
if(a) dfs_c(u,a,0);
for(int i=1;i<=9;i++){
if(!st[i]){
st[i] = true;
dfs_a(u+1,a*10+i);
st[i] = false;
}
}
}
int main(){
cin>>n;
dfs_a(0,0);
cout<<cnt<<endl;
return 0;
}
递推与递归的区别:
递归是从目标开始**自顶向下**的找同类子问题的解,最后解决原问题。而递推是**自底向上**先求子问题,根据子问题的解解决原问题。
## 递推例题:
#### 1.简单斐波那契
![](https://img-blog.csdnimg.cn/direct/913e29913ef746649b915fc8b54d5216.png)
代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin>>n;
int f[46]={0};
f[1] = 0;
f[2] = 1;
for(int i = 3;i<=n;i++){
f[i] = f[i-1] + f[i-2];
}
for(int i=1;i<=n;i++){
cout<<f[i]<<" ";
}
return 0;
}
采取滚动数组的思想:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin>>n;
int a = 0,b = 1;
for(int i = 1;i<=n;i++){
cout<<a<<" ";
int f = a+b;
a = b;
b = f;
}
return 0;
}
#### 2.费解的开关
[题目链接:95. 费解的开关 - AcWing题库]( )
题意:
改变一个灯的状态会将他上下左右的灯的状态都改变。需要使用最少次数修改灯的状态使得所有的灯变成亮着的。
解题思路:
找到题目隐藏信息,1.所有开关只能按一次,按两次相当于没按。2.灯的状态跟按开关的顺序无关,无论以什么顺序按开关,灯的状态相同。
我们发现,假如可以枚举第一行的操作(是否按开关),枚举完后,我们不能对第一行再进行操作(因为所有开关只能按一次,并且我们枚举了第一行的按开关的所有操作),**此时我们需要按第二行的开关,并且操作被第一行灯的亮灭状态所唯一决定。(递推)**当第二行操作结束后,第三行的操作也被唯一决定。以此类推,直到n-1行操作完成,此时前n-1行的状态全部为亮,只需查看最后一行是否为量即可判断该方案是否可取,如果可取,更新最小答案。
代码:
代码实现有不少细节的地方,比如turn函数中传入坐标x,y不能直接使用,应该用a,b代替。因为坐标会在for循环中被修改,而实际上传进来的x,y坐标不应该变。
还有就是memcpy函数对原数组进行备份, 因为for循环枚举所有方案,每次方案开始时要保证都是原来的数组,所以要备份。在每个方案结束后再将数组还原。
#include<bits/stdc++.h>
using namespace std;
char light[6][6],backup[6][6];
int dx[5]={0,1,0,-1,0},dy[5]={1,0,-1,0,0};
void turn(int x,int y){
for(int i=0;i<5;i++){
int a = x + dx[i];
int b = y + dy[i];
if(a<0||a>=5||b<0||b>=5) continue;
if(light[a][b] == '0') light[a][b] = '1';
else light[a][b] = '0';
}
return;
}
int main(){
int n;
cin>>n;
for(int a=0;a<n;a++){
for(int i=0;i<5;i++) cin>>light[i];
int res = 10;
for(int op=0;op<32;op++){
memcpy(backup,light,sizeof light);
int step = 0;
for(int k=0;k<5;k++){
if(op>>k&1){
turn(0,k);
step++;
// cout<<1111<<endl;
}
}
for(int i=0;i<4;i++){
for(int j=0;j<5;j++){
if(light[i][j] == ‘0’){
turn(i+1,j);
step++;
}
}
}
bool dark = false;
for(int i=0;i<5;i++){
if(light[4][i] == ‘0’) dark = true;
}
if(!dark) res = min(res,step);
memcpy(light,backup,sizeof backup);
}
if(res>6) res = -1;
cout<<res<<endl;
}
return 0;
}
## 递归习题:
#### 1.翻硬币
![](https://img-blog.csdnimg.cn/direct/4a208b3258a249fd8e4a713888610aab.png)
解题思路:
挖掘题目信息1.所有硬币的中间相当于有个开关,按一次会将开关两侧的硬币翻转。2.按开关的顺序与硬币状态无关。3.开关只能按一次,按两次相当于没按。
考虑按顺序按开关,发现假如前面的开关已经按下的话,后面开关是否需要被按被前面硬币的状态所唯一决定。(递推)所以按顺序按开关就完事了。
代码:
#include<bits/stdc++.h>
using namespace std;
string a,b;
void turn(int i){
if(a[i]==‘'){
a[i] = ‘o’;
if(a[i+1] == '’){
a[i+1] = ‘o’;
}else{
a[i+1] = ‘';
}
}else{
a[i] = '’;
if(a[i+1] == ‘'){
a[i+1] = ‘o’;
}else{
a[i+1] = '’;
}
}
return;
}
int main(){
cin>>a;
cin>>b;
int n = a.size();
int step = 0;
for(int i=0;i<n-1;i++){
if(a[i]!=b[i]){
turn(i);
step++;
}
}
cout<<step;
return 0;
}
#### 2.飞行员兄弟
题目链接:[116. 飞行员兄弟 - AcWing题库]( )
题意:
费解的开关的简化版,需要令一个4x4的矩阵全部变成'-'。矩阵的字符可能为'+'或'-'。当按动一个开关,同列的状态和同行的状态都会被改变。题目要求输出最小步骤数和操作的开关位置。
解题思路:
由于题目范围不大,只有总共16个数,可以考虑指数型枚举所有开关的操作。复杂度为2^16。对于每个操作,判断结果是否符合预期,如果符合,那么更新最小步骤数。
代码:
代码实现方面有个小点没想到,在turn函数中for循环中会令[x,y]处状态修改两次,所以后面要单独再修改一次。
#include<bits/stdc++.h>
using namespace std;
char fridge[5][5],backup[5][5];
void turn(int x,int y){
for(int i=0;i<4;i++){
if(fridge[x][i] == ‘+’){
fridge[x][i] = ‘-’;
}else fridge[x][i] = ‘+’;
if(fridge[i][y] == '+'){
fridge[i][y] = '-';
}else fridge[i][y] = '+';
}
//经过前面的处理,[x,y]的位置会被修改两次,这里要修改回来。
if(fridge[x][y] == '+'){
fridge[x][y] = '-';
}else fridge[x][y] = '+';
return;
}
int main(){
for(int i=0;i<4;i++) cin>>fridge[i];
int res = 100;
vector<pair<int,int>> ans;
for(int op=0;op<65536;op++){
memcpy(backup,fridge,sizeof fridge);
int step = 0;
vector<pair<int,int>> sp;
for(int i=0;i<16;i++){ //枚举每一位
if(op>>i&1){
int x = i/4;
int y = i%4;
turn(x,y);
// cout<<x<<y<<endl;
sp.push_back({x,y});
step++;
}
}
//判断方案是否可行
bool yes = true;
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
if(fridge[i][j] == ‘+’){
yes = false;
// break;
}
}
// if(!yes) break;
}
if(yes&&step<res){
cout<<step<<endl;
res = step;
ans = sp;
}
memcpy(fridge,backup,sizeof backup);
}
// cout<<ans.size()<<endl;
for(int i=0;i<ans.size();i++){
cout<<ans[i].first+1<<’ '<<ans[i].second+1<<endl;
}
return 0;
}
## 整数二分问题的思路:
模板使用的时候只需要判断是L=M还是R=M,据此判断使用模板1还是模板2。![](https://img-blog.csdnimg.cn/direct/2d2d01bf715a4707afc2a9742601c192.png)
![](https://img-blog.csdnimg.cn/direct/7ae979651d524102854f81ae0f88a622.png)
## 二分例题:
#### 1.数的范围
题目链接:[789. 数的范围 - AcWing题库]( )
题意:给n个非递减的数,找出某个数值的起始位置和结束位置。
#### 二分模板的选择:
选**右区间的左端点**时选第一个模板,选**左区间的右端点**时选第二个模板。
bool check(int x) {/* … */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
解题思路:
首先考虑题目是否可以二分,答案一定在题目给定的区间范围内,并且可以使用二段性(是否大于等于/小于等于)分隔区间,并且答案是区间的端点,所以可以使用二分。
### 最后
技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。
**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**
>技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。
![](https://img-blog.csdnimg.cn/img_convert/b1a5471dfaedc76d712105f00c5accfc.webp?x-oss-process=image/format,png)
else r = mid - 1;
}
return l;
}
解题思路:
首先考虑题目是否可以二分,答案一定在题目给定的区间范围内,并且可以使用二段性(是否大于等于/小于等于)分隔区间,并且答案是区间的端点,所以可以使用二分。
最后
技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。
[外链图片转存中…(img-UyepzaqL-1714247866863)]