使用大量元素的列表时超出时间限制?

时间:2016-03-29 19:10:06

标签: c#

使用大量元素运行时,函数失败了性能测试:超出时间限制。     如何通过性能测试?

 //Function finds indices of two elements whose sum is equal to as passed in the parameter
 public static Tuple<int, int> FindTwoSum(IList<int> list, int sum)
 {
     for (int i = 0; i < list.Count; i++)
     {
         int sum2 = sum - list[i];
         int index = list.IndexOf(sum2);
         if (index > 0 )
         {
             return new Tuple<int, int>(i, index);
         }
     }
     return null;
 }

 //Main function to call FindTwoSum method
 public static void Main(string[] args)
 {
  Tuple<int, int> indices = FindTwoSum(new List<int>() { 1, 3, 5, 7, 9 },12);
  Console.WriteLine(indices.Item1 + " " + indices.Item2);
 }

4 个答案:

答案 0 :(得分:3)

从表面上看,散列解决方案应该是最快的 - 实际上,它可能适用于超过2GB大小的超大阵列。

然而(令人惊讶的是)int数组的大小达到50,000,000个元素,对数组进行排序并使用与排序数组一起使用的优化算法会更快。

这是一个可以在排序数组上使用的算法(请注意,它需要一个额外的数组,用于在排序之前指示元素的原始索引):

public static Tuple<int, int> FindTwoSumInSortedList(IList<int> list, int[] indices, int sum)
{
    for (int i = 0, j = list.Count - 1; i < j;)
    {
        int s = list[i] + list[j];

        if (s == sum)
            return new Tuple<int, int>(indices[i], indices[j]);
        else if (s < sum)
            ++i;
        else
            --j;
    }

    return null;
}

对原始列表进行排序需要一些额外的工作:

int n = 10000000;
int[] array = new int[n];
...
var indices = Enumerable.Range(0, n).ToArray();
Array.Sort(array, indices);
result = FindTwoSumInSortedList(array, indices, target);

这似乎是一项额外的大量工作,但令我惊讶的是,它在20,000,000个元素的数组上优于散列算法。

我在下面发布我的测试程序,批评。我试图使FindTwoSumInSortedList()算法的样本数据尽可能笨拙。

我从PC上的RELEASE版本获得的结果是:

n = 10,000,000 

3031
(5000000, 5000001)
1292
(5000000, 5000001)

n = 20,000,000

6482
(10000000, 10000001)
2592
(10000000, 10000001)

n = 50,000,000

17408
(25000000, 25000001)
5653
(25000000, 25000001)

所以你可以看到排序算法的速度是原来的两倍多。这真让我感到惊讶!

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    class Program
    {
        public static void Main()
        {
            int n = 10000000;
            int[] array = new int[n];
            var rng = new Random(18789);

            for (int i = 0; i < n; ++i)
                array[i] = rng.Next(0, n);

            array[n/2] = n;
            array[n/2 + 1] = n+1;

            var sw = Stopwatch.StartNew();

            // This is too slow to test:
            //var result = FindTwoSum(array, n*2+1);
            //Console.WriteLine(sw.ElapsedMilliseconds);
            //Console.WriteLine(result);

            sw.Restart();
            var result = FindTwoSumFaster(array, n*2 + 1);
            Console.WriteLine(sw.ElapsedMilliseconds);
            Console.WriteLine(result);

            sw.Restart();
            var indices = Enumerable.Range(0, n).ToArray();
            Array.Sort(array, indices);
            result = FindTwoSumInSortedList(array, indices, n*2+1);
            Console.WriteLine(sw.ElapsedMilliseconds);
            Console.WriteLine(result);
        }

        public static Tuple<int, int> FindTwoSum(IList<int> list, int sum)
        {
            for (int i = 0; i < list.Count; i++)
            {
                int sum2 = sum - list[i];
                int index = list.IndexOf(sum2);
                if (index > 0)
                {
                    return new Tuple<int, int>(i, index);
                }
            }
            return null;
        }

        public static Tuple<int, int> FindTwoSumInSortedList(IList<int> list, int[] indices, int sum)
        {
            for (int i = 0, j = list.Count - 1; i < j;)
            {
                int s = list[i] + list[j];

                if (s == sum)
                    return new Tuple<int, int>(indices[i], indices[j]);
                else if (s < sum)
                    ++i;
                else
                    --j;
            }

            return null;
        }

        public static Tuple<int, int> FindTwoSumFaster(IList<int> list, int sum)
        {
            if (list == null)
                throw new NullReferenceException("Null list");

            // constructing a hashset to have O(1) operations
            var listSet = new HashSet<int>();

            // number -> index mapping
            // O(n) complexity
            var listReverseSet = new Dictionary<int, int>();
            int i = 0;
            foreach (var elem in list)
            {
                if (!listSet.Contains(elem))
                    listSet.Add(elem);

                listReverseSet[elem] = i++;
            }

            // O(n) complexity
            int listCount = list.Count;
            for (int index = 0; index < listCount; index++)
            {
                var elem = list[index];

                if (listSet.Contains(sum - elem))
                    return new Tuple<int, int>(index, listReverseSet[sum - elem]);
            }

            return null;
        }
    }
}

答案 1 :(得分:2)

超出时间限制可能是因为您正在执行O(n ^ 2)操作。你可以在O(n)中解决它,无论如何检查你的时间都可能知道这一点。

如果只是找到任何具有所需金额的货币对,请执行以下操作:

  • 对于每个项目,计算所需的数量。例如,如果sum = 10并且您看到7,则需要存储3.将其存储在哈希集O(1)中。
  • 当您浏览列表时,请检查哈希集中是否存在。再次,O(1)。
  • 总的来说,你有一个O(n)而不是O(n ^ 2)就像你提交的一样。

答案 2 :(得分:1)

当使用哈希集时,卡洛斯说复杂性可以大大降低到O(n)时是正确的。代码应如下所示:

public static Tuple<int, int> FindTwoSumFaster(IList<int> list, int sum)
{
    if (list == null)
        throw new NullReferenceException("Null list");

    // constructing a hashset to have O(1) operations
    var listSet = new HashSet<int>();

    // number -> index mapping
    // O(n) complexity
    var listReverseSet = new Dictionary<int, int>();
    int i = 0;
    foreach (var elem in list)
    {
        if (!listSet.Contains(elem))
            listSet.Add(elem);

        listReverseSet[elem] = i++;
    }

    // O(n) complexity
    int listCount = list.Count;
    for (int index = 0; index < listCount; index ++)
    {
        var elem = list[index];

        if (listSet.Contains(sum - elem))
            return new Tuple<int, int>(index, listReverseSet[sum - elem]);
    }

    return null;
}

答案 3 :(得分:0)

我使用类似的方法解决了这个问题。它与其他解决方案一样快,因为它只遍历列表一次,考虑到对字典的访问为O(1),所以该解决方案的复杂度为O(n)。

您可以在此处测试代码:TESTDOME - Two sum

public static Tuple<int, int> FindTwoSum(IList<int> list, int sum)
{
    if (list == null)
        return null;

    var listSet = new Dictionary<int, int>();
    int i = 0;
    // O(n)
    foreach (var elem in list)
    {
        if (listSet.Count > 0) {
            var search = sum - elem;
            if (listSet.ContainsKey(search)) {
                var index = listSet[search];
                if (i != index) {
                    return new Tuple<int, int>(i, index);
                }
            }
        }
        listSet[elem] = i++;
    }
    return null;
}