zoukankan      html  css  js  c++  java
  • 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!

  • 相关阅读:
    安装AD域时,出现NetBIOS名称冲突?
    SharePoint Server 2010 安装图解
    Visual C++ 2011519
    Visual C++ 2011520
    Visual C++ 2011518
    Visual C++ 2011520
    设计模式创建型 C++版本
    线程条件变量,一次性初始化
    Visual C++ 2011526
    Visual C++ 2011512
  • 原文地址:https://www.cnblogs.com/ram314/p/14059059.html
Copyright © 2011-2022 走看看