【问题标题】:Given an array, find combinations of n numbers that are less than c给定一个数组,找出小于 c 的 n 个数的组合
【发布时间】:2016-03-12 09:42:51
【问题描述】:

这很难,至少对于我的最低 c 技能而言。

基本上,用户将价格列表输入到数组中,然后输入他想要购买的商品数量,最后是不超过的最大成本。

我需要检查所需数量的项目有多少组合小于或等于给定的成本。

如果问题是组合中的项目数量固定,比如 3 个,那么只需三个循环选择每个价格并将它们添加到测试中就会容易得多。

我被难住的地方是要求用户输入任意数量的项目,最多为数组中的项目数。

这是我最初决定的,后来我意识到用户可以指定任意数字的组合,而不仅仅是三个。它是在此处类似主题的帮助下创建的,但同样,它仅在用户指定他想要每个组合 3 个项目时才有效。否则它不起作用。

// test if any combinations of items can be made
  for (one = 0; one < (count-2); one++) // count -2 to account for the two other variables
  {
    for (two = one + 1; two < (count-1); two++) // count -1 to account for the last variable
    {
      for (three = two + 1; three < count; three++)
      {
        total = itemCosts[one] + itemCosts[two] + itemCosts[three];
        if (total <= funds)
        {
          // DEBUG printf("\nMatch found! %d + %d + %d, total: %d.", itemCosts[one], itemCosts[two], itemCosts[three], total);
          combos++;
        }
      }
    }
  }

据我所知,没有简单的方法可以根据用户希望的每个组合的项目数量来灵活调整它。

如果能提供任何帮助,我将不胜感激。

【问题讨论】:

    标签: c arrays loops


    【解决方案1】:

    扁平化嵌套迭代的一个技巧是使用递归。

    创建一个函数,该函数接受您目前选择的项目数组,以及到目前为止您已拾取的项目数。算法应该是这样的:

    • 如果您选择的项目数量等于您的目标 N,请计算总和并根据限制检查它
    • 如果您没有选择足够的项目,请在列表中再添加一项,然后进行递归调用。

    为确保您不会选择两次相同的项目,请传递函数可能从中选择的最小索引。函数的声明可能如下所示:

    int count_combinations(
        int itemCosts[]
    ,   size_t costCount
    ,   int pickedItems[]
    ,   size_t pickedCount
    ,   size_t pickedTargetCount
    ,   size_t minIndex
    ,   int funds
    ) {
        if (pickedCount == pickedTargetCount) {
            // This is the base case. It has the code similar to
            // the "if" statement from your code, but the number of items
            // is not fixed.
            int sum = 0;
            for (size_t i = 0 ; i != pickedCount ; i++) {
                sum += pickedItems[i];
            }
            // The following line will return 0 or 1,
            // depending on the result of the comparison.
            return sum <= funds;
        } else {
            // This is the recursive case. It is similar to one of your "for"
            // loops, but instead of setting "one", "two", or "three"
            // it sets pickedItems[0], pickedItems[1], etc.
            int res = 0;
            for (size_t i = minIndex ; i != costCount ; i++) {
                pickedItems[pickedCount] = itemCosts[i];
                res += count_combinations(
                    itemCosts
                ,   costCount
                ,   pickedItems
                ,   pickedCount+1
                ,   pickedTargetCount
                ,   i+1
                ,   funds
                );
            }
            return res;
        }
    }
    

    你这样调用这个函数:

    int itemCosts[C] = {...}; // The costs
    int pickedItems[N]; // No need to initialize this array
    int res = count_combinations(itemCosts, C, pickedItems, 0, N, 0, funds);
    

    Demo.

    【讨论】:

    • 那么这个函数会返回数组中指定大小的所有可能组合吗?此外,您将函数声明为 void,但随后继续从中返回值。
    • 好的,我看它没有。我假设您在另一个循环中运行此函数,该循环递增起始索引并将函数返回的 1 或 0 添加到原始组合变量中。
    • @Patrick 我修复了几个小错误,并添加了一个演示。请看一下链接。
    【解决方案2】:

    这可以通过使用 backtracking 算法来完成。这相当于实现了一个嵌套for loops 的列表。通过尝试查看一系列嵌套 for 循环的执行模式,可以更好地理解这一点。

    例如,假设您拥有 3 个fors 的序列,并且代码执行已达到第三级(最内层)。在完成所有迭代之后,您将返回到第二级for,在那里您将进入下一个迭代,在该迭代中您再次跳转到第三级for。同样,当第二级完成所有迭代时,您会跳回第一级for,这会继续进行下一次迭代,您将在第二级跳转到第三级。

    因此,在给定的关卡中,您尝试进入更深的关卡(如果有的话),如果没有更多的迭代,则返回关卡(回溯)。

    使用回溯,您可以通过一个数组来表示嵌套的for,其中每个元素都是一个索引变量:array[0] 是for 0 级的索引,依此类推。

    以下是针对您的问题的示例实现:

    #define NUMBER_OF_OBJECTS  10
    #define FORLOOP_DEPTH       4 // This is equivalent with the number of
                                  // of nested fors and in the problem is
                                  // the number of requested objects
    #define FORLOOP_ARRAY_INIT -1 // This is a init value for each "forloop" variable
    #define true  1
    #define false 0
    typedef int bool;
    int main(void)
    {
        int object_prices[NUMBER_OF_OBJECTS];
        int forLoopsArray[FORLOOP_DEPTH];
        bool isLoopVariableValueUsed[NUMBER_OF_OBJECTS];
        int forLoopLevel = 0;
    
        for (int i = 0; i < FORLOOP_DEPTH; i++)
        {
            forLoopsArray[i] = FORLOOP_ARRAY_INIT;
        }
        for (int i = 0; i < NUMBER_OF_OBJECTS; i++)
        {
            isLoopVariableValueUsed[i] = false;
        }
    
    
        forLoopLevel = 0; // Start from level zero
        while (forLoopLevel >= 0)
        {
            bool isOkVal = false;
    
            if (forLoopsArray[forLoopLevel] != FORLOOP_ARRAY_INIT)
            {
                // We'll mark the loopvariable value from the last iterration unused 
                // since we'll use a new one (in this iterration)
                isLoopVariableValueUsed[forLoopsArray[forLoopLevel]] = false;
            }
    
            /* All iterations (in all levels) start basically from zero
             * Because of that here I check that the loop variable for this level
             * is different than the previous ones or try the next value otherwise
            */
            while (    isOkVal == false
                    && forLoopsArray[forLoopLevel] < (NUMBER_OF_OBJECTS - 1))
            {
                forLoopsArray[forLoopLevel]++; // Try a new value
                if (loopVariableValueUsed[forLoopsArray[forLoopLevel]] == false)
                {
                    objectUsed[forLoopsArray[forLoopLevel]] = true;
                    isOkVal = true;
                }
            }
    
            if (isOkVal == true) // Have we found in this level an different item?
            {
                if (forLoopLevel == FORLOOP_DEPTH - 1) // Is it the innermost?
                {
                    /* Here is the innermost level where you can test
                     * if the sum of all selected items is smaller than
                     * the target
                     */
                }
                else // Nope, go a level deeper
                {
                    forLoopLevel++;
                }
            }
            else // We've run out of values in this level, go back
            {
                forLoopsArray[forLoopLevel] = FORLOOP_ARRAY_INIT;
                forLoopLevel--;
            }
        }
    }
    

    【讨论】:

    • 我现在正在阅读 Wikipedia 文章,回家后我会深入研究代码。这似乎与它的本质有关。这堂课才刚刚开始讲数组,而我们才刚刚开始学习字符串。
    • @Patrick 好的,希望您在使用代码时不会遇到问题,因为您只需要进行少量修改。 PS 维基百科上的事情似乎比实际情况复杂得多;))
    猜你喜欢
    • 2020-04-17
    • 1970-01-01
    • 2021-02-11
    • 1970-01-01
    • 2018-03-07
    • 2020-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-22
    相关资源
    最近更新 更多