蓝桥杯凑平方数(dfs-组合)

118 篇文章 22 订阅
8 篇文章 0 订阅

1. 问题描述:

把0~9这10个数字,分成多个组,每个组恰好是一个平方数,这是能够办到的。 比如:0, 36, 5948721 
再比如: 
1098524736 
1, 25, 6390784 
0, 4, 289, 15376等等... 
注意,0可以作为独立的数字,但不能作为多位数字的开始。 分组时,必须用完所有的数字,不能重复,不能遗漏。 如果不计较小组内数据的先后顺序,请问有多少种不同的分组方案? 

输出
输出一个整数表示答案 

来源:http://oj.ecustacm.cn/problem.php?id=1307

2. 思路分析:

① 一开始想到的是生成0~9这十个数字的全排列然后对每一个生成的排列进行切分看是否使得每一组都是一个平方数,这里存在一个很麻烦的操作是处理数字0的操作,因为题目要求数字0是不可以作为多位数字的开始,所以这是一个需要处理的细节,而且对每一个生成的排列都是这样分割导致计算耗时很大,并且题目要求是不计较小组内数据的先后顺序,而全排列是对于不同顺序的排列都是算做两种的又是一个需要处理的细节,所以整体感觉还是很难处理,后面想到另外一个解决的办法:因为每一个数字都是平方数字而且最大的数字是987654321,所以我们可以生成这个范围内的所有的平方数,这样可以避免数字0作为多位数字开始的问题。并且在生成平方数字的时候需要判断当前的数字中的位是否存在重复的数字,如果出现了重复的数字那么这个平方数是不符合题目要求的,我们在生成平方数字的时候可以使用一个方法isOne来判断是否当前数字各个位的数字是否存在重复,具体的操作是:将当前的平方数字转为字符串,并且将字符串加入到set集合中判断set集合的长度是否与原来字符串的长度想到,如果相等说明不存在重复的元素,当前的平方数字是符合题目要求的。

② 在①步中我们可以将[0, 9876543210]范围的平方数字存储到一个一维列表nums中,因为是不计较小组内数据的先后顺序所以当前这道题目是一个组合问题,其实表达的意思是当前我有这么多个数字存储在nums中(实际上存储的是平方数字的字符串形式这样好计算序列长度),需要做的是在nums中的这些数字中选择出x个数字使得选出x个数字的长度满足10(这个长度是经过set集合之后的长度:需要满足题目中0-9的数字只能出现一次的条件)。计算组合方案可以通过递归解决,通过for循环中递归选择出x个数字。使用递归计算组合数目也是固定的套路,在递归的方法传递当前递归的位置,在for循环中递归表示的是可以选择当前的位置的元素也可以不选择当前的元素,当遍历到当前的位置的时候表示选择当前这个位置的元素,当返回到这一层进入for循环的下一个元素的时候表示不选择这个元素,我在之前也写过一篇生成组合方案的例子。下面表示的是for循环中递归的过程,主要是for循环递归的范围与下一次递归的位置

③ 我们在往下递归之前需要进行减枝(下图中的if判断与return语句都属于减枝),没有经过减枝递归下去计算最终的结果会有点久。经过下面的减枝之后明显很快就计算出结果。先判断之前已经生成序列的长度加上当前的递归位置对应的平方数字的长度是否小于等于10,如果满足才递归下去,如果发现大于10这个时候一个比较好的减枝方法是直接return到上一层,而不是continue(这样可以避免for循环中尝试当前位置后面的平方数字),因为我们知道当前位置i的平方数字的长度加上原来序列的长度已经超出了10那么i位置后面的数字更加不用尝试了(平方数字在生成的时候都是从小到大的顺序的),返回到上一层尝试其他的数字即可。通过这个减枝那么计算结果就很快了,所以有的时候在递归的时候需要多想想怎么样减枝才能够避免计算哪些没有用的递归方案

④ 在递归方法中可以传递一个列表来记录中间递归生成的结果这样当到达了递归出口之后可以输出列表中记录的结果验证是否正确。而且我们还可以将结果通过输出语句记录到文件中更方便查看,最终的答案是300。其实这道题目的本质是计算组合的数目,根据递归计算组合方案的套路即可,其中需要根据题目的要求注意细节,能够减枝就尽量减枝,减少耗时。

3. 代码如下:

from typing import List
res = 0


# 判断当前的平方数字中的位是否存在重复
def isOne(n: int):
    s = str(n)
    return len(set(s)) == len(s)

# index表示当前递归的位置, curlen表示当前的序列长度, rec记录生成的序列结果
def dfs(nums: List[str], index: int, curlen: int, rec: List[str]):
    global res, file
    if curlen == 10:
        # 如果生成的序列没有重复的数字说明满足条件
        if len(set("".join(rec))) == 10:
            print(rec)
            res += 1
    for i in range(index, len(nums)):
        # 当前序列长度+当前的平方数字的长度小于等于10才递归否则return到上一层
        if curlen + len(nums[i]) <= 10:
            rec.append(nums[i])
            dfs(nums, i + 1, curlen + len(nums[i]), rec)
            # 回溯
            rec.pop()
        else: return


if __name__ == '__main__':
    i = 0
    nums = list()
    # 生成平方数字的位没有重复的数字添加到列表中(转为字符串后面计算组合数的时候会比较好计算序列长度)
    while i * i < 9876543210:
        n = str(i * i)
        if isOne(n):
            nums.append(n)
        i += 1
    rec = list()
    dfs(nums, 0, 0, rec)
    print(res)

将结果通过print语句输出到文件中方便查看:

from typing import List
res = 0
file = open("data.txt", "w")


# 判断当前的平方数字中的位是否存在重复的数字
def isOne(n: int):
    s = str(n)
    return len(set(s)) == len(s)


def dfs(nums: List[str], index: int, curlen: int, rec: List[str]):
    global res, file
    if curlen == 10:
        if len(set("".join(rec))) == 10:
            print(rec, file=file)
            res += 1
    for i in range(index, len(nums)):
        if curlen + len(nums[i]) <= 10:
            rec.append(nums[i])
            dfs(nums, i + 1, curlen + len(nums[i]), rec)
            rec.pop()
        else: return


if __name__ == '__main__':
    # 思路是先生成这些平方数字然后在平方数字选出若干个数字
    i = 0
    nums = list()
    while i * i < 9876543210:
        n = str(i * i)
        if isOne(n):
            nums.append(n)
        i += 1
    rec = list()
    dfs(nums, 0, 0, rec)
    print(res)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值