【问题标题】:How can I determine if a certain number can be made up from a set of numbers?如何确定某个数字是否可以由一组数字组成?
【发布时间】:2015-12-22 21:42:34
【问题描述】:

所以我有一个整数,例如1234567890,以及一组给定的数字,例如{4、7、18、32、57、68}

问题是 1234567890 是否可以由给定的数字组成(您可以多次使用一个数字,而不必使用所有这些数字)。在上述情况下,一种解决方案是:
38580246 * 32 + 1 * 18

(不需要给出具体的解决方案,只要能做到就行)

我的想法是尝试所有解决方案。例如我会尝试
1 * 4 * + 0 * 7 + 0 * 18 + 0 * 32 + 0 * 57 + 0 * 68 = 4
2 * 4 * + 0 * 7 + 0 * 18 + 0 * 32 + 0 * 57 + 0 * 68 = 8
3 * 4 * + 0 * 7 + 0 * 18 + 0 * 32 + 0 * 57 + 0 * 68 = 12
.....
308 641 972 * 4 * + 0 * 7 + 0 * 18 + 0 * 32 + 0 * 57 + 0 * 68 = 1234567888
308 641 973 * 4 * + 0 * 7 + 0 * 18 + 0 * 32 + 0 * 57 + 0 * 68 = 1234567892 ==> 超过
0 * 4 * + 1 * 7 + 0 * 18 + 0 * 32 + 0 * 57 + 0 * 68 = 7
1 * 4 * + 1 * 7 + 0 * 18 + 0 * 32 + 0 * 57 + 0 * 68 = 11
2 * 4 * + 1 * 7 + 0 * 18 + 0 * 32 + 0 * 57 + 0 * 68 = 15
等等...

这是我在 c# 中的代码:

    static int toCreate = 1234567890;
    static int[] numbers = new int[6] { 4, 7, 18, 32, 57, 68};
    static int[] multiplier;
    static bool createable = false;

    static void Main(string[] args)
    {
        multiplier = new int[numbers.Length];
        for (int i = 0; i < multiplier.Length; i++)
            multiplier[i] = 0;

        if (Solve())
        {
            Console.WriteLine(1);
        }
        else
        {
            Console.WriteLine(0);
        }
    }

    static bool Solve()
    {
        int lastIndex = 0;
        while (true)
        {
            int comp = compare(multiplier);
            if (comp == 0)
            {
                return true;
            }
            else if (comp < 0)
            {
                lastIndex = 0;
                multiplier[multiplier.Length - 1]++;
            }
            else
            {
                lastIndex++;
                for (int i = 0; i < lastIndex; i++)
                {
                    multiplier[multiplier.Length - 1 - i] = 0;
                }
                if (lastIndex >= multiplier.Length)
                {
                    return false;
                }
                multiplier[multiplier.Length - 1 - lastIndex]++;
            }
        }
    }

    static int compare(int[] multi)
    {
        int osszeg = 0;
        for (int i = 0; i < multi.Length; i++)
        {
            osszeg += multi[i] * numbers[i];
        }
        if (osszeg == toCreate)
        {
            return 0;
        }
        else if (osszeg < toCreate)
        {
            return -1;
        }
        else
        {
            return 1;
        }
    }

代码运行良好(据我所知),但速度太慢了。求解这个例子大约需要 3 秒,100 个数字可能有 10000 个数字。

【问题讨论】:

  • 假设集合中的数字互质,我是否正确?
  • 在我看来,您可以通过预先为每个数字做一些基本的数学运算来消除很多潜在的答案。例如,您知道,当您只添加一个数字时,您只需检查所需的数字是否可以被该数字整除。这是一次检查,而不是遍历每个可能的数字,直到超过所需的数字
  • 模数运算符可能是你的朋友。您可以从 1234567890 % 68 开始,然后看看您是否可以从其他较小的数字中创建余数。这会先把它变成一个小问题。
  • @TrentSartain 这是我想说的一个更清楚的例子
  • 如果代码运行良好,上 CodeReview 不是更好吗?

标签: c# math numbers number-theory


【解决方案1】:

我有一个递归解决方案。它在大约 0.005 秒内(在我的机器上)解决了 OP 的原始问题,并告诉您计算结果。

private static readonly int Target = 1234567890;
private static readonly List<int> Parts = new List<int> { 4, 7, 18, 32, 57, 68 };

static void Main(string[] args)
{
    Console.WriteLine(Solve(Target, Parts));
    Console.ReadLine();
}

