如何在整数数组中找到所有有序元素对,其总和位于给定的值范围内

时间:2014-12-30 12:19:25

标签: python arrays algorithm

给定一个整数数组,找到数组中所有有序元素对的数量,其总和位于给定范围[a,b]

这是针对相同

的O(n ^ 2)解决方案
'''
counts all pairs in array such that the 
sum of pair lies in the range a and b
'''
def countpairs(array, a, b):
    num_of_pairs = 0
    for i in range(len(array)):
        for j in range(i+1,len(array)):
            total = array[i] + array[j]
            if total >= a and total <= b:
                num_of_pairs += 1
    return num_of_pairs

我知道我的解决方案不是最佳的 这样做有什么更好的算法。

9 个答案:

答案 0 :(得分:14)

  1. 对数组进行排序(按升序排列)。
  2. 对于数组中的每个元素x:
    • 在元素之后考虑数组切片
    • 在[a - x]的数组切片上进行二进制搜索,将其称为y0。如果未找到完全匹配,请将最接近的匹配大于而不是[a - x]视为y0。
    • 只要x + y&lt; = b
    • ,就从y0向前输出所有元素(x,y)
  3. 时间复杂度当然是输出敏感的,但这仍然优于现有的算法:

    O(nlogn) + O(k)
    

    其中k是满足条件的对的数量。

    注意:如果您只需计算对的数量,您可以在O(nlogn)中执行此操作。修改上述算法,以便搜索[b - x](或下一个较小的元素)。通过这种方式,您可以计算“匹配”的数量。每个元素在O(logn)中只有第一个和最后一个匹配的索引。然后,这只是一个总结得到最终计数的问题。这样,最初的O(nlogn)排序步骤占主导地位。

答案 1 :(得分:11)

