【问题标题】:Sorting 10 million objects in O(n) time and O(1) extra memory在 O(n) 时间和 O(1) 额外内存中对 1000 万个对象进行排序
【发布时间】:2017-04-15 22:19:11
【问题描述】:

我正在尝试找到最近收到但无法解决的面试问题的答案。我被要求使用 O(1) 空间和 O(n) 时间按 10 位整数对 1000 万个对象(每个对象包含一个 10 位整数和一个唯一的字符串值)进行排序。出于所有密集目的,字符串值只会使问题变得更加困难,但您不会按它对对象进行排序。

我正在考虑使用计数排序,但只有在对 10 位整数进行排序时才有效。对对象进行排序会使事情变得更加复杂,因为我需要跟踪它们唯一的字符串值。我查看了基数排序,但它似乎使用 O(n) 作为最坏情况。

我是否缺少某种类型的东西?

【问题讨论】:

    标签: algorithm sorting runtime time-complexity space-complexity


    【解决方案1】:

    There's an in-place (MSB) radix sort.

    它是这样的:

    • 从第一位开始。
    • 将所有该位等于 0 的元素向左移动
      以及所有该位等于 1 的那些。

      这类似于快速排序,方法是从两侧移动到中间,将左侧的 1 与右侧的 0 交换。

    • 考虑下一位在左侧和右侧进行递归。

    这个过程是这样的:

            ↓                ↓
          0...             1...
    ---------------  ---------------
       ↓       ↓        ↓       ↓
     00...   01...    10...   11...
    ------- -------  ------- -------
     ↓   ↓   ↓   ↓    ↓   ↓   ↓   ↓
    000 001 010 011  101 110 110 111
    --- --- --- ---  --- --- --- ---
    ...
    

    时间复杂度为O(bits*n),空间复杂度为O(bits)。

    所以在位数不变的情况下,时间复杂度为O(n),空间复杂度为O(1)。



    也可以使用计数排序(具有相同的时间和空间复杂度)来做到这一点。

    • 计算每个元素的数量。
    • 使用它,您可以确定这些元素应出现在数组中的哪个位置。
    • 创建一个包含上述所有起点的查找表(将索引映射到偏移量,可以是一个数组),用于确定下一个元素应该去哪里。

    • 现在您遍历数组并将每个不合适的元素交换到它可以到达的第一个位置,然后对我们交换的每个元素执行相同的操作,直到当前元素位于它所属的位置。

      每一步增加相关元素在查找表中的偏移量。

      请注意,同一元素不可能交换两次,因此这将是线性时间。

    这听起来很令人困惑,所以这里有一个例子:

    4 5 3 1 2 3 4 4 1 5 4 3 2 3
    
    // counts:
    1: 2, 2: 2, 3: 4, 4: 4, 5: 2
    
    // the starting points (offsets) based on the counts:
    1 at position 0
    2 at position (offset of 1)+(count of 1) = 0+2 = 2
    3 at position (offset of 2)+(count of 2) = 2+2 = 4
    4 at position (offset of 3)+(count of 3) = 4+4 = 8
    5 at position (offset of 4)+(count of 4) = 8+4 = 12
    
    // sorting:
    4 5 3 1 2 3 4 4 1 5 4 3 2 3   // out of place, swap with offsets[4] = 8 (offsets[4]++)
    ^
    
    1 5 3 1 2 3 4 4 4 5 4 3 2 3   // in correct position, moving on         (offsets[1]++)
    ^
    
    1 5 3 1 2 3 4 4 4 5 4 3 2 3   // swap with offsets[5] = 12              (offsets[5]++)
      ^
    
    1 2 3 1 2 3 4 4 4 5 4 3 5 3   // swap with offsets[2] = 2               (offsets[2]++)
      ^
    
    1 3 2 1 2 3 4 4 4 5 4 3 5 3   // swap with offsets[3] = 4               (offsets[3]++)
      ^
    
    1 2 2 1 3 3 4 4 4 5 4 3 5 3   // swap with offsets[2] = 3               (offsets[2]++)
      ^
    
    1 1 2 2 3 3 4 4 4 5 4 3 5 3   // in correct position, moving on         (offsets[1]++)
      ^
    
    1 1 2 2 3 3 4 4 4 5 4 3 5 3   // in correct position, moving on         (offsets[2]++)
        ^
    

    你懂的……

    注意,计数表和查找表的大小为O(max value),如果每个数字包含固定位数,则为O(1)。

    你为每个元素做固定的工作量,所以这是O(n)时间。

    【讨论】:

    • 就地 MSD 基数排序是我正在寻找的,谢谢!
    • @Konrad 我根据计数排序为我的答案添加了另一个解决方案。
    【解决方案2】:

    可以使用标准的快速排序枢轴代码将整数 0 的所有项目移动到数组的开头。在其余元素上继续使用 1、2、3 等,一旦旋转到 1023 就停止。

    这会遍历数组 1024 次,每个枢轴需要 O(n) 时间,O(n) 也是如此。

    在实践中更有效的一种非常相似的方法是使用标准的三向快速排序算法。人们通常会认为这需要 O(n log n) 时间,并且在最坏的情况下使用 O(log n) 空间。但是在这里,整数的域被限制为 1024 个值,因此保证在 O(n) 时间内完成并使用 O(1) 空间,因为在对快速排序的每次递归调用中,枢轴值永远不会被使用两次。

    [这个答案做了一个假设——问题中的“1000万”是n,而“10位”保持不变]。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多