当前位置: 首页 > 知识库问答 >
问题:

在大小为N的整数数组中查找和为X的对,其中元素的范围为0到N-1

杜建章
2023-03-14

这是一个面试问题。我们有一个大小为N的整数数组,包含0到N-1之间的元素。一个数字可能出现两次以上。目标是找到总和为给定数字X的对。

我使用了一个辅助数组,该数组包含主数组的元素计数,然后根据辅助数组重新排列主数组,以便对主数组进行排序,然后搜索对。

但是面试官想要空间复杂度常数,所以我告诉他对数组进行排序,但这不是时间复杂度解。他想要O(n)解。

是否有任何方法可以在没有任何额外空间的情况下在O(n)中执行此操作?

共有3个答案

席烨
2023-03-14

字符串排序是n log n,但是如果可以假设数字是有界的(并且可以,因为您只对求和为某个值的数字感兴趣),则可以使用基数排序。基数排序需要O(kN)时间,其中“k”是键的长度。在你的情况下,这是一个常数,所以我认为公平地说是O(N)。

然而,通常我会使用散列来解决这个问题。

http://41j.com/blog/2012/04/find-items-in-an-array-that-sum-to-15/

尽管这当然不是线性时间解。

李康安
2023-03-14

这可以通过在O(N)时间内将输入数组转换为“就地”计数器列表来实现。当然,这假设输入数组不是不变的。不需要对每个数组元素中未使用的位进行任何其他假设。

从以下预处理开始:尝试将每个数组的元素移动到由元素值确定的位置;将此位置上的元素也移动到由其值确定的位置;继续直到:

  • 下一个元件移动到该循环开始的位置,
  • 无法移动下一个元素,因为它已经位于与其值相对应的位置上(在这种情况下,将当前元素置于该循环开始的位置)

在预处理后,每个元素要么位于其“正确”位置,要么“指向”其“正确”位置。如果我们在每个元素中有未使用的位,我们可以将每个正确定位的元素转换为计数器,用“1”初始化它,并允许每个“指向”元素增加适当的计数器。额外的位允许区分计数器和值。同样的事情可以在没有任何额外位的情况下完成,但使用更少的繁琐算法。

计算数组中的值如何等于0或1。如果存在任何此类值,请将其重置为零,并更新位置0和/或1处的计数器。设置k=2(值小于k的数组部分的大小由计数器替换)。对k=2、4、8、…,应用以下程序。。。

  1. 在位置<代码>k.处查找元素。。2k-1如果处于“正确”位置,则将其替换为计数器,初始值为“1”
  2. 对于位置<代码>k.处的任何元素。。2k-1,值为2。。k-1更新位置处的相应计数器2。。k-1,并将值重置为零
  3. 对于位置<代码>0.处的任何元素。。2k-1,值为k。。2k-1更新位置k处的相应计数器。。2k-1,并将值重置为零

该过程的所有迭代都具有O(N)时间复杂性。最后,输入数组完全转换为计数器数组。这里唯一的困难是在<代码>0的位置最多有两个计数器。。2k-1的值可能大于k-1。但这可以通过为每个索引存储两个额外的索引,并将这些索引处的元素作为计数器而不是值来处理来缓解。

生成计数器数组后,我们可以将计数器对相乘(其中对应的索引对总和为X)以获得所需的对计数。

齐运诚
2023-03-14

不,我不这么认为。您要么需要额外的空间来通过分配给存储桶来“排序”O(n)中的数据,要么需要就地排序,而不是O(n)。

当然,如果你能做出某些假设,总会有技巧。例如,如果N

换句话说,使用较低的16位存储数组中的值,然后使用较高的16位存储与索引匹配的值的计数。

让我们使用一个简化的示例,其中N==8。因此,数组的长度为8个元素,每个元素的整数都小于8,尽管它们的宽度为8位。这意味着(最初)每个元素的前四位为零。

  0    1    2    3    4    5    6    7    <- index
(0)7 (0)6 (0)2 (0)5 (0)3 (0)3 (0)7 (0)7

将计数存储到前四位的O(n)调整的伪码为:

for idx = 0 to N:
    array[array[idx] % 16] += 16 // add 1 to top four bits

作为示例,考虑存储7的第一个索引。因此,该赋值语句将在索引7中添加16,增加7的数量。模运算符用于确保已增加的值仅使用较低的四位来指定数组索引。

所以数组最终变成:

  0    1    2    3    4    5    6    7    <- index
(0)7 (0)6 (1)2 (2)5 (0)3 (1)3 (1)7 (3)7

然后您将新数组放在常量空间中,您可以使用int(array[X]/16)来计算有多少X值。

但是,这是相当狡猾的,需要如前所述的某些假设。这很可能是面试官想要的狡猾程度,或者他们可能只是想看看未来的员工如何处理编码的小林丸:-)

