从一个集合[0,1,...,K]中取出P个数,组成一个排列(数字可以重复取),使它们的和为K,求所有可能的排列数?

1.非递归方式:

# -*- coding: utf-8 -*-
# @Time    : 2020/9/24 17:16
# @Author  : wmy
"""
问题描述: 从一个集合[0,1,...,K]中取出P个数,组成一个排列(数字可以重复取),使它们的和为K,求所有可能的排列数?
解决方法:
    step1:条件约束下的有放回采样,计算包含P个数的集合且集合之和为K的所有组合
    step2:针对每个可能包含重复元素的组合,计算其全排列数,然后累加
examples:
    - K=5
    - N=3
    组合:[0,0,5],[0,1,4],[0,2,3],[1,1,3],[1,2,2]
    排列:[0,0,5] - 3
        [0,1,4] - 6
        [0,2,3] - 6
        [1,1,3] - 3
        [1,2,2] - 3
    结果: 3+6+6+3+3 = 21
"""
import math


class PermutationQuantity:
    def __init__(self, K, P):
        self.quantity = 0
        self.K = K
        self.P = P

    def combinations(self):
        """
        计算包含P个数的集合且集合之和为K的所有组合
        :return:
        """
        if not self.K and self.P:
            return
        indices = [0] * self.P
        while True:
            # for ...else :当迭代对象完成所有迭代后且此时的迭代对象为空时,如果存在else子句则执行else子句,没有则继续执行后续代码;
            # 如果迭代对象因为某种原因(如带有break关键字)提前退出迭代,则else子句不会被执行,程序将会直接跳过else子句继续执行后续代码
            for i in reversed(range(self.P)):
                if indices[i] != self.K:
                    indices[i:] = [indices[i] + 1] * (self.P - i)
                    break
                # if sum(indices) == self.K:
            else:
                return
            if sum(indices) == self.K:
                self.quantity += self.permutations(indices)

    def permutations(self, s):
        """
        计算包含重复元素的集合的排列数
        :param s:
        """
        num_dict = dict()
        for e in s:
            if e in num_dict.keys():
                num_dict[e] += 1
            else:
                num_dict[e] = 1
        n = math.factorial(len(s))
        for key, value in num_dict.items():
            n /= math.factorial(value)
        return n

    def run(self):
        self.combinations()
        return int(self.quantity)


if __name__ == '__main__':
    K = 5
    P = 4
    pq = PermutationQuantity(K, P)
    quantity = pq.run()
    print(quantity)

2.递归方式

# -*- coding: utf-8 -*-
# @Time    : 2020/9/20 01:16
# @Author  : wmy
"""
问题描述: 从一个集合[0,1,...,K]中取出P个数,组成一个排列(数字可以重复取),使它们的和为K,求所有可能的排列数?
解决方法:
    step1:条件约束下的有放回采样,计算包含P个数的集合且集合之和为K的所有组合
    step2:针对每个可能包含重复元素的组合,计算其全排列数,然后累加
examples:
    - K=5
    - N=3
    组合:[0,0,5],[0,1,4],[0,2,3],[1,1,3],[1,2,2]
    排列:[0,0,5] - 3
        [0,1,4] - 6
        [0,2,3] - 6
        [1,1,3] - 3
        [1,2,2] - 3
    结果: 3+6+6+3+3 = 21
"""
import math


class Quantity:
    def __init__(self, M, N):
        self.quantity = 0
        self.M = M
        self.N = N
        self.comb_list = list()

    def calc_quantity(self, m, n, sum, M, N, li):
        if m > M:
            return
        if n + 1 == N:
            if M - sum >= m:
                li[n] = M - sum
                self.quantity += self.calc_permutation_quantity(li)
            return
        if m + 1 + sum > M:
            return
        self.calc_quantity(m + 1, n, sum, M, N, li)
        if (m << 1) + sum > M:
            return
        li[n] = m
        self.calc_quantity(m, n + 1, sum + m, M, N, li)

    def calc_permutation_quantity(self, li):
        """
        计算包含重复元素的集合的排列数
        :param li:
        """
        num_dict = dict()
        for e in li:
            if e in num_dict.keys():
                num_dict[e] += 1
            else:
                num_dict[e] = 1
        n = math.factorial(len(li))
        for key, value in num_dict.items():
            n /= math.factorial(value)
        return n

    def run(self):
        li = [0 for i in range(self.N)]
        self.calc_quantity(0, 0, 0, self.M, self.N, li)
        return int(self.quantity)


if __name__ == '__main__':
    M = 100
    N = 10
    pq = Quantity(M, N)
    print(pq.run())

Guess you like

Origin blog.csdn.net/qq_36940806/article/details/108813930