private static bool Solve(int target, List<int> parts)
{
    parts.RemoveAll(x => x > target || x <= 0);
    if (parts.Count == 0) return false;

    var divisor = parts.First();
    var quotient = target / divisor;
    var modulus = target % divisor;

    if (modulus == 0)
    {
        Console.WriteLine("{0} X {1}", quotient, divisor);
        return true;
    }

    if (quotient == 0 || parts.Count == 1) return false;

    while (!Solve(target - divisor * quotient, parts.Skip(1).ToList()))
    {
        if (--quotient != 0) continue;
        return Solve(target, parts.Skip(1).ToList());
    }

    Console.WriteLine("{0} X {1}", quotient, divisor);
    return true;
}

基本上,它会遍历每个数字,看看在给定当前商和数字的情况下是否有可能的解决方案“低于”它。如果没有,它从商中减去 1 并再次尝试。它会执行此操作,直到用尽该号码的所有选项,然后如果可用,则转到下一个号码。如果所有数字都用尽,则没有解决方案。

【讨论】:

  • 这是惊人的快,但不幸的是至少有一个错误。你说if (quotient == 0) return false; 的那一行不应该在那里,因为这意味着对于 5 和部分{ 10, 5 } 的目标,当明显存在时没有解决方案(你可以只使用 0 10)。我正在更彻底地检查程序,如果遇到任何其他问题,我会通知您
  • 好吧,你打败了我,得到了一个很好的答案。我看不出有任何边缘情况不起作用(除了上述情况,您可以轻松过滤掉)或工作非常缓慢,所以这似乎是最好的答案
  • 事实上修复就像在Main的开头附近添加Parts.RemoveAll(x =&gt; x &gt; Target);一样简单
  • @KevinWells 很棒的收获!我更新了解决方案。
【解决方案2】:

没有办法测试解决方案,但以下应该可以。

给定一个目标号码target 和一组numbers 的有效号码:

bool FindDecomposition(int target, IEnumerable<int> numbers, Queue<int> decomposition)
{
    foreach (var i in numbers)
    {
        var remainder = target % i;

        if (remainder == 0)
        {
             decomposition.Enqueue(i);
             return true;
        } 

        if (FindDecomposition(remainder, numbers.Where(n => n < i), decomposition))
        {
             return true;
        }
    }

    return false
}

decomposition 构建n 非常简单。

【讨论】:

  • 这不起作用的原因与最后一个错误答案不起作用的原因相同(尽管我做出了一些假设,因为您没有说得很清楚这是在做什么)。如果您只是对每个给定的数字执行desiredNumber % numberFromList,那么检查当您尝试对所需的数字 28 和给定的数字 5 和 9 执行此操作时会发生什么。它将执行 28 % 5 = 3 =&gt; 3 % 9 = 328 % 9 = 2 =&gt; 2 % 5 = 2 并得出结论它不能可以完成,但实际上(2 * 5) + (2 * 9) = 28所以可以完成。
  • @KevinWells 非常真实!我错过了上一个答案,它已被删除。我会留下这个,以免其他人犯同样的错误。
  • 酷,我还对解释为什么这种方法不起作用的问题发表了评论。我最初被同一件事所吸引,直到找到一个反例
【解决方案3】:

您总是可以尝试将模函数与 LINQ 表达式结合使用来解决问题。

您将有一个列表和一个正在运行的模变量来跟踪您在迭代中所处的位置。然后简单地用递归来判断你是否满足条件。

一个例子如下:

static int toCreate = 1234567890;
    static List<int> numbers = new List<int> { 4, 7 };


    static void Main(string[] args)
    {
        numbers.Sort();
        numbers.Reverse();

        Console.WriteLine(Solve(numbers,toCreate).ToString());
    }

    static bool Solve(List<int> lst1, int runningModulo)
    {
        if (lst1.Count == 0 && runningModulo != 0) 
            return false;
        if (lst1.Count == 0 || runningModulo == 0)
            return true;

        return numbers.Any(o => o < (toCreate % lst1.First())) ? //Are there any in the remaining list that are smaller in value than the runningModulo mod the first element in the list.
            Solve(lst1.Where(o => o != lst1.First()).ToList(), runningModulo % lst1.First()) //If yes, then remove the first element and set the running modulo = to your new modulo
            : Solve(lst1.Where(o => o != lst1.First()).ToList(), toCreate); //Otherwise, set the running modulo back to the original toCreate value.
    }

【讨论】:

  • 这不起作用,原因与最后一个错误答案不起作用的原因相同。如果您只是对每个给定数字执行desiredNumber % numberFromList,那么检查当您尝试对所需的数字 28 和给定的数字 5 和 9 执行此操作时会发生什么。它将执行 28 % 5 = 3 =&gt; 3 % 9 = 328 % 9 = 2 =&gt; 2 % 5 = 2 并得出结论它不能完成了,但实际上(2 * 5) + (2 * 9) = 28所以它可以完成
猜你喜欢
  • 1970-01-01
  • 2014-11-16
  • 1970-01-01
  • 2015-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-23
相关资源
最近更新 更多