zoukankan      html  css  js  c++  java
  • 「Solution」1223330000组成只读一个零的十位数

    这道数学题是我偶然间发现的:

    1223330000组合成十位数,问:共有多少种组合方式可使组合出的十位数只读一个零.

    这题不需要用到什么高级的数学技巧,但是需要大量分类和枚举,于是,我想到了编程解决这一问题。

    语言上我选择的是Python,另外,我的编程环境为PyCharm

    接下来,我们进入正文部分。


    思路

    我的思路是这样的:

    遍历每一种组合情况,并对每一种情况进行检查,看是否符合题目中只读一个零的条件。

    那么,这个程序就可以被分为两个部分:
    1.遍历
    2.检查
    那么,我们开始逐步实现这一程序。

    第一部分:遍历

    我们需要用十个数字来组成十位数,这种情况下我们就要对每一个数位进行遍历,我们倒是可以用十个for循环嵌套在一起来实现,但是这显然会影响代码的可读性美观,所以我们选择另外一种算法——递归

    什么是递归

    如果对递归算法有所了解的,可以直接跳过这一部分。
    递归算法,用一句话来解释就是:

    递归:自己调用自己

    专业一点叫做:

    在函数的定义中使用函数自身的方法

    为了各位读者更容易理解递归,我们来举一个简单的例子:

    首先,定义一个数列,(a_1=0)(a_n=a_{n-1}+2(ngeq2))

    接下来,我们编程来实现,输入(n),求(a_n)的值。

    由于这是一个首项为0,公差为2的等差数列,我们可以这样写:

    n = int(input("Enter n:"))
    an = 2 * (n-1)
    print(an)
    

    当然,如果用到刚才讲到的递归,我们就可以这样写:

    def a(term):
        if term == 1:
            return 0
        elif term >= 2:
            a(term - 1)
    
    
    n = int(input("Enter n:"))
    print(a(n))
    

    这样写可能会有些复杂,但是这一程序很好的体现了递归算法,看到这里:

    def a(term):
        …
        elif term >= 2:
            a(term - 1)
    

    这就是前面所提到的,”自己调用自己“。

    那么,这一程序是如何运行的呢?
    手绘递归展示

    注意图中还标有两个词:”回推“和”递推“

    用我的方式来解释这两个词就是:

    回推:一层一层向下调用的过程

    递推:一层一层向上返回值的过程

    以上,就是我对递归算法一个简单的介绍,如果想对递归有更多、更深入的了解,可以前往知乎上查看其他大佬对递归的解释

    用递归来实现遍历

    运用递归,我们的实现方式就应该是这样:递归时,每回推一次,通过for循环遍历一个数位的可能情况,当所有数位确定完成时,进行下一部分。

    与此同时,我们就也确定出了我们的函数需要的两个参数:已确定的数位(numlist),还未使用的数字(unused)。

    当然,由于这个程序没有返回值,也就没有递推的过程了。

    结合以上的思路,我们的程序会是这样:

    def recu(numlist, unused):
        if len(numlist) < 10:
            # 长度小于十,说明还未组成十位数
            used = []
            for addnum in unused:
                snumlist = numlist[:]
                sunused = unused[:]
                snumlist.append(addnum)
                sunused.remove(addnum)
                if snumlist[0] != 0:
                    recu(snumlist, sunused)
        elif len(numlist) == 10:
            # 长度等于十,说明十位数已经组成,那么检查是否能够读零
            …
    

    当然,我们还需要注意一点,在题目给出的可选数字中,有大量的重复数字,如果我们的遍历部分就这样写,对于同一个数字组合方式,程序会计算(A^2_2 imes A^3_3 imes A^4_4=288)次,也就是说,程序给出答案的时长会多出整整288倍

    那么,我们要做的就是避免重复计算同一种情况,方法也很简单,每次遍历,检查addnum是否在列表used中,如果返回值为True,将addnum添加到列表used中。

    修改后的程序会是这样:

    def recu(numlist, unused):
        if len(numlist) < 10:
            # 长度小于十,说明还未组成十位数
            used = []
            for addnum in unused:
                if addnum not in used:
                    used.append(addnum)
                    snumlist = numlist[:]
                    sunused = unused[:]
                    snumlist.append(addnum)
                    sunused.remove(addnum)
                    if snumlist[0] != 0:
                        recu(snumlist, sunused)
        elif len(numlist) == 10:
            # 长度等于十,说明十位数已经组成,那么检查是否能够读零
            …
    

    第二部分:检查

    第二部分要做的工作是检查已组成的十位数是否符合”只读一个零“的条件。

    并不是每一个数中的零都会被读出,那么,我们来梳理一下读零的规则:

    • 处于每级末尾的零或由零组成的数串不会被读出「如:10 2300 3300」
    • 处于数中部全部由零组成的数级,且前后数级不全是零的,会读一个零「如:12 0000 2333」

    我们会发现,以上两条规则都是基于数级的,所以别的先抛开,我们要做的第一件事,就是将组成的十位数分级。

    实现方式不难,我们先准备一个二维列表splitnum,用于存放被分级的十位数。

    splitnum = [[], [], []]
    

    然后,for循环遍历numlist,将每一个数字存放在最前面一个尚未存满的数级中(存满,即数级中已有四个数字)。

    我的代码如下,**需要注意的是,我的这种方式分解出来的数字是倒序的,在之后的程序,不要忽略这一点,当然,你也可以使分解数字正续排列,可以遍历index,甚至可以这样写:splitnum = [[numlist[0], …], …]:

    for i in range(0, len(numlist)):
        for grade in splitnum:
            if len(grade) < 4:
                grade.append(numlist.pop())
                break
    

    结合读零规则的第一条,即”处于每级末尾的零或由零组成的数串不会被读出“,我们首先去掉处于每级末尾的零:

    for grade in splitnum:
        while True:
            if grade[0] == 0 and len(grade) > 0:
                del grade[0]
           else:
                break
    

    注意这里的if grade[0] == 0 and len(grade) > 0,在检测第一项为0的同时,也保证不把整个数级删空,这可以避免IndexError,同时,也因为整个数级均由零构成,可能会读零。

    做完以上工作后,剩下的处于数中的0数串就全部都会被读出了,所以,我们将三个数级连接起来,再检查里面有几个由零组成的数串即可:

    for grade in splitnum:
        # 将每一级连接起来
        for num in grade:
            check.append(num)
    while True:
        if check[0] == 0:
            del check[0]
        else:
            break
    # 将连在一起的零变为单独的一个零
    zero = False
    idx = 0
    oldcheck = check[:]
    for num in oldcheck:
        if num == 0:
            if zero:
                del check[idx]
                idx -= 1
            if not zero:
                zero = True
            else:
                zero = False
        idx += 1
        if check.count(0) == 1:
            result += 1
    

    完工

    至此,我们程序的主要部分就已全部编写完成了,加以整理,即可编写出完整的程序!

    最后附上带有注释的完整代码:

    # -*- coding: utf-8 -*-
    """
    题目:
    1223330000组合成十位数,问:共有多少种组合方式可使组合出的十位数只读一个零.
    """
    
    
    def recu(numlist, unused):
        if len(numlist) < 10:
            # 长度小于十,说明还未组成十位数
            used = []
            for addnum in unused:
                if addnum not in used:
                    used.append(addnum)
                    snumlist = numlist[:]
                    sunused = unused[:]
                    snumlist.append(addnum)
                    sunused.remove(addnum)
                    if snumlist[0] != 0:
                        recu(snumlist, sunused)
        elif len(numlist) == 10:
            # 长度等于十,说明十位数已经组成,那么检查是否能够读零
            print(numlist, end='    ')
            global result
            # 首先按级进行分割
            splitnum = [[], [], []]
            for i in range(0, len(numlist)):
                for grade in splitnum:
                    if len(grade) < 4:
                        grade.append(numlist.pop())
                        break
            # 对每一级进行检查,看是否读零
            check = []
            for grade in splitnum:
                # 末尾的零不会读出,所以去掉末尾的零
                while True:
                    if grade[0] == 0 and len(grade) > 1:
                        del grade[0]
                    else:
                        break
                # 将每一级连接起来
                for num in grade:
                    check.append(num)
            while True:
                if check[0] == 0:
                    del check[0]
                else:
                    break
            # 将连在一起的零变为单独的一个零
            zero = False
            idx = 0
            oldcheck = check[:]
            for num in oldcheck:
                if num == 0:
                    if zero:
                        del check[idx]
                        idx -= 1
                    if not zero:
                        zero = True
                else:
                    zero = False
                idx += 1
            print("check:", check, end="    ")
            if check.count(0) == 1:
                result += 1
                print("PASS")
            else:
                print("NOT PASS")
        else:
            print("Error")
    
    
    if __name__ == "__main__":
        alternative = [1, 2, 2, 3, 3, 3, 0, 0, 0, 0]
        result = 0
        recu([], alternative)
        print(result)
    

    运行结果:

    …
    [3, 0, 0, 0, 0, 3, 2, 3, 1, 2]    check: [2, 1, 3, 2, 3, 0, 3]    PASS
    [3, 0, 0, 0, 0, 3, 2, 3, 2, 1]    check: [1, 2, 3, 2, 3, 0, 3]    PASS
    [3, 0, 0, 0, 0, 3, 3, 1, 2, 2]    check: [2, 2, 1, 3, 3, 0, 3]    PASS
    [3, 0, 0, 0, 0, 3, 3, 2, 1, 2]    check: [2, 1, 2, 3, 3, 0, 3]    PASS
    [3, 0, 0, 0, 0, 3, 3, 2, 2, 1]    check: [1, 2, 2, 3, 3, 0, 3]    PASS
    2460
    
    Process finished with exit code 0
    

    所以,我们得出了这道题目的答案——2460。

  • 相关阅读:
    IntelliJ IDEA 编译方式介绍
    IntelliJ IDEA 缓存和索引介绍和清理方法
    无线鼠标没反应了
    L1-039 古风排版 (20 分)
    L1-043 阅览室 (20 分)
    数学思想
    输入
    2018年湘潭大学程序设计竞赛 E 吃货
    问题 1936: [蓝桥杯][算法提高VIP]最大乘积
    指针 链表
  • 原文地址:https://www.cnblogs.com/liuxizai/p/13765293.html
Copyright © 2011-2022 走看看