【问题标题】:Finding an integer sum in an array of 1 000 000在 1 000 000 的数组中查找整数和
【发布时间】:2015-11-05 04:38:03
【问题描述】:

给定大量整数(超过 1 000 000 个值),找出有多少种方法可以选择其中两个加起来为 0.... 是问题

我所做的是创建一个正随机整数列表:

Random pos = new Random();
int POSNO = pos.Next(1, 1000000);
lstPOS.Items.Add(POSNO);
lblPLus.Text = lstPOS.Items.Count.ToString();
POSCount++;

并创建了负面清单:

Random neg = new Random();
int NEGNO = neg.Next(100000, 1000000);
lstNEG.Items.Add("-" + NEGNO);
lblNegative.Text = lstNEG.Items.Count.ToString();
NegCount++;

要进行我正在使用的总和检查:

foreach (var item in lstPOS.Items)
{
    int POSItem = Convert.ToInt32(item.ToString());
    foreach (var negItem in lstNEG.Items)
    {
        int NEGItem = Convert.ToInt32(negItem.ToString());
        int Total = POSItem - NEGItem;
        if (Total == 0)
        {
            lstADD.Items.Add(POSItem + "-" + NEGItem + "=" + Total);
            lblAddition.Text = lstADD.Items.Count.ToString();
        }
    }
}

我知道这不是最快的路线。我考虑过使用数组。你有什么建议吗?

【问题讨论】:

  • lstPOSlstNEG的类型是什么?
  • 当您将整数放入列表时,我认为您不想将它们转换为字符串。为什么要这样做?
  • lstPOS & lstNEG 是简单的列表框。我已将这些用于视觉表示。当我将它们添加到列表框时,它们仍然是整数。如果我从列表框中提取,它会保留整数形式还是更改为列表框项?
  • 不要忘记两个零加起来为零0 + 0 == 0;因此,除了正面和负面之外,您还必须将零作为单独的案例来解决。

标签: c# sum


【解决方案1】:

让我们看看;你的数组是这样的:

  int[] data = new int[] {
    6, -2, 3, 2, 0, 0, 5, 7, 0, -2
  };

您可以通过两种不同的方式加到零:

  1. a + (-a) // 正 + 负
  2. 0 + 0 // 任意两个零

在上面的示例中有 5 对:

  -2 + 2 (two pairs): [1] + [3] and [3] + [9]
   0 + 0 (three pairs): [4] + [5], [4] + [8] and [5] + [8]

因此,您必须跟踪正/负对和零。实现

 Dictionary<int, int> positives = new Dictionary<int, int>();
 Dictionary<int, int> negatives = new Dictionary<int, int>(); 
 int zeros = 0;

 foreach(var item in data) {
   int v;

   if (item < 0) 
     if (negatives.TryGetValue(item, out v))     
       negatives[item] = negatives[item] + 1;
     else
       negatives[item] = 1;  
   else if (item > 0) 
     if (positives.TryGetValue(item, out v))     
       positives[item] = positives[item] + 1;
     else
       positives[item] = 1;  
   else
     zeros += 1;
 } 

 // zeros: binomal coefficent: (2, zeros)
 int result = zeros * (zeros - 1) / 2;

 // positive/negative pairs
 foreach (var p in positives) {
   int n;

   if (negatives.TryGetValue(-p.Key, out n)) 
     result += n * p.Value; 
 } 

 // Test (5)
 Console.Write(result); 

请注意,这里没有排序,并且字典(即哈希表)用于正负数,因此执行时间将是线性O(n);实现的阴暗面是需要两个额外的结构(即额外的内存)。在您的情况下(仅数百万个整数 - 兆字节)您拥有该内存。

编辑:更简洁,但可读性较差的 Linq 解决方案:

  var dict = data
    .GroupBy(item => item)
    .ToDictionary(chunk => chunk.Key, chunk => chunk.Count());

  int result = dict.ContainsKey(0) ? dict[0] * (dict[0] - 1) / 2 : 0;

  result += dict
    .Sum(pair => pair.Key > 0 && dict.ContainsKey(-pair.Key) ? pair.Value * dict[-pair.Key] : 0);

【讨论】:

  • 不错的答案。我很好奇你是否可以像对待其他人一样审查我的,看看我是否通过:-) 干杯。
【解决方案2】:

无需排序的最快方法!

首先你知道两个整数的和只有0,当它们具有绝对相等值,但一个是负数,另一个是正数。所以你不需要排序。你需要的是将正面列表与负面列表相交(通过比较绝对值)。结果是总和为 0 的数字。

Intersect 的时间复杂度为 O(n+m),其中 n 是第一个列表的大小,m 是第二个列表的大小。

private static void Main(string[] args)
{
    Random random = new Random();

    int[] positive = Enumerable.Range(0, 1000000).Select(n => random.Next(1, 1000000)).ToArray();
    int[] negative = Enumerable.Range(0, 1000000).Select(n => random.Next(-1000000, -1)).ToArray();

    var zeroSum = positive.Intersect(negative, new AbsoluteEqual());

    foreach (var i in zeroSum)
    {
        Console.WriteLine("{0} - {1} = 0", i, i);
    }
}

您还需要使用这个 IEqualityComparer。

