【问题标题】:For a given integer a, find all unique combinations of positive integers that sum up to a对于给定的整数 a,找出总和为 a 的所有正整数的唯一组合
【发布时间】:2014-07-25 15:24:29
【问题描述】:

不是作业问题。 我正在处理here 的问题,我遇到了这个问题。 已经有人回答了。我已经尝试了很多来理解所使用的递归,但我无法得到它。谁能给我解释一下?

编写一个函数,对于给定的数字,打印出所有不同的方法来制作这个数字,通过使用加法和任何等于或小于这个数字并且大于零的数字。

例如,给定a = 5,我们有以下七种方式组成5:

  • 1、1、1、1、1
  • 1, 4
  • 1、1、1、2
  • 1、1、3
  • 2, 3
  • 1、2、2
  • 5

该网站的解决方案是用 C++ 编写的:

void printSeq( int num , int a[] , int len , int s )
{
    if( num <= 0 )
    {
        for( int j = 0 ; j < len ; j++ )
            cout << a[ j ] << "," ;
        cout << endl;

        return;
    }

    for(int i = s ; i <= num ; i++)
    {
        a[ len ] = i;
        printSeq( num - i , a , len + 1 , i );
    }
}

int main()
{
    int a[5];
    printSeq(5,a,0,1);
    cin.get();
    return 0;
} 

【问题讨论】:

    标签: c++ recursion combinations permutation


    【解决方案1】:

    遇到此类问题时,最好从编辑器/IDE 中退后一步,在白板上画出一个简单的案例来思考问题。甚至不要做伪代码,只需画出一个流程图,说明这个问题的一个简单案例(例如a = 3)将如何一路龟缩。另外,一开始不要担心重复的组合。尝试找到一个可以为您提供所有所需组合的解决方案,然后改进您的解决方案以不给您重复。在这种情况下,为什么不看看a = 3 的可管理案例呢?让我为你画一个小图。绿色复选标记表示我们得到了一个有效的组合,红色叉号表示一个组合无效。

    如您所见,我们从三个空子组合开始,然后通过为每个子组合附加一个数字来构建三个新的子组合。我们要检查所有可能的路径,因此我们选择 1、2 和 3,最终得到 [1][2][3]。如果组合中的数字之和等于 3,则我们找到了有效组合,因此我们可以停下来检查这条路径。如果组合中的数字之和超过3,则组合无效,我们也可以停止。如果两者都不是,我们只是继续构建组合,直到我们得出一个有效或无效的解决方案。

    由于您的问题似乎主要是关于如何为此类问题制定递归解决方案,而不是关于特定语法的问题,而您恰好找到了 C++ 解决方案,我将在 Python 中提供解决方案(看起来几乎就像伪代码,它就是它所知道的)。

    def getcombs(a, combo = None):
        # initialize combo on first call of the function
        if combo == None:
            combo = []
    
        combosum = sum(combo) # sum of numbers in the combo, note that sum([]) == 0
        # simple case: we have a valid combination of numbers, i.e. combosum == a
        if combosum == a:
            yield combo # this simply gives us that combination, no recursion here!
        # recursive case: the combination of numbers does not sum to a (yet)
        else:
            for number in range(1, a + 1): # try each number from 1 to a               
                if combosum + number <= a:  # only proceed if we don't exceed a
                    extcombo = combo + [number] # append the number to the combo
                    # give me all valid combinations c that can be built from extcombo
                    for c in getcombs(a, extcombo):
                        yield c
    

    让我们测试一下代码吧!

    >>> combos = getcombs(3)
    >>> for combo in combos: print(combo)
    ... 
    [1, 1, 1]
    [1, 2]
    [2, 1]
    [3]
    

    这似乎工作正常,a = 5 的另一个测试:

    >>> combos = getcombs(5)
    >>> for combo in combos: print(combo)
    ... 
    [1, 1, 1, 1, 1]
    [1, 1, 1, 2]
    [1, 1, 2, 1]
    [1, 1, 3]
    [1, 2, 1, 1]
    [1, 2, 2]
    [1, 3, 1]
    [1, 4]
    [2, 1, 1, 1]
    [2, 1, 2]
    [2, 2, 1]
    [2, 3]
    [3, 1, 1]
    [3, 2]
    [4, 1]
    [5]
    

    解决方案包括我们正在寻找的所有七种组合,但代码仍然会产生重复项。您可能已经注意到,没有必要取一个小于之前选择的数字的数字来生成所有组合。因此,让我们添加一些代码,这些代码仅开始为不小于组合中当前最后一个数字的数字构建extcombo。如果组合为空,我们只需将前面的数字设置为 1。

    def getcombs(a, combo = None):
        # initialize combo on first call of the function
        if combo == None:
            combo = []
    
        combosum = sum(combo) # sum of numbers in combo, note that sum([]) == 0
        # simple case: we have a valid combination of numbers, i.e. combosum == a
        if combosum == a:
            yield combo # this simply gives us that combination, no recursion here!
        # recursive case: the combination of numbers does not sum to a (yet)
        else:
            lastnumber = combo[-1] if combo else 1 # last number appended
            for number in range(lastnumber, a + 1): # try each number between lastnumber and a
                if combosum + number <= a:
                    extcombo = combo + [number] # append the number to the combo
                    # give me all valid combinations that can be built from extcombo
                    for c in getcombs(a, extcombo):
                        yield c
    

    再一次,让我们测试一下代码!

    >>> combo = getcombs(5)
    >>> for combo in combos: print(combo)
    ... 
    [1, 1, 1, 1, 1]
    [1, 1, 1, 2]
    [1, 1, 3]
    [1, 2, 2]
    [1, 4]
    [2, 3]
    [5]
    

    提出的解决方案可能不是现有的最有效的解决方案,但希望它会鼓励您进行递归思考。逐步分解问题,为少量输入绘制一个简单案例,一次解决一个问题。

    【讨论】:

      【解决方案2】:

      暂时搁置解决方案,看看问题本身:

      将此问题与数组(或任何递归算法)的插入排序进行比较。在执行期间的任何时候,在插入排序中,我们有一部分数组是已排序的,另一部分是未排序的。我们从未排序的部分中挑选一个元素,并找到它在排序部分的位置,从而扩展排序部分,使问题更小。

      在这个问题的情况下,我们可以从整数 1 到问题中的数字(我们称之为 N)中选择固定数量的元素,作为总和为 N 的序列的一部分。

      在任何时候我们都收集了一些总和小于 N(比如 X)的数字,将问题减少到 NX 大小,也将我们的选择从 1..N 减少到 1..(NX) 以进行下一次递归.

      解决方案很明显,从 1 到 (N-X) 进行每个选择,并递归进行直到 X=N。每次算法达到 X=N,意味着找到一个排列。

      注意:我看到的解决方案的一个问题是它需要事先知道将要找到的排列数。 int a[5]; 如果该值未知,这可能会导致问题。

      【讨论】:

      • 解决方案的其他问题在if( num &lt;= 0 ) 语句中。它应该是if( num == 0 ),因为这是意味着算法停止的实际测试。它也适用于&lt;=,因为调用确保num 永远不会低于0,但从逻辑上讲它是错误的和误导性的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-21
      • 1970-01-01
      • 1970-01-01
      • 2010-12-02
      • 2019-10-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多