在 O(1) 空间和 O(n) 时间中重新排序的布尔数组

Posted

技术标签:

【中文标题】在 O(1) 空间和 O(n) 时间中重新排序的布尔数组【英文标题】:Boolean array reordering in O(1) space and O(n) time 【发布时间】:2015-06-25 18:26:38 【问题描述】:

问题取自Elements of Programming Interviews:

给定一个包含 n 个对象且具有布尔值键的数组 A,重新排序该数组以使键为 false 的对象首先出现。 键为 true 的对象的相对顺序不应改变。 使用 O(1) 额外空间和 O(n) 时间。

我做了以下,它保留了键为 true 的对象的相对顺序并使用了 O(1) 额外空间,但我相信它的时间复杂度是 O(n*n!)。

public static void rearrangeVariant4(Boolean[] a) 
  int lastFalseIdx = 0;
  for (int i = 0; i < a.length; i++) 
    if (a[i].equals(false)) 
      int falseIdx = i;
      while (falseIdx > lastFalseIdx) 
        swap(a, falseIdx, falseIdx-1);
        falseIdx--;
      
      lastFalseIdx++;
    
  

有人知道如何在 O(n) 时间内解决它吗?

【问题讨论】:

密切相关,Dutch National Flag Problem. 【参考方案1】:
boolean array[n]; // The array
int lastTrue = n;
for (int i = n-1; i >= 0; --i) 
  if (array[i]) 
    swap(array[--lastTrue], array[i]);
  

在每次迭代之后,lastTrue 之后的所有元素都为真。没有两个真正的元素被交换,因为如果在ilastTrue 之间有一个真正的元素,它就会已经遇到并移到lastTrue 后面。这在O(n) 时间和O(1) 内存中运行。

【讨论】:

非常好!干净高效! OP 只要求保留真实值之间的相对顺序。 @Ricky Don't fall into this trap。在任何情况下,我们都在谈论 Java,因此整数(和数组大小)被限制为 32 位,因此假设单个数字的操作和内存恒定时间更有用且更简单。 @Ricky 是的,这完全取决于您要使用的计算模型。事实证明,对于多种算法来说,假设单个整数占用 O(1) 空间和 O(1) 时间来进行简单的算术运算是最有用的。因此,对于许多问题,假设 reduce(arr, (t, e) -&gt; t + e) 为 O(n) 非常好,虽然是的,您也可以使用 O(nm) 且 m 为位大小 - 对于某些问题,这确实会产生很大的不同。跨度> 如果递减不是内联发生的,我会认为这是一种改进。 =/ 少思考,少混乱。【参考方案2】:

让数组有基于 0 的索引,并让它有 n 元素。然后你可以执行以下操作(下面的伪代码)

     // A[] is your array
     i = 0
     k = 0
     for i from 0 to n-1
          if A[i] is true
             swap A[i] and A[k]
             increment k

时间复杂度为O(n),额外空间仅用于两个变量ij,因此内存为O(1)。这样顺序被保留在 false 和 true 值之间。 (这个方法是把真实的放在第一位,你可以根据你的要求改变它)。

【讨论】:

是的,顺序被保留,但数组以真值而不是假开始。 (也许在 O(n) 时间内反转它会奏效吗?) 只需从前向后而不是从前向后。 或者只测试假值而不是真值。将if A[i] is true 更改为if A[i] is false @ubica:不。这将保留假键元素的顺序,同时将它们放在开头。您想要的是保留真键元素的顺序,同时将它们放在最后。 true 替换为false。这不是重新排序一些真实值吗?你需要插入而不是交换,不是吗?【参考方案3】:

观察到固定 k 的 2k 是 O(1),而 2n 是 O(n)。构造第二个数组,将源数组中的元素复制到目标数组,在一端添加键为false 的元素,在另一端添加键为true 的元素。您可以扫描一次数组以找出边界必须在哪里。

【讨论】:

OP 需要 O(1) 额外空间。如何在 O(1) 空间中创建一个额外的数组。 k 指的是什么?【参考方案4】:

Java 代码 - 如果您使用的是 Boolean[] - 对象:

时间 - O(1),空间 - O(1)

public static <T> void swap(T[] a, int i, int j) 
    T t = a[i];
    a[i] = a[j];
    a[j] = t;

时间 - O(N),空间 - O(1)

public static Boolean[] getReorderBoolObjects(Boolean[] array) 
    int lastFalse = 0;

    for (int i = 0; i < array.length; i++) 
        if (!array[i]) 
            swap(array, lastFalse++, i);
        
    

    return array;

Spock测试用例:

def "reorder bools - objects"() 
    given:
    Boolean[] b = [false, true, true, true, false, true]

    when:
    getReorderBoolObjects(b)

    then:
    b == [false, false, true, true, true, true]

Java 代码 - 如果您使用的是 boolean[] - 原语:

时间 - O(N),空间 - O(1)

public static boolean[] getReorderBoolPrimitives(boolean[] array) 
    int falseCount = 0;
    for (final boolean bool : array) 
        if (!bool) 
            falseCount++;
        
    
    for (int i = 0; i < array.length; i++) 
        array[i] = i >= falseCount;
    
    return array;

Spock测试用例:

def "reorder bools - primitives"() 
    given:
    boolean[] b = [false, true, true, true, false, true]

    when:
    getReorderBoolPrimitives(b)

    then:
    b == [false, false, true, true, true, true]

【讨论】:

数组包含带有布尔字段的对象。需要将整个对象交换到新位置,而不仅仅是真值。 "给定一个包含布尔值键的 n 个对象的数组 A" @EdwardDoolittle 你说得对,我读的问题标题是boolean[] 它不保留顺序。我知道布尔值太原始了,无法看到不保留真实元素顺序的后果。【参考方案5】:
public static void rearrange(boolean[] arr) 
          int invariant = arr.length-1;
          for (int i = arr.length -1; i >= 0; i --) 
            if ( !arr[i] ) 
              swap( arr,i,invariant);
              invariant--;
            
          
    
    private static void swap(boolean arr[] , int from ,int to)
        boolean temp = arr[from];
        arr[from]=arr[to];
        arr[to]=temp;
    

【讨论】:

虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。

以上是关于在 O(1) 空间和 O(n) 时间中重新排序的布尔数组的主要内容,如果未能解决你的问题,请参考以下文章

常用的排序算法的时间复杂度和空间复杂度

常用的排序算法的时间复杂度和空间复杂度

常用的排序算法的时间复杂度和空间复杂度

常用的排序算法的时间复杂度和空间复杂度

为啥计数排序的时间和空间复杂度是 O(n + k) 而不是 O(max(n, k))?

九大排序算法时间复杂度空间复杂度稳定性