首先对数组进行排序,然后按两个索引计算对。两个索引方法类似于2-sum problem中的方法,这避免了N次的二进制搜索。该算法的耗时是Sort Complexity + O(N),通常,sort是O(NInN),因此该方法是O(NInN)。该算法的思想是,对于索引i,找到下限和上限,使a <= arr[i]+arr[low] <= arr[i]+arr[high] <= bi增加时,我们应该做的是减少{{1}和low来保持条件。为避免两次计算同一对,我们保留high,同时保留low > i。以下计数方法的复杂性是O(N),因为在low <= high中,我们可以做的是while loop++i--low,并且最多--high此类行动。

N

答案 2 :(得分:6)

计算工作对的问题可以在排序时间+ O(N)中完成。这比Ani给出的解决方案更快,即排序时间+ O(N log N)。这个想法是这样的。首先你排序。然后,您运行几乎相同的单通道算法两次。然后,您可以使用两个单通算法的结果来计算答案。

我们第一次运行单遍算法时,我们将创建一个新数组,列出可以与该索引合作的最小索引,以得到大于a的总和。例如:

a = 6
array = [-20, 1, 3, 4, 8, 11]
output = [6, 4, 2, 2, 1, 1]

因此,数组索引1处的数字是1(基于0的索引)。它可以配对以获得超过6的最小数字是8,它在索引4处。因此输出[1] = 4. -20不能与任何东西配对,因此输出[0] = 6(从边界)。另一个例子:输出[4] = 1,因为8(索引4)可以与1(索引1)或之后的任何数字配对,总和超过6。

你现在需要做的就是让自己相信这是O(N)。它是。代码是:

i, j = 0, 5
while i - j <= 0:
  if array[i] + array[j] >= a:
    output[j] = i
    j -= 1
  else:
    output[i] = j + 1
    i += 1

想想从边缘开始并向内工作的两个指针。它是O(N)。你现在做同样的事情,只是条件b&lt; = a:

while i-j <= 0:
  if array[i] + array[j] <= b:
    output2[i] = j
    i += 1
  else:
    output2[j] = i-1
    j-=1

在我们的示例中,此代码为您提供(数组和b以供参考):

b = 9
array = [-20, 1, 3, 4, 8, 11]
output2 = [5, 4, 3, 3, 1, 0]

但是现在,output和output2包含了我们需要的所有信息,因为它们包含配对的有效索引范围。 output是它可以配对的最小索引,output2是它可以配对的最大索引。差值+ 1是该位置的配对数。因此对于第一个位置(对应于-20),有5 - 6 + 1 = 0对。对于1,有4-4 + 1对,数字在索引4为8.另一个微妙,这个算法计算自我配对,所以如果你不想要它,你必须减去。例如。 3似乎包含3-2 + 1 = 2对,一个在索引2,一个在索引3.当然,3本身在索引2,所以其中一个是自配对,另一个是与4配对。只要输出和输出2的索引范围包含您正在查看的索引本身,您只需要减去一个。在代码中,您可以写:

answer = [o2 - o + 1 - (o <= i <= o2) for i, (o, o2) in enumerate(zip(output, output2))]

哪个收益率:

answer = [0, 1, 1, 1, 1, 0]

总和为4,对应于(1,8),(3,4),(4,3),(8,1)

无论如何,正如你所看到的,这是sort + O(N),这是最佳的。

编辑:要求完整实施。提供。供参考,完整代码:

def count_ranged_pairs(x, a, b):
    x.sort()

    output = [0] * len(x)
    output2 = [0] * len(x)

    i, j = 0, len(x)-1
    while i - j <= 0:
      if x[i] + x[j] >= a:
        output[j] = i
        j -= 1
      else:
        output[i] = j + 1
        i += 1

    i, j = 0, len(x) - 1
    while i-j <= 0:
      if x[i] + x[j] <= b:
        output2[i] = j
        i += 1
      else:
        output2[j] = i-1
        j -=1

    answer = [o2 - o + 1 - (o <= i <= o2) for i, (o, o2) in enumerate(zip(output, output2))]
    return sum(answer)/2

答案 3 :(得分:5)

from itertools import ifilter, combinations

def countpairs2(array, a, b):
    pairInRange = lambda x: sum(x) >= a and sum(x) <= b
    filtered = ifilter(pairInRange, combinations(array, 2))
    return sum([2 for x in filtered])

我认为Itertools库非常方便。我还注意到你计算了两次对,例如你把(1,3)和(3,1)算作两种不同的组合。如果您不想这样,只需将最后一行中的2更改为1即可。 注意:最后一个可以更改为return len(list(filtered)) * 2。这可以更快,但代价是使用更多RAM。

答案 4 :(得分:3)

由于对数据有一些限制,我们可以在线性时间内解决问题(对不起Java,我对Python不太熟悉):

public class Program {
    public static void main(String[] args) {
        test(new int[]{-2, -1, 0, 1, 3, -3}, -1, 2);
        test(new int[]{100,200,300}, 300, 300);
        test(new int[]{100}, 1, 1000);
        test(new int[]{-1, 0, 0, 0, 1, 1, 1000}, -1, 2);
    }

    public static int countPairs(int[] input, int a, int b) {
        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;
        for (int el : input) {
            max = Math.max(max, el);
            min = Math.min(min, el);
        }
        int d = max - min + 1; // "Diameter" of the array
        // Build naive hash-map of input: Map all elements to range [0; d]
        int[] lookup = new int[d];
        for (int el : input) {
            lookup[el - min]++;
        }
        // a and b also needs to be adjusted
        int a1 = a - min;
        int b1 = b - min;
        int[] counts = lookup; // Just rename
        // i-th element contain count of lookup elements in range [0; i]
        for (int i = 1; i < counts.length; ++i) {
            counts[i] += counts[i - 1];
        }
        int res = 0;
        for (int el : input) {
            int lo = a1 - el; // el2 >= lo
            int hi = b1 - el; // el2 <= hi
            lo = Math.max(lo, 0);
            hi = Math.min(hi, d - 1);
            if (lo <= hi) {
                res += counts[hi];
                if (lo > 0) {
                    res -= counts[lo - 1];
                }
            }
            // Exclude pair with same element
            if (a <= 2*el && 2*el <= b) {
                --res;
            }
        }
        // Calculated pairs are ordered, divide by 2
        return res / 2;
    }

    public static int naive(int[] ar, int a, int b) {
        int res = 0;
        for (int i = 0; i < ar.length; ++i) {
            for (int j = i + 1; j < ar.length; ++j) {
                int sum = ar[i] + ar[j];
                if (a <= sum && sum <= b) {
                    ++res;
                }
            }
        }
        return res;
    }

    private static void test(int[] input, int a, int b) {
        int naiveSol = naive(input, a, b);
        int optimizedSol = countPairs(input, a, b);
        if (naiveSol != optimizedSol) {
            System.out.println("Problem!!!");
        }
    }
}

对于数组的每个元素,我们知道该对的第二个元素可以放置的范围。该算法的核心是给出范围内元素的计数[a; b]在O(1)时间内。

产生的复杂度为O(max(N,D)),其中D是数组的max和min元素之间的差异。如果该值与N的顺序相同 - 复杂度为O(N)。

注意:

  • 不涉及分类!
  • 需要构建查找才能使算法使用否定 数字并使第二个数组尽可能小(正面 影响记忆和时间)
  • 丑陋条件if (a <= 2*el && 2*el <= b)是必需的,因为算法总是计算对(a [i],a [i])
  • 算法需要O(d)额外的内存,这可能很多。

另一种线性算法是基数排序+线性对计数。

修改即可。如果D远小于N并且不允许修改输入数组,则此算法可以非常好。对于这种情况的替代选项将略微修改,计数排序与计数数组(附加的O(D)内存),但不将排序的元素填充回输入数组。可以使用对数计数来使用计数数组而不是完全排序的数组。

答案 5 :(得分:2)

我有一个解决方案(实际上是2个解决方案;-))。用python编写:

def find_count(input_list, min, max):
    count = 0
    range_diff = max - min
    for i in range(len(input_list)):
        if input_list[i]*2 >= min and input_list[i]*2 <= max:
            count += 1
        for j in range(i+1, len(input_list)):
            input_sum = input_list[i] + input_list[j]
            if input_sum >= min and input_sum <= max:
                count += 2

这将使nCr(n次组合)次数达到最大值,并为您提供所需的计数。这比排序列表然后找到范围内的对更好。如果组合失败的元素数量更多以及所有数字都是正整数,我们可以通过添加检查元素的条件来更好地改善结果,

  • 即使添加了最大值
  • ,也不属于该范围的数字
  • 大于范​​围最大数量的数字。

这样的事情:

# list_maximum is the maximum number of the list (i.e) max(input_list), if already known
def find_count(input_list, min, max, list_maximum):
    count = 0
    range_diff = max - min
    for i in range(len(input_list)):
        if input_list[i] > max or input_list[i] + list_maximum < min:
            continue
        if input_list[i]*2 >= min and input_list[i]*2 <= max:
            count += 1
        for j in range(i+1, len(input_list)):
            input_sum = input_list[i] + input_list[j]
            if input_sum >= min and input_sum <= max:
                count += 2

我也很乐意学习比这更好的解决方案:-)如果我遇到一个,我会更新这个答案。

答案 6 :(得分:1)

我相信这是一个简单的数学问题,可以用numpy来解决,没有循环而我们没有排序。我不完全确定,但我认为在更糟糕的情况下复杂度为O(N ^ 2)(希望能够通过numpy中的时间复杂性更熟悉的人对此进行一些确认)。

无论如何,这是我的解决方案:

import numpy as np

def count_pairs(input_array, min, max):
    A = np.array(input_array)
    A_ones = np.ones((len(A),len(A)))
    A_matrix = A*A_ones
    result = np.transpose(A_matrix) + A_matrix
    result = np.triu(result,0)
    np.fill_diagonal(result,0)
    count = ((result > min) & (result < max)).sum()
    return count

现在让我们来看看 - 首先我创建一个矩阵,其中的列代表我们的数字:

A = np.array(input_array)
A_ones = np.ones((len(A),len(A)))
A_matrix = A*A_ones

假设我们的输入数组看起来像[1,1,2,2,3,-1],因此,此时此值应为A_matrix

[[ 1.  1.  2.  2.  3. -1.]
 [ 1.  1.  2.  2.  3. -1.]
 [ 1.  1.  2.  2.  3. -1.]
 [ 1.  1.  2.  2.  3. -1.]
 [ 1.  1.  2.  2.  3. -1.]
 [ 1.  1.  2.  2.  3. -1.]]

如果我将其添加到自身的转置中......

result = np.transpose(A_matrix) + A_matrix

...我应该得到一个表示对的总和的所有组合的矩阵:

[[ 2.  2.  3.  3.  4.  0.]
 [ 2.  2.  3.  3.  4.  0.]
 [ 3.  3.  4.  4.  5.  1.]
 [ 3.  3.  4.  4.  5.  1.]
 [ 4.  4.  5.  5.  6.  2.]
 [ 0.  0.  1.  1.  2. -2.]]

当然,该矩阵在对角线上镜像,因为对(1,2)和(2,1)产生相同的结果。我们不想考虑这些重复的条目。我们也不想考虑项目的总和,所以让我们清理我们的数组:

result = np.triu(result,0)
np.fill_diagonal(result,0)

我们的结果现在看起来像:

[[ 0.  2.  3.  3.  4.  0.]
 [ 0.  0.  3.  3.  4.  0.]
 [ 0.  0.  0.  4.  5.  1.]
 [ 0.  0.  0.  0.  5.  1.]
 [ 0.  0.  0.  0.  0.  2.]
 [ 0.  0.  0.  0.  0.  0.]]

剩下的就是计算通过我们标准的项目。

count = ((result > min) & (result < max)).sum()

提醒一句:

如果0位于可接受的域中,则此方法无法正常工作,但我确信操纵上面的结果矩阵将这些0转换为某些字段会很简单其他毫无意义的数字......

答案 7 :(得分:1)

我们可以简单地检查是否使用关系运算符 数组元素i和j的总和在指定的范围内。

def get_numOfPairs(array, start, stop):
    num_of_pairs = 0
    array_length = len(array)

    for i in range(array_length):
        for j in range(i+1, array_length):
            if sum([array[i], array[j]]) in range(start, stop):
                num_of_pairs += 1

    return num_of_pairs

答案 8 :(得分:0)

n = int(input())
ar = list(map(int, input().rstrip().split()))[:n]
count=0
uniq=[]
for i in range(n):
    if ar[i] not in uniq:
        uniq.append(ar[i])
for j in uniq:
    if ((ar.count(j))%2==0):
        count=count+((ar.count(j))/2)
    if ((ar.count(j))%2!=0) & (((ar.count(j))-1)%2==0):
        count=count+((ar.count(j)-1)/2)
print(int(count))