201129_关于算法练习题「排列 permutation」的个人独特解法

【声明】

本文意在基于我的初始解法进行最浅显的解释及优化。

【题目信息】

算法竞赛入门经典(第 2 版)/ 刘汝佳 p35

习题 2-6 排列 (permutation)

用 1, 2, 3, ..., 9 组成 3 个三位数 abc, def 和 ghi,每个数字恰好使用一次,要求 abc: def: ghi = 1: 2: 3。后略。

【写作缘起】

一看这题目首先想到暴力解法,但 9 重循环实在是不想去用,故在网上搜索了一下大家的解法。

受到了一些启发,但当我解决问题回头对比网上方案时,发现要么重复一个字符串数组解法,要么是一些暴力解法(不代表全部结果,仅浏览了几个结果)。

我觉得我的解法与他们的不同,值得给大家看一看,目前的初始版本没使用任何循环以上的知识,入门一小时的新人也能看懂吧,大概。

【题目分析】

普通的递归解法与“九层妖塔”解法在我看来没什么区别。但一种利用字符串的解法启发了我(好吧其实是“字符串”这三个字启发了我,让我想起以前做的「数字翻转」),有思路就上手。

仔细看了下题目,发现数字是有规律的,粗略分析知道极值是「987」及「123」,由于三个数成倍数关系,故 abc 的范围在 [123, 333) 之中(为什么不是「329」?因为要花更多时间算出来,麻烦),另外两个数分别是 abc 的两倍和三倍。这样就确定了循环范围了,一共只有 200 多个可能的值。

基本的思路是,把统计每个数字出现的次数,可以用数组,但我决定直接用一个 9 位的数字就好了,每位代表一个数,结果是 9 个 1 就表示每个数字只用了一次。

既然别人把它当字符来玩,那目的「对每位字符进行验证」就可以变成「如何取出每位数字再进行处理」,取数字简单,模运算大法可解之。

     123 % 10 = 3
123 / 10 % 10 = 2
    123 / 100 = 1

同理,将三个数字的各位取出来就好了,再逐个验证。

最初的想法是 3 个数字分别取出来,分别取了分别验证,验证就用 switch 结构。但受到统计数字的记性,直接合并成一个大数字简洁多了,至于效率,是优化时候的事。

n = a * 1000000 + a * 2000 + a * 3;

这样,这个 n 就是合并后的数了,再把各位取出来并进行判断就好了。取了各位也是要一个个的统计的,脑中萌生了一个问题「取数的循环与逐一统计的循环能否合并呢?」,可以的,变成每次都取个位就好了,说白了就是取完数就除 10 一下,最后这个数会小到没有(归零,准确说是归 1, 因为最小是 1 开头)。

while(n > 0){
  int t = n % 10;
   //统计 t 是哪个数
  n /= 10;
}  

然后就是统计了,用 switch 还是 if else 还是一堆 if 都没差,我就用一堆 if 好了,一家人就要整整琪琪(误)。

 1 while(n > 0){
 2    int t = n % 10;
 3    if(n % 10 == 0) break;
 4    if(n % 10 == 1) flag += 1;
 5    if(n % 10 == 2) flag += 10;
 6    if(n % 10 == 3) flag += 100;
 7    if(n % 10 == 4) flag += 1000;
 8    if(n % 10 == 5) flag += 10000;
 9    if(n % 10 == 6) flag += 100000;
10    if(n % 10 == 7) flag += 1000000;
11    if(n % 10 == 8) flag += 10000000;
12    if(n % 10 == 9) flag += 100000000;
13    n /= 10;
14 }

这样一通下来,就能统计到这两百多个 9 位数的数字分布了,每次生成的 flag 如果等于 111111111(9 个 1) 就表示刚好用了一次。另外有个要先处理的问题,查到 0 要直接 out,因为目标数字不由 0 参与组成。

再来是 n 在这个过程中被除掉了,所以事先就要保存起来,后面输出要用。

至此,解法 v1.0 的思路就是这些了,还是很不完善,留下了很多可改进的地方,就以后再说啦。

【解法 v1.0】

完整可运行代码如下,上述的代码都是说明性的,有些不完整。

#include<stdio.h>

int main(){
    for(int a = 123; a < 333; a ++){
        int flag = 0, n, m;
        m = n = a * 1000000 + a * 2000 + a * 3;
        while(n > 0){
            int t = n % 10;
            if(n % 10 == 0) break;
            if(n % 10 == 1) flag += 1;
            if(n % 10 == 2) flag += 10;
            if(n % 10 == 3) flag += 100;
            if(n % 10 == 4) flag += 1000;
            if(n % 10 == 5) flag += 10000;
            if(n % 10 == 6) flag += 100000;
            if(n % 10 == 7) flag += 1000000;
            if(n % 10 == 8) flag += 10000000;
            if(n % 10 == 9) flag += 100000000;
            n /= 10;
        }
        if(flag == 111111111){
            printf("%d
", m);
        }
    }
    return 0;
}

【余留问题】

1. 尝试不合并数字,循环 3 次把数字各位取出来,用其它方法统计?

2. 尝试用数组(非字符数组)来存放这些数字,然后通过其它方法统计?

3. 各位数字的和是固定的,但相同数字也会产生这个和,是否值得探索?

4. 真正的最小循环范围是多少?起码不是 [123, 333)。

5. 那个字符解法是怎么解的?能否用到数字数组模式中去?

6. 这个题目的在哪个 OJ 中有?希望验证一下效率。

7. 在每次统计前查看是否已有?这样用数组或二进制即可。

【其它的话】

不 DIY 的话,默认界面实在不太方便,连等宽字体都不提供,早晚要改掉。

编辑完发发现可以用 MD 来写,诶,MD!

原文地址:https://www.cnblogs.com/ram314/p/14059059.html