面试题-全排列输出其所有的排列方式的两种解法

我丢下C语言有些时间了,上一次刷题是为了2017年秋季的PAT考试。趁着周末大好时光不能浪费,做点有意义的事情,回顾一下这道经典面试题。回顾的过程不是很顺利,上午去官网下CodeBlock,官网只有最新版本17.12,折腾了一个多小时mingw编译器就是没能配好。问小伙伴要了个13.12的版本,10分钟就配好了。

说到CodeBlock版本,我想起了大一新生那会我带了台很老旧的笔记本去学校,有多老呢,电脑的CPU是U7600,主频1.2GHZ。
那时工作室来宣传招新,我立马来了兴趣。进工作室要先刷题霸榜,学长推荐刷题用CodeBlock编译器,在学校OJ刷题,那时最新版本是13.12,我那电脑启动编译器需要近半分钟,编译一次要好几秒,可能是因为打字姿势不对,CodeBlock在我手上抽风得厉害,因此我坚持用了半年Microsoft Visual C++(真是太难了),直到换电脑。

面试题-全排列输出其所有的排列方式的两种解法_第1张图片

现在回头看,手上的电脑换了又换,CodeBlock也更新了好几版13.12、16.01、17.12,倒是这题目还是老题。这道题我以前肯定做过,说不定解法还发到了评论区并且底下还有几句学弟们的牛逼尖叫呢。可是我已经不记得以前的解法,只能从头再来敲一遍,这一遍要记录下来,既要放荡不羁也要稳如老狗。


这道题应该各OJ平台都有,我贴上来的图片是在leetcode上截取的。

面试题-全排列输出其所有的排列方式的两种解法_第2张图片

看到全排列问题,我首先想到这肯定有解,并且是有限解(这不是废话嘛,无限解让判题机咋判题)。

看到整整齐齐的输出结果,我闻到了用for循环打印的味道,边界在哪里呢,我感觉应该是找到未使用过的元素,有点像路径搜索类题目。

最后只剩下一个未使用元素,找到它就完成一次输出,因此某种情况下几乎可以确定会有结果,这又有了点数学归纳法的味道。
简单的1-2-3位全排列,我们可以脑补出来是什么结果,复杂的4位或以上的,通过化简成3-2-1位后也是一样的解法。


这道题就有了两个思路,一个是搜索,一个是数学归纳法(递归求解)。
这里的代码代码只是为了验证思路,没有按格式输入输出。


先说搜索的解法,
通过给每个元素做标记,我们可以知道这个元素是否使用过,
对于使用过的元素,我们跳过,没有使用过的元素,我们记下来,
并且记录这是迄今为止找到的第几个元素,进入下一阶搜索,如果已经筹够N个,
表示已完成一次全排列的搜寻。
完成一次全排列的搜寻后,需要按阶一步步后退,
后退时将用过的元素标记成未使用过,这样在下一阶求解可以用到这个元素。

 

#include 
struct Node{
    bool isUsed;
    int num;
};
struct Node arr[3];
void init();
int main(void) {
    init();
    for(int i = 0; i < 3; i++){
        if(!arr[i].isUsed){
            arr[i].isUsed = true;
            for(int j = 0; j < 3; j++){
                if(!arr[j].isUsed){
                    arr[j].isUsed = true;
                    for(int k = 0; k < 3; k++){
                        if(!arr[k].isUsed){
                            printf("%d",arr[i].num);
                            printf(" %d",arr[j].num);
                            printf(" %d\n",arr[k].num);
                        }
                    }
                    arr[j].isUsed = false;
                }

            }
        arr[i].isUsed = false;
        }
    }
    return 0;
}
void init(){
    arr[0].isUsed = false;
    arr[1].isUsed = false;
    arr[2].isUsed = false;
    arr[0].num = 1;
    arr[1].num = 2;
    arr[2].num = 3;
}

运行截图:

面试题-全排列输出其所有的排列方式的两种解法_第3张图片

再说说数学归纳法(递归)的解法,
通过将n阶问题简化成n-1阶,递归求解每次下降一阶,直至降到1阶段,得到答案。

在运算过程中,数组中的元素排列就是所求的答案,
按住第一位(arr[0]),对后面的元素求全排列。这里有2个解法。
交换第一位和第二位,对后面的元素求全排列。这里又有2个解法。
交换第一位和第三位,对后面的元素求全排列。这里还有2个解法,共计6种。


#include 
void swap(int *a, int *b);
int arr[] = {1, 2, 3};
void permutation(int index)//ееап
{
    int i;
    if(index > 2)
    {
    printf("%d,%d,%d\n", arr[0],arr[1],arr[2]);
    }
    else
    {
        for(i = index; i < 3; i++)
        {
            swap(&arr[index], &arr[i]);
            permutation(index + 1);
            swap(&arr[index], &arr[i]);
        }
    }
}
int main()
{
    permutation(0);
    return 0;
}
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}


运行截图:

面试题-全排列输出其所有的排列方式的两种解法_第4张图片
总结:搜索的解法解释起来难,但是编程起来简单,递归的解法解释起来简单,但是编程起来难。难在递归的关键是何时跳出递归,也就是递推公式要有出口,在本题中递归解法回溯时还有点逆向思维的味道。这两种解法效果上也不太相同,第一种是字典顺序输出,第二种不是。对比两种解法可以获得更深刻的理解,两种解法都不难,希望本文可以给各位小伙伴一点启发。

你可能感兴趣的:(路漫漫)