找到满足条件的数字

时间:2014-06-19 12:55:33

标签: c# algorithm

假设您有一百万个连续的整数。            返回a,b和c的所有可能值,以便

      a+b+c<=d.
      d will be provided to you.
      ex: if the numbers are 1,2,3,4,5,6,7
      and d=7
       [1,2,3]
       [1,2,4]
       [1,2,3] will be same as [1,3,2] and [3,2,1]...

因为一百万太大了,在这个例子中我以1000为例。同样为了方便起见,我使用1到1000作为数据集。 假设

      `a<b<c`

因此3*a<1000 ==&gt; a&lt; 333.33,所以我从1到333提取a

 static void Main(string[] args)
    {
        int d = 519;
        var setA = Enumerable.Range(1, 333);
        IEnumerable<int> value = Enumerable.Range(1, 1000);
        var result =     (from a in setA
                          from b in value
                          from c in value
                          where a!=b && a!=c && b!=c && a + b + c <= d
                          select new {a,b,c}).ToList().Distinct().ToList();
        foreach (var item in result)
        {
            Console.WriteLine(item);
        }
        Console.ReadKey();
    }

速度慢,抛出System.OutOfMemoryException例外.....

4 个答案:

答案 0 :(得分:1)

a缩小到1...333是一个好主意。您可以使用a < b < c这一事实进一步改进代码,因此b,c in Enumerable.Range(1, 1000)不是最理想的。

您可以分别根据给定的数字bc定义ab的下限和上限:

a < b => b >= a + 1, b in Enumerable.Range(a + 1, ...)
b < c => c must be in Enumerable.Range(b + 1, ...)

此外,您可以像这样定义ab的界限:

  • 由于b >= a + 1a + b + c <= totala + (a + 1) + ((a + 1) + 1) <= total也必须成立。也就是说,a < total / 3是不够的。它是a <= (total - 3) / 3
  • 同样a + b + (b + 1) <= total,即b <= (total - a - 1) / 2
  • 当然,a + b + c <= total会转换为c <= total - a - b

您可以通过嵌套迭代并使用SelectMany展平结果列表来利用它:

        var result = Enumerable.Range(1, (total - dba - dca) / 3)
            .SelectMany(
                a => Enumerable.Range(a + 1, (total - a - dcb) / 2 - a)
                    .SelectMany(
                        b => Enumerable.Range(b + 1, (total - a - b) - b)
                            .Select(c => new { a, b, c })));

至于你的表现和内存不足问题:

从LINQ查询中删除ToList()。它会导致所有结果在开始处理之前加载到内存中。由于您只想打印元组,因此不需要将所有元素加载到内存中。这是LINQ的最大优势 - 它只返回一个枚举器而不实际计算结果。如果从LINQ查询中删除ToList(),for-each循环将计算每次迭代的一个结果,将其打印出来,然后再次忘记它。


作为对您的评论的解释性答案:

Enumerable.Range的实现如下:

private static IEnumerable<int> RangeIterator(int start, int count)
{
    for (int i = 0; i < count; ++i)
        yield return start + i;
}

SelectMany的实施:

private static IEnumerable<TResult> SelectManyIterator<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
{
  foreach (TSource source1 in source)
  {
    foreach (TResult result in selector(source1))
      yield return result;
  }
}

例如,

foreach (var item in Enumerable.Range(1, 10).SelectMany(n => Enumerable.Range(1, n)))
{ /* do something */ }

概念上转换为:

for (int i = 0; i < 10; ++i)
{
    var n = 1 + i;

    for (int j = 0; j < n; ++j)
    {
        var result = 1 + j;

        /* do something */  // <- processes the current result and forgets it again
    }
}

但是,当您添加ToList时:

foreach (var item in Enumerable.Range(1, 10).SelectMany(n => Enumerable.Range(1, n)).ToList())
{ /* do something */ }

这转化为:

var list = new List<int>();
for (int i = 0; i < 10; ++i)
{
    var n = 1 + i;

    for (int j = 0; j < n; ++j)
    {
        var item = 1 + j;
        list.Add(item); // <- puts the current result in a list
    }
}

// list content: 1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6...

foreach (var item in list)
{ /* do something */ }

答案 1 :(得分:0)

假设输入已排序,您可以使用index和每个for循环的选择进行播放。第一个应该是主数组的三分之一,第二个循环应该不超过一半,但是从第一个循环的索引开始。像这样的东西:

    const int totalElements = 1000;
        const int target = 1000;
        var value = Enumerable.Range(1, totalElements).OrderBy(dd => dd).ToList();
        var sw = new Stopwatch();
        var result = new List<int[]>();
        sw.Start();

        for (int i = 0; i < value.Count / 3; i++)
        {
            var a = value[i];

            for (int j = i + 1; j < value.Count / 2; j++)
            {
                var b = value[j];

                for (int k = j + 1; k < value.Count; k++)
                {
                    var c = value[k];

                    if (a + b + c <= target)
                    {
                        var newR = new[] { a, b, c };
                        result.Add(newR);
                    }
                }
            }
        }

        sw.Stop();
        Console.WriteLine("Time Taken: " + sw.Elapsed.TotalSeconds);
        Console.WriteLine("Total result count: " + result2.Count);   

对于1000,它在我糟糕的机器上需要4.9秒,而LINQ正在投掷outofmemory exception。最重要的是你必须在LINQ中添加某种相等比较,否则你会得到像1 + 2 + 3和3 + 2 + 1这样的结果,并且只使用索引就可以摆脱它。

您还可以通过执行以下操作来调整范围:

        for (int i = 0; i < value.Count / 3; i++)
        {
            var a = value[i];

            var index = value.IndexOf(target - a) + 1;

            for (int j = i + 1; j < index; j++)
            {
                var b = value[j];

                var remainingIndex = value.IndexOf(target - (a + b)) + 1;

                for (int k = j + 1; k < remainingIndex; k++)
                {
                    var c = value[k];

                    if (a + b + c <= target)
                    {
                        var newR = new[] { a, b, c };
                        result.Add(newR);
                    }
                }
            }
        }

如果值像这个例子一样是顺序的,你可以更容易地找到索引。如果不是这种情况,你必须通过Binary Search找到索引,但无论如何它可能都是类似的表现。

答案 2 :(得分:0)

我建议将数字存储在SortedSet类中。您可以执行以下操作(伪代码):

For each n in GetViewBetween (1, d)
    For each m in GetViewBetween (n+1, d-n)
        For each k in GetViewBetween (m+1, d-n-m)
            Store or print or callback or return with continuation the Tuple object with n, m, k
        Loop
    Loop
Loop

答案 3 :(得分:-1)

我会建议你一个几何表示:想象一下,你的&#39; d&#39;是三角形边界的总和。好吧,你需要找到适合的所有三角形。在您的代码中,您可以使用双变量:(a + b + c)=(b + c + a)。但是如果你为每个三角形边做3个循环,那么它会更快。