public class AbsoluteEqual : IEqualityComparer<int>
{
    public bool Equals(int x, int y)
    {
        return (x < 0 ? -x : x) == (y < 0 ? -y : y);
    }

    public int GetHashCode(int obj)
    {
        return obj < 0 ? (-obj).GetHashCode() : obj.GetHashCode();
    }
}

【讨论】:

  • 你为什么说Intersect has time complexity of O(n+m)
  • 相交使用哈希集。 hashset 的时间复杂度为 O(1) 添加和 O(1) 删除。如果你好奇,你可以搜索一下。在第一个相交中,我们创建第二个列表的哈希集(我们添加第二个列表的每个项目,因此它的 O(m))。然后我们尝试从哈希集中删除第一个列表的每个项目(我们删除它的O(n))。请注意,当删除成功时,它被视为相交。所以相交是O(m+n)。您也可以进行搜索;)@qxg
  • 是的,在我检查了referencesource.microsoft.com/#System.Core/System/Linq/… 之后,你就对了。但我认为它不支持非常大的数据,在这种情况下你可能会耗尽内存。排序和搜索算法将始终有效。您有数 TB 的数据存储在文件中,您可以通过合并算法对它们进行排序,并将搜索值作为流进行排序。哪种解决方案更好实际上取决于输入大小。 @格雷格阿德勒
  • 反例:int[] positive = new int[] { 1, 1, 2 };int[] negative = new int[] { -1, -1 }; 四个对,但实际返回 一个
  • @M.kazem Akhgary:Linq 解决方案没有问题,但您必须考虑 permutations(如上面的反例);恕我直言,GroupBy() 而不是简单的Intesect() 将解决问题。
【解决方案3】:

您试图避免检查两个接近的数字(1、2 接近,3、4 接近),但您没有避免检查(-100000, 1)、(-1, 100000)。时间复杂度为 O(n^2)。 为了避免这种情况,您需要先对它们进行排序,然后从两个方向搜索。

var random = new Random();
var input = Enumerable.Range(1, 100).Select(_ => random.Next(200) - 100).ToArray();

Array.Sort(input); // This causes most computation. Time Complexity is O(n*log(n));
var expectedSum = 0;
var i = 0;
var j = input.Length - 1;
while (i < j) // This has liner time complexity O(n);
{
    var result = input[i] + input[j];
    if(expectedSum == result)
    {
        var anchori = i;
        while (i < input.Length && input[i] == input[anchori] )
        {
            i++;
        }
        var anchorj = j;
        while (j >= 0 && input[j] == input[anchorj])
        {
            j--;
        }
        // Exclude (self, self) combination
        Func<int, int, int> combination = (n, k) =>
        {
            var mink = k * 2 < n ? k : n - k;
            return mink == 0 ? 1 
                : Enumerable.Range(0, mink).Aggregate(1, (x, y) => x * (n - y)) 
                 / Enumerable.Range(1, mink).Aggregate((x, y) => x * y);
        };
        var c = i < j ? (i - anchori) * (anchorj - j) : combination(i - anchori, 2);
        for (int _ = 0; _ < c; _++)
        {
            // C# 6.0 String.Format
            Console.WriteLine($"{input[anchori]}, {input[anchorj]}");
        }
    }
    else if(result < expectedSum) {
        i++;
    }
    else if(result > expectedSum) {
        j--;
    }
}

【讨论】:

  • @qxg: Counter Example: var input = new List&lt;int&gt;() {6, -2, 3, 2, 0, 0, 5, 7, 0, -2}; 5 对,而 2 对实际返回。
  • @DmitryBychenko,感谢 cmets。你是对的,我没有处理重复的输入。在这种情况下,只需移动光标直到当前值发生变化。我已经更新了我的代码。
  • 编辑的代码反例int[] input = new int[] {0, 0};IndexOutOfRangeException 是在预期1 时触发
  • @DmitryBychenko,你的面试我失败了:)
  • 我会考虑与 self 组合作为有效输入,但这确实取决于要求。添加了两个光标重叠时的更多检查。
【解决方案4】:

这是另一个使用(呵呵)LINQ 的解决方案。希望代码是不言自明的

首先是一些数据

var random = new Random();
var data = new int[1000000];
for (int i = 0; i < data.Length; i++) data[i] = random.Next(-100000, 100000);

现在解决方案

var result = data
    .Where(value => value != int.MinValue)
    .GroupBy(value => Math.Abs(value), (key, values) =>
    {
        if (key == 0)
        {
            var zeroCount = values.Count();
            return zeroCount * (zeroCount - 1) / 2;
        }
        else
        {
            int positiveCount = 0, negativeCount = 0;
            foreach (var value in values)
                if (value > 0) positiveCount++; else negativeCount++;
            return positiveCount * negativeCount;
        }
    })
    .Sum();

理论上,上面应该有 O(N) 时间和 O(M) 空间复杂度,其中 M 是列表中唯一绝对值的计数。

【讨论】:

  • 你已经通过了,很好的解决方案;来自我的 +1
  • @DmitryBychenko 感谢您花时间查看此内容,非常感谢您的意见。
猜你喜欢
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
  • 2018-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-14
  • 1970-01-01
相关资源
最近更新 更多