递归与递推

对每一道题,首先分析问题抽象考察什么模型,分析数据范围,选用最佳的解决方案;考试时候一定要考虑清楚再写

最简单的递归-斐波那契数列
自己调用自己在这里插入图片描述在这里插入图片描述

92.递归实现指数型枚举
在这里插入图片描述
在这里插入图片描述
红圈:当我们从当前节点向下执行到最后一层向上回溯时需要"恢复现场"以保证当前节点的状态不受其他分支代码的影响
恢复现场是递归中十分重要的一个环节

#include<bits/stdc++.h>
using namespace std;
const int N=16;//数据范围 一般用const修饰

int n;
int st[N];//st数组记录当前每个位置的状态:0表示未处理,1表示选,2表示不选

void dfs(int u)//u表示当前在做第几位的排列方案-分支节点位置
{
    if(u>n)
    {
        for(int i=1;i<=n;i++)
        {
            if(st[i]==1)
            {
                printf("%d ",i);
            }
        }
        printf("\n");//输出一种排列方案后换行
        return;
    }
    
    st[u]=2;
    dfs(u+1);   //第一个分支:不选
    st[u]=0;  //恢复现场
    
    st[u]=1;
    dfs(u+1);  //第二个分支:选
    st[u]=0; 
}

int main()
{
    cin>>n;    
    dfs(1);    
    return 0;
}

94.递归实现排列型枚举
在这里插入图片描述
在这里插入图片描述
放缩法证明时间复杂度在这里插入图片描述

#include<bits/stdc++.h>

using namespace std;
const int N=10;

int n;//变量定义为全局变量,初始值为0;局部变量值随机
int state[N];//0表示还未放数,1~n表示放了那个数
bool used[N];//true 表示用过,false 表示还未用过

void dfs(int u)
{
    if(u>n)
    {
        for(int i=1;i<=n;i++) printf("%d ",state[i]);//打印方案
        puts("");
        return;
    }
    //依次枚举每个分支,即当前位置可以填那些数
    for(int i=1;i<=n;i++)
    {
        if(!used[i])
        {
            state[u]=i;
            used[i]=true;
            dfs(u+1);
            
            state[u]=0;
            used[i]=false;//恢复现场
        }
    }
}

int main()
{
    cin>>n;
    dfs(1);
    return 0;
}

93.递归实现组合型枚举
在这里插入图片描述

枚举
1.排列考虑顺序,比如对1.2.3三个数字排列:123.132.213.231.312.321
2.组合不考虑顺序,比如从1~5任意选择三个数字:123.124.125… …组合的数不能重样,比如不能存在123.231这样重复数字组合。
我们采用升序组合,保证每次新加的数比前面的一个数大,123.124.125.234.234…
也就是a1<a2,a2<a3…a(n-1)<a(n)
为了防止调试时数据越界,我们在const int N=;一般多开一些数据范围
在这里插入图片描述
剪枝:以上面的组合搜索树为例,如果某一分支节点下没有满足条件的解,我们将该分支剪掉-提前return 以提高算法效率
以本题为例,当我们枚举到第u个位置的数,此时已经枚举完成u-1个数,剩下(m-start+1)个数,若 u-1+n-start+1<m 则说明如果把后面的数全选上都不够m个,说明当前分支一定无解->提前退出,提高算法效率

#include<bits/stdc++.h>
using namespace std;

const int N=30;
int n,m;
int way[N];

void dfs(int u,int start)//u表示当前枚举到那个位置;start表示从那个数开始枚举
{
    if(u + n - start < m) return;//剪枝 如果把后面的数全选上都不够m个,说明当前分支一定无解->提前退出,提高算法效率
    if(u == m+1)//当u==m+1 说明枚举完成最后一个数 输出一种组合
    {
        for(int i=1;i<=m;i++) printf("%d ",way[i]);
        puts("");//换行
        return;
    }//边界
    
    for(int i = start ; i <= n ; i ++)
    {
        way[u] =i;
        dfs( u + 1 ,i + 1);
        
        way[u] = 0;//虽然后面的值会把该位置覆盖掉,为了更严谨依然置0 恢复现场
    }
}

int main()
{
    scanf("%d %d",&n,&m);//n个整数当中选出m个数组合
    
    dfs(1,1);//从第一个位置开始枚举,第一个枚举的数为1
    
    return 0;
}

1209.带分数在这里插入图片描述
关于空间复杂度:1Byte(字节)=8bit(比特)1bit就是8二进制位;以空间限制64MB为例, 64 MB = 642^20 Byte,可以开1.610^7个int;当前们在代码中开一个数组,在该数组尚未被使用的情况下,这个数组不占任何空间

1.正整数 n = a + b / c ,整数a b c由数字1~9组成且不重复 =>满足等式 b = n * c - a * c 检查a,c是否满足组合要求
2.枚举a,a每个叶子节点枚举c,由 b = n * c - a * c 的出b并判断等式是否成立

#include<bits/stdc++.h>

using namespace std;

const int N = 20;
int n;  //表示成带分数形式的正整数
bool st[N],backup[N];//记录当前位置是否已经被数字占用 true占用 false 未被使用
int ans;//记录一共有多少种组合方案

bool check(int a,int c)
{
    int b = n * c - a * c;
    
    if(!a || !b || !c) return false;
    
    memcpy(backup, st, sizeof st);
    while(b)//当 b > 0,拆解b判断组成b的数字当中是否有0 
    {
        int x = b % 10;//取个位
        b /= 10;       //删个位
        if(!x || backup[x]) return false;  //  x不能为0  backup[x]判断x之前是否出现过
        backup[x] = true;  //标记x已经被使用过
    }
    
    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 == n) return;//当前n个数字全部用完
    
    if(check(a,c)) ans ++;//判断a、c是否满足要求
    
    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)
{
    if (a >= n) return; // 得到的a比带分数n大
    if(a > 0) 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); //枚举完u个数字后在a后面插入数字i,把a作为一个分支节点
            st[i] = false;//恢复现场
        }
    }
}

int main()
{
    cin>>n;  
    
    dfs_a(0, 0);//(当前已经枚举/用完多少个数字,a从0开始 枚举生成正整数)
    
    cout<<ans<<endl;
    
    return 0;
}

递推

717.简单斐波那契
在这里插入图片描述

**#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 fn=a+b;
        a=b,b=fn;
    }
    return 0;
}**
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=50;
int fib[N];
int main()
{//基础写法
    int n;
    cin>>n;
    fib[1]=0,fib[2]=1;
    for(int i=1;i<=n;i++)
    {
        if(i>=3) fib[i]=fib[i-1]+fib[i-2];
        cout<<fib[i]<<" ";
    }
    return 0;
}

1208.翻硬币
递归题-费解开关的一个简化版
类型题:179.八数码在这里插入图片描述
模型分析—
1.2.思路:这里我们把一步操作后的状态一个点,每一步不同操作形成边连接不同状态的点,从初始状态到目标状态就是求两个点间的最短距离 ;把每一步发明转看作一个开关,在每个开关最多按1次情况下每个问题最多有1种方案/无解
数据范围:字符串长度最大为100,相邻硬币翻动一次,每次操作最多有99种选择,最多翻动50次达到目标状态(极端情况下,100枚硬币全部朝正面/反面=》<=50^99
代码逻辑–
1.从第一个灯泡依次比较当前灯泡与目标灯泡状态,如不一样则做一次“开关”,一样则跳过继续比较,最终统计一共按开关的次数–枚举
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

const int N = 110;  //字符串长度最大为100(枚硬币),防止越界问题多开一部分数据
int n; //字符串长度
char start[N],aim[N];//存放初始状态与目标状态 数组

void turn(int i) //start[]为全局声明,所以不用传入start参数
{
    if(start[i] == '*') start[i] ='o';
    else start[i] = '*';
}

int main()
{
    cin>>start>>aim;
    n  = strlen(start);
    
    int res = 0;
    
    for(int i=0;i<n-1;i++)
    {
        if(start[i] != aim[i])    // 比较两个位置上的状态是否一致,不一致则翻转一次
        {
            turn(i),turn(i+1);
            res++;
        }
    }
    cout<<res<<endl;
    return 0;
}

116.飞行员兄弟(!看不懂!)
208.开关问题
在这里插入图片描述数据范围:共2^16种方案,每种方案有16个开关,每个开关操作7个开关
每次操作完判断16个开关的状态并记录,最多全亮/灭要记录16次
2^16*(16*7+16+16)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

using namespace std;

#define x first
#define y second

typedef pair<int ,int> PII;  //用pair存放二维数组 -- 简化代码

const int N = 5;

char g[N][N],backup[N][N];

// 映射 -- 返回第 x 行 y 列位置上的数
int get(int x,int y)
{
    return x * 4 + y;
}

// 改变灯泡状态
void turn_one(int x, int y)
{
    if(g[x][y] == '+')  g[x][y] = '-';
    else g[x][y] = '+';
}

void turn_all(int x, int y)
{
    //将在 x行 和 y列 位置上的灯泡状态改变 <--按(x,y)一次开关
    for(int i = 0; i < 4; i++)
    {
        turn_one(x,i);
        turn_one(i,y);  
    }
    turn_one(x,y);  //上述(x , y)位置开关被turn两次,这里恢复到1次
}

int main()
{
    for(int i = 0; i < 4; i++)
    {
        for(int j = 0; j < 4; j ++ )
        {
            cin >> g[i][j];
        }
    }
    
    vector<PII> res;        //记录方案所需要的结构
    
    //枚举出所有方案共2^16种
    for(int op = 0; op < 1 << 16; op ++ ) //1左移16位--2^16
    {
        for(int i = 0; i < 4; i ++ )
        {
            vector<PII> temp;  //用来存放当前位置上的方案
            //由于我们要对所有灯泡状态进行操作,这里先把状态备份一下--最后要进行还原
            memcpy(backup,g,sizeof g);
            
            // 进行操作
            for(int i = 0; i < 4; i ++)
            {
                for(int j = 0; j < 4; j ++)
                {
                    if(op>>get(i,j) & 1)    //如果当前位置是1,get作用就是返回二进制位中那一位是第几位-从而判断是否为1
                    {
                        temp.push_back({i,j});
                        //按一下开关
                        turn_all(i,j);
                    }
                }
            }
            
            //判断所有灯泡是否全亮
            bool has_closed = false;
            for(int i = 0; i < 4; i ++ )
            {
                for(int j = 0; j < 4; j ++)
                {
                    if(g[i][j] == '+')
                    {
                        has_closed = true;
                    }
                }
            }
            
            if( has_closed == false)
            {
                // 记录下方案
                if(res.empty() || res.size() > temp.size() )  res = temp;
                //若当前方案为空或记录到的步数严格>当前方案的操作步数
            }
            memcpy(g,backup,sizeof g);      //还原
        }
    }
    cout<<res.size() << endl;
    for(auto op : res) cout << op.x + 1 << " " << op.y + 1 << endl;
    return 0;
}
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值