一旦有了计数,就可以很容易地找到和给定的X的对,仍然是O(N)。基本方法是获得卡特斯产品。例如,再次考虑N是8,并且需要求和为8的对。忽略上面多路复用阵列的下半部分(因为您只对计数感兴趣,所以您有:

 0   1   2   3   4   5   6   7    <- index
(0) (0) (1) (2) (0) (1) (1) (3)

基本上,你要做的是一步一步地遍历数组,得到总数为8的数字的乘积。

  • 对于0,需要添加8(不存在)
  • 对于1,您需要添加7。计数的乘积是0 x 3,因此没有任何结果
  • 对于2,您需要添加6。计数的乘积为1 x 1,因此出现一次<代码>(2,6)
  • 对于3,您需要添加5。计数的乘积为2 x 1,因此会出现两次(3,5)
  • 对于4,这是一个特例,因为你不能使用该产品。在这种情况下,这并不重要,因为没有4s,但如果有4s,就不可能成为一对。如果配对的数字相同,则公式为(假设有m个)<代码>1 2 3。。。m-1。再加上一点数学上的宽限,结果是m(m-1)/2

除此之外,您正在与左边的值配对,您已经这样做了,所以您可以停止。

那么你最终得到了什么

a b c d e f g h <- identifiers
7 6 2 5 3 3 7 7

是:

(2,6) (3,5) (3,5)
(c,b) (e,d) (f,d) <- identifiers

没有其他值加起来等于8。

下面的程序说明了这一点:

#include <stdio.h>

int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 4, 4, 4, 4};
#define SZ (sizeof(arr) / sizeof(*arr))

static void dumpArr (char *desc) {
    int i;
    printf ("%s:\n   Indexes:", desc);
    for (i = 0; i < SZ; i++) printf (" %2d", i);

    printf ("\n   Counts :");
    for (i = 0; i < SZ; i++) printf (" %2d", arr[i] / 100);

    printf ("\n   Values :");
    for (i = 0; i < SZ; i++) printf (" %2d", arr[i] % 100);

    puts ("\n=====\n");
}

上面的那一位只是为了调试。进行铲斗排序的实际代码如下:

int main (void) {
    int i, j, find, prod;

    dumpArr ("Initial");

    // Sort array in O(1) - bucket sort.

    for (i = 0; i < SZ; i++) {
        arr[arr[i] % 100] += 100;
    }

最后我们用代码进行配对:

    dumpArr ("After bucket sort");

    // Now do pairings.

    find = 8;
    for (i = 0, j = find - i; i <= j; i++, j--) {
        if (i == j) {
            prod = (arr[i]/100) * (arr[i]/100-1) / 2;
            if (prod > 0) {
                printf ("(%d,%d) %d time(s)\n", i, j, prod);
            }
        } else {
            if ((j >= 0) && (j < SZ)) {
                prod = (arr[i]/100) * (arr[j]/100);
                if (prod > 0) {
                    printf ("(%d,%d) %d time(s)\n", i, j, prod);
                }
            }
        }
    }

    return 0;
}

输出为:

Initial:
   Indexes:  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
   Counts :  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   Values :  3  1  4  1  5  9  2  6  5  3  5  8  9  4  4  4  4
=====

After bucket sort:
   Indexes:  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
   Counts :  0  2  1  2  5  3  1  0  1  2  0  0  0  0  0  0  0
   Values :  3  1  4  1  5  9  2  6  5  3  5  8  9  4  4  4  4
=====

(2,6) 1 time(s)
(3,5) 6 time(s)
(4,4) 10 time(s)

如果你检查输入的数字,你会发现对是正确的。

 类似资料:
  • 因此,我试图编写一个python函数,它接受两个参数n和num,并计算在0和num之间出现的'n'。例如, 应为。

  • 问题内容: 我只需要找到1D中最小的第n个元素。 例如: 我想获得第五个最小的元素,所以我想要的输出是。 我当前的解决方案是这样的: 但是,找到5个最小的元素然后再选择最大的元素对我来说似乎很笨拙。有更好的方法吗?我是否缺少一个可以实现目标的功能? 有些问题的标题与此相似,但我没有看到任何答案。 编辑: 我本来应该提到它,但是性能对我来说很重要。因此,虽然不错的解决方案对我来说不起作用。 结果:

  • 本文向大家介绍在C ++中以小于O(n)的时间在有限范围的数组中查找每个元素的频率,包括了在C ++中以小于O(n)的时间在有限范围的数组中查找每个元素的频率的使用技巧和注意事项,需要的朋友参考一下 假设我们有一个整数数组。数组为A,大小为n。我们的任务是找到阵列中所有元素的频率小于O(n)时间。元素的大小必须小于一个等于M的值。在这里,我们将使用二进制搜索方法。在这里,如果末端元素不同,则将数组

  • 问题内容: 我知道我可以像下面这样: 但是,由于它做了完整的排序,所以它非常慢。 我想知道numpy是否提供一些可以快速完成的方法。 问题答案: 该模块具有一种快速的局部排序方法,可直接与Numpy数组配合使用:。 请注意,返回的是已排序的实际值,如果要使用已排序的值的索引(返回值),则应使用。 我已经进行了基准测试: 其中是一个随机的1,000,000个元素的数组。 时间安排如下: :每个循环2

  • 我有一个关于Hackkerrank的挑战如下 示例 票=[8,5,4,8,4] 已排序的有效子序列为{4,4,5}和{8,8}。这些子序列的m值分别为3和2。返回3。 功能描述 在下面的编辑器中完成maxTickets函数。 采样输入0 STDIN函数 4张票[]大小n=4 2 3 示例输出0 输出: 我的代码能用吗?有没有我的代码失败的情况?你能为这个挑战提供一个更好的算法吗?

  • 给定一个大小为n的数组,包含0和1以及两次运算,找出使所有元素都为0的最小运算次数。 操作: > < li> 如果第(I ^ 1)个元素为1,并且从第(I ^ 2)个到所有连续元素为0或不存在(如果I ^ 2 = n),则翻转第I个元素。(0 例如,上述操作适用于: 在这个元素上 1100年 或 在这个元素上 1011 n 例如,输入: 1,0,0,0 产量:15 解释: 1000 -