【ACwing】一、基础算法:1.1 递归枚举(指数型)


周更打卡ACwing!可是感觉好难啊,要看好久才能懂,加油!

92. 递归实现指数型枚举

在这里插入图片描述
首先使用数组 x[i] 标记当前数i是否被选择过,x[i]=1表示被放入输出序列,x[i]=0则不被放入。当我们对所有的数进行了选择后(即current>n),根据x[i]就可以决定输出最终的序列。

那么从x[1]开始,尝试所有可能的组合,直观表示如下(此部分涉及dfs):
在这里插入图片描述
可以将过程进行拆解,每个数都有两种情况x[i]=1或者x[i]=1,在确定当前的数是否会被放入所选序列后,就要选择下一个应该被放入序列的数,所以执行dfs(current+1)。值得注意的是要确定递归结束的条件,也就是所选择的数达到最大限制n。所以得到如下代码:

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int x[20];//标记当前是否被选择过
int n;
void dfs(int current)
{
    if(current>n)//到达叶子结点输出结果
    {
        for(int i=1;i<=n;i++)
            if(x[i])
            cout<<i<<' ';
        cout<<endl;
        return ;
    }
    x[current]=1;
    dfs(current+1);

    x[current]=0;
    dfs(current+1);
}
int main()
{
    cin>>n;
    dfs(1);
    return 0;
}

输出:

3
1 2 3
1 2
1 3
1
2 3
2
3

1572. 递归实现指数型枚举 II

在这里插入图片描述
看似和上一个题目类似,实则不然,上一个题的前提是不重复的待选数字1~n,所以可以直接使用数字i表示待选数字,这里出现了重复的数字则需使用另外一个数组a来对待选数字进行存储。另一个问题是当两个待选数字相同时,第一个题的方法就不适用了,如果两个数相同则在上一题会被当做不同的数来处理使得答案重复,如下:
在这里插入图片描述
那么应该怎么办呢?

解法一:

先对输入进行排序使得重复的元素相邻,若进行选择时前后两元素相同(以a[1]=a[2]=2为例),需要保证第一次选择的是2是a[1],这样保证结果出现 2 2 时前一个是a[1]后一个是a[2],这样相同数字的相邻组合只会被选择一次。这种方法可以总结为一个规则:前后两数相同时:若上一个未被选则回退到上一步,使其标记为1后当前才可以被选;若上一个被选则当前才有可能被选。这么做就能保证了两数重复的顺序只出现了一次,见下图可发现该规律,那么如何实现呢?
在这里插入图片描述
前后两数相同时:若上一个未被选则回退到上一步,使其标记为1后当前才可以被选;若上一个被选则当前才有可能被选。这句话的前提是上一步未被选,所以应先使所有的数的x[i]=0(也就是都不选),这样才能从中找到回退的条件,再添加回退条件,再尝试将x[i]赋为1。

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int x[20],a[20];//标记当前是否被选择过
int n;
void dfs(int c)
{
    if(c > n)//到达叶子结点输出结果
    {
        for(int i=1; i<=n; i++)
            if(x[i])
                cout<<a[i]<<' ';
        cout<<endl;
        return ;
    }
    x[c]=0;
    dfs(c+1);
    if(c>1&&a[c - 1] == a[c]&&x[c-1]==0)
        return;
    x[c]=1;
    dfs(c + 1);
}
int main()
{
    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>a[i];
    sort(a+1,a+n);
    dfs(1);
    return 0;
}
/*
输入:

4
1 2 2 2
*/

输出:


2
2 2
2 2 2
1
1 2
1 2 2
1 2 2 2

解法二:

同理也是要控制两个相同数字的出现顺序,但是此处换个思路,上一题使用标记数组x[i]来判断上一个数字是否被选择。而此处无需标记数组,直接使用num储存我们最终选中的序列。那么我们可以把选择过程中的每一步来作为输出的结果。

可以使用用动态数组num存储不同长度、不同顺序的排列结果,所以每次dfs后都要进行一次输出,不必判断是否到达叶子结点,好处是动态数组是栈的存储结构不必考虑下标,同时值得注意的是该数组的起始位置i=0,与之前不同。

以输入数据1 2 2为例,对于最终排列的每一个位置(即num[i])来说都需要考虑每个数字(即a[i])是否被选择,所以使用for(int i = u; i < n; i++)来遍历数组a,两数字相同表示为a[i - 1] == a[i]i != u表示该数在与前一个数相同且不是出现重复序列的第一个,那么此时应该跳过后续对该种情况的分支选择。(即continue——跳过当前的for循环选择下一个数)。

代码过程的图示如下:
在这里插入图片描述

#include <iostream>
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
int a[N];
vector<int> num;
int n;
void dfs(int u)
{
    for(int i = 0; i < num.size(); i++)
        cout << num[i] << " ";
    cout<<endl;


    for(int i = u; i < n; i++)
    {
        if(i != u && a[i - 1] == a[i])
            continue;

        num.push_back(a[i]);
        dfs(i + 1);
        num.pop_back();//回溯
    }
    return;

}
int main()
{
    cin>>n;
    for(int i=0; i<n; i++)
        cin>>a[i];

    sort(a,a+n);
    dfs(0);
    return 0;
}
/*
输入:

3
1 2 2
*/

输出:


1
1 2
1 2 2
2
2 2
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值