【问题标题】:How to find 2-sum in linear time?如何在线性时间内找到 2-sum?
【发布时间】:2025-11-29 00:40:01
【问题描述】:

我报名了 Coursera 上的Algorithms, Part II 课程,其中一道面试题(未评分)如下:

2-sum。给定一个由n 64 位整数组成的数组a 和一个目标值T, 确定是否存在两个不同的整数 ij 使得 a[i] + a[j] = T。你的算法应该在最坏的情况下以线性时间运行 案例。

提示:按线性时间对数组进行排序。

我可以想办法解决这个问题:

  1. 一次性将元素插入哈希表。然后进行第二遍,在哈希表中寻找T - a[i]。哈希表的空间复杂度为 O(n),2 遍的空间复杂度为 O(n)。此解决方案满足问题中所述的时间要求。

  2. 对数组进行排序,然后分别从开头和结尾运行2个指针ij,寻找a[i] + a[j] = T。如果a[i] + a[j] < T,则增加i,否则减少j。空间复杂度取决于排序算法;假设,快速排序,不需要额外的空间。时间复杂度,nlogn,所以它没有达到问题中所述的时间要求。

  3. 考虑到问题是在基数排序讲座之后给出的,我假设意图是使用基数排序之一。由于问题指定了 64 位整数,使用 long 的二进制表示并使用 In-place MSD radix sort,因此可以在线性时间内对数组进行就地排序。这似乎是最好的方法。

其他想法?

附:我看过这个question,但它假定一个排序数组,并且那里的所有答案都使用某种散列。

我也见过这个question,但对于 2-sum 的具体情况来说,它似乎过于复杂。

【问题讨论】:

  • @lexicore 标记的问题不是重复的,因为数组被指定为在那里而不是在这里排序!
  • 我投票支持重新开放。
  • 你能分享你尝试过的代码吗?
  • @pamcevoy 没必要,这是关于算法的问题,应该很清楚,我已经考虑过了。您有什么要补充的吗?
  • 使用基数排序,您可能可以立即从初始数组中拒绝一些高数字。例如,如果 T = 15,那么任何具有 5 位或更高位的数字都可以立即被拒绝,从而缩短要搜索匹配项的数组。

标签: java arrays algorithm sorting data-structures


【解决方案1】:

我分析了您的观察结果,并分析了似乎符合要求的观察结果:

一次性在哈希表中插入元素。然后进行第二次传球, 在哈希表中寻找 T - a[i]。空间复杂度为 O(n) 哈希表,以及 2 遍的 O(n)。该解决方案符合 问题中说明的时间要求。

我认为这种方法不符合要求,因为理论上hashtable 最坏情况插入复杂度为 O(n)。

正如您所说您学习了基数排序,我相信这是要走的路,您可以在时间要求内对数组进行排序,然后您可以使用Two Pointers技术检查是否有和T

int left = 0, right = array_size - 1;
boolean found = false;
while (left < right) {

    if (a[left] + a[right] == T) {              
        found = true;
    }
    else if (a[left] + a[right] < T) {
        left++;
    }
    else {
        right--;
    }
}

【讨论】:

  • 最坏情况复杂度只取决于哈希函数的兼容性或不兼容性。如您所知,如果散列函数不好,整个散列表是“无用的”。在这些场景中,我们难道不能理所当然地认为散列函数完成了它的工作并且复杂性确实是 O(1) 摊销的吗?
  • @JohEker 我们可以讨论这个话题,但无论如何我不认为这是解决这个问题的方法。提示表明需要进行排序。
  • 无法想象整数的哈希函数会退化为 O(n)。虽然理论上是可行的,但我们知道允许的输入范围,以及 Java OOTB 哈希码可以正常工作的事实。如果我们不知道输入,那么也许我会担心哈希冲突。也就是说,我也倾向于 Radix 排序,因为它是那一章的学习练习。
  • @AbhijitSarkar 我同意,但是,很明显,排序是要走的路。是否按预期解决了您的问题?
  • 我支持你,但是我的问题中已经提到了你使用基数排序的建议,所以没有工作代码,我不觉得提供了额外的东西。我为那些将来可能会偶然发现这篇文章的人发布了工作代码。
【解决方案2】:

当您遍历数组时,将值放入一个哈希表中,将值映射到索引。由于我们只求两个数之和,因此在哈希中求当前数与余数之和即可得到目标。

public static int[] twoSumInOnePass(int[] values, int target) throws Exception {
    // value => index
    Map<Integer, Integer> valueToIndexMap = new HashMap<Integer, Integer>();
    for (int i = 0; i < values.length; i++) {
        int remainder = target - values[i];
        if (valueToIndexMap.containsKey(remainder)) {
            return new int[] { valueToIndexMap.get(remainder), i };
        }
        valueToIndexMap.put(values[i], i);
    }

    throw new Exception("Could not find indexes that sum to " + target);
}

https://leetcode.com/problems/two-sum/description/

【讨论】:

  • 上述与我帖子中的解决方案 #1 有何不同?
  • 这只是 1 次传球。这些值在我们进行时插入并在同一通道中检查。在每个索引处,我们询问是否存在 a[j] 且 j
【解决方案3】:

回答我自己的问题,这是 Java 中的有效解决方案:

public class RadixSortsInterviewQuestions {
    private static final int MSB = 64;

    static Map.Entry<Integer, Integer> twoSum(long[] a, long sum) {
        int n = a.length - 1;
        sort(a, MSB, 0, n);

        for (int i = 0, j = n; i < j; ) {
            long t = a[i] + a[j];
            if (t == sum) {
                return new SimpleImmutableEntry<>(i, j);
            } else if (t < sum) {
                i++;
            } else {
                j--;
            }
        }
        return null;
    }

    // Binary MSD radix sort: https://en.wikipedia.org/wiki/Radix_sort#In-place_MSD_radix_sort_implementations
    private static void sort(long[] a, int d, int lo, int hi) {
        if (hi < lo || d < 1) return;

        int left = lo - 1;
        int right = hi + 1;

        for (int i = left + 1; i < right; ) {
            if (isBitSet(a[i], d)) {
                swap(a, i, --right);
            } else {
                left++;
                i++;
            }
        }
        sort(a, d - 1, lo, left);
        sort(a, d - 1, right, hi);
    }

    private static boolean isBitSet(long x, int k) {
        boolean set = (x & 1L << (k - 1)) != 0;

        // invert signed bit so that all positive integers come after negative ones
        return (k == MSB) != set;
    }

    private static void swap(long[] a, int i, int j) {
        long tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
}

【讨论】: