【问题标题】:Sorting in O(n) intersect在 O(n) 中排序
【发布时间】:2017-01-19 13:08:06
【问题描述】:
  • S1S2 是两组整数(它们不一定不相交)。
  • 我们知道 |S1| = |S2| = n(即每个集合都有 n 整数)。
  • 每个集合都存储在一个长度为n 的数组中,其中 它的整数按升序排序。
  • k ≥ 1 为整数。
  • 设计一个算法来找到 k 在 O(n) 时间内 S1 ∩ S2 中的最小整数。

这是我目前所拥有的:

  1. 创建一个名为Intersection 的新数组
  2. 对于S1 中的每个e,将e 添加到哈希集在O(n) 时间内
  3. 对于S2 中的每个e,检查e 是否存在于哈希集中在O(n) 时间内
  4. 如果e 存在于哈希集中,则将e 添加到Intersection
  5. 完成比较后,按计数排序 Intersection 在 O(n) 时间内
  6. 返回第一个 k 整数

因此 O(n) + O(n) + O(n) = O(n)

我走对了吗?

【问题讨论】:

  • 由于数组已经按升序排序,这可以使用 2-pointer 技术来完成(几乎与 mergesort 中的合并过程相同)。
  • 哈希是杀死苍蝇的锤子。使用合并操作。
  • @Yves:合并是使用稍微更小的锤子来杀死苍蝇 - 更好,但它仍然是锤子 :-) 没有必要合并 any 方式,它可以在 O(n) 时间和 O(1) 空间内完成。当然,除非根据我的回答,您的意思是“虚拟”合并操作,在这种情况下,我提前道歉。
  • @paxdiablo:如果结果必须可用于进一步处理,则合并是强制性的,绝不是矫枉过正。顺便说一句,用追加替换你的打印就足够了,你会得到完全相同的算法。
  • 计数排序很可能是不合适的,因为它的复杂度不是 O(n) 而是 O(n+m),其中 m 是键范围的大小。通常,m>>n。

标签: algorithm sorting time-complexity hashset


【解决方案1】:

创建两个数组,分别命名为arr1arr2,大小为array_size,并按升序使用整数值填充它们。创建两个索引,分别命名为ij,分别用于迭代arr1arr2并将它们初始化为0。比较每个数组的前两个值:如果arr1[0]小于比arr2[0] 增加i,否则如果arr1[0] 大于arr2[0] 增加j,否则这些值相交,我们可以返回这个值。一旦我们返回了 k 个相交的值,我们就可以停止迭代。在最坏的情况下,这将是 i + j, O(n) 如果两组值之间没有发生交集,我们将不得不迭代到每个数组的末尾。

这是 bash 中的解决方案:

#!/bin/bash
#-------------------------------------------------------------------------------
# Design an algorithm to find the k smallest integers in S1 ∩ S2 in O(n) time.
#-------------------------------------------------------------------------------

typeset -a arr1 arr2 arr_answer
typeset -i array_size=20 k=5

function populate_arrs {
    typeset -i counter=0

    while [[ ${counter} -lt ${array_size} ]]; do
        arr1[${counter}]=$((${counter} * 2))
        arr2[${counter}]=$((${counter} * 3))

        counter=${counter}+1
    done

    printf "%8s" "Set1: "; printf "%4d" ${arr1[*]}; printf "\n"
    printf "%8s" "Set2: "; printf "%4d" ${arr2[*]}; printf "\n\n"
}


function k_smallest_integers_main {
    populate_arrs

    typeset -i counter=0 i=0 j=0
    while [[ ${counter} -lt ${k} ]]; do
        if [[ ${arr1[${i}]} -eq ${arr2[${j}]} ]]; then
            arr_answer[${counter}]=${arr1[${i}]}
            counter=${counter}+1; i=${i}+1; j=${j}+1
        elif [[ ${arr1[${i}]} -lt ${arr2[${j}]} ]]; then
            i=${i}+1
        else
            j=${j}+1
        fi
    done

    printf "%8s" "Answer: "; printf "%4d" ${arr_answer[*]}; printf "\n"
}

k_smallest_integers_main

输出:

  Set1:    0   2   4   6   8  10  12  14  16  18  20  22  24  26  28  30  32  34  36  38
  Set2:    0   3   6   9  12  15  18  21  24  27  30  33  36  39  42  45  48  51  54  57

Answer:    0   6  12  18  24

【讨论】:

  • 只是一个小问题,O(i+j) 似乎表明了对复杂性分析的根本误解。这只是O(n),最坏的情况。更准确地说,最坏的情况是i + j“steps”,而不是在组合中引入 big-O。
  • @paxdiablo 谢谢,已更正,不是像 O(m+n) 这样的东西只是 O(n) 还是我在这里遗漏了什么?
【解决方案2】:

是的,您肯定走在正确的轨道上,但实际上根本不需要生成哈希表或额外的集合。由于您的两个集合已经排序,您可以简单地通过它们运行一个索引/指针,寻找共同的元素。

例如,要从两个集合中查找第一个公共元素,请使用以下伪代码:

start at first index of both sets
while more elements in both sets, and current values are different:
    if set1 value is less than set2 value:
        advance set1 index
    else
        advance set2 index

最后,set1 index 将引用一个交叉点,前提是两个索引都没有超出各自列表中的最后一个元素。然后,您可以在循环中使用该方法来查找第一个 x 交点值。

这是 Python 3 中的概念证明,它为您提供了两个列表中的前三个数字(二的倍数和三的倍数)。完整的交集将是 {0, 6, 12, 18, 24},但您会看到它只会提取其中的前三个:

# Create the two lists to be used for intersection.

set1 = [i * 2 for i in range(15)] ; print(set1) # doubles
set2 = [i * 3 for i in range(15)] ; print(set2) # trebles

idx1 = 0 ; count1 = len(set1)
idx2 = 0 ; count2 = len(set2)

# Only want first three.

need = 3
while need > 0:
    # Continue until we find next intersect or end of a list.

    while idx1 < count1 and idx2 < count2 and set1[idx1] != set2[idx2]:
        # Advance pointer of list with lowest value.

        if set1[idx1] < set2[idx2]:
            idx1 += 1
        else:
            idx2 += 1

    # Break if reached end of a list with no intersect.

    if idx1 >= count1 or idx2 >= count2:
        break

    # Otherwise print intersect and advance to next list candidate.

    print(set1[idx1]) ; need -= 1
    idx1 += 1 ; idx2 += 1

输出如预期:

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42]
0
6
12

如果您需要一个列表而不是仅仅打印出交叉点,您只需在循环之前初始化一个空容器并将值附加到它而不是打印它。然后,这变得更像您提出的解决方案,但具有不需要哈希表或排序的优点。

【讨论】:

    【解决方案3】:

    在 Python 中:

    i1= 0; i2= 0
    while k > 0 and i1 < n and i2 < n:
        if S1[i1] < S2[i2]:
            i1+= 1
        elif S1[i1] > S2[i2]:
            i2+= 1
        else:
            Process(S1[i1], S2[i2])
            i1+= 1; i2+= 1
            k-= 1
    

    如果交集中的元素不够多,执行将执行少于kProcess 的调用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-01
      • 2022-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多