Move Zeroes

早晨在地铁上试着求解和证明 Move Zeroes 的算法。

Given an array nums, write a function to move all 0’s to the end of it while maintaining the relative order of the non-zero elements.

For example, given nums = [0, 1, 0, 3, 12], after calling your function, nums should be [1, 3, 12, 0, 0].

Note: You must do this in-place without making a copy of the array. Minimize the total number of operations.

解:

令 A: 移动前的数组; A’: 移动后的数组(加’表示原地替换); k 表示 0 的个数, i 表示 A 中的元素索引, j 表示 A’ 中的非0元素索引。 n 表示 A 的元素个数

根据题目要求,我们易得,k = i - j,即 A的第i个元素,它必然等于A’的第j个元素,i - j 的差值就是出现 0 的个数:

P0: k = i - j

我们还有以下两个约束条件:

P1: 任意 0 < k,  A'(n - k) = 0
P2: 对于任意的 0 <= j <= i < n,  A'(j) = A(i) != 0

特别地,初始时,即 i = j = k = 0 时,A’(j) 必然要么满足 P0,要么满足 P2,也是满足约束条件的。

我们使用 0<=i < n 作为循环,P0, P2 应满足的条件作为两个分支应满足的条件,写出算法的骨架:

int i, j, k
i := j := k := 0

do i < n ->
    if A(i) = 0 -> // x1: 这里应满足 P0
    | A(i) != 0 -> // x2: 这里应满足 P2
    fi
    i := i + 1; //x3: 使循环往终止方向推进
od

我们来推导一下 x1, x2.

由于 x3 的存在,如果什么都不做,会使 x1, x2 不满足 P0, P2。 相应的,我们需要使 x1, x2 重新满足 P0, P2。

x1: 只需要加一句 k := k+1 ,就能得到 (k + 1) = (i + 1) - j ,重新满足 P0 x2: 我们必须让 A(j + 1) := A(i + 1) 才能使 P2 重新得到满足,另外,还需要加一句 j := j + 1, 才能得到 k := (i + 1) - (j + 1), 重新满足 P0

当程序运行到这里的时候, P1 全部值域仍然尚未未能得到满足,因此有必要再来一个循环不变式,遍历一次 P1 的值域。

do k > 0 ->  
    // x4: 满足 P1
    k := k - 1;
od

易得,x4 是 A(n - k) := 0;

综上,整个算法为

int i, j, k
i := j := k := 0

do i < n ->
    if A(i) = 0 ->
        k := k+1;
    | A(i) != 0 ->
        A(j + 1) := A(i + 1); j = j + 1;
    fi
    i := i + 1; 
od

do k > 0 ->  
    A(n - k) := 0; 
    k := k - 1;
od

至此,该算法完成,正确性也得到了证明。