方法一:双指针遇 0 交换
1. 基本思路回顾
该方法使用了两个指针m
和i
,m
用于标记当前已经处理好的非零元素应该放置的位置,i
用于遍历整个数组。当遇到nums[m]
为0
时,会通过内层while
循环找到下一个非零元素(如果存在的话),然后将这个非零元素与nums[m]
进行交换,使得非零元素不断往前移动,最终实现将所有零元素移到数组末尾的效果。
2. 时间复杂度分析
- 最好情况:数组本身就是已经将非零元素排在前面,零元素排在后面的有序状态,此时内层
while
循环每次都不会执行,整个算法只需要遍历一遍数组,时间复杂度为 ,其中n
是数组nums
的长度。 - 最坏情况:数组中所有元素都是
0
,那么对于每个0
元素(也就是每次nums[m]
为0
时),内层while
循环都要一直执行到数组末尾才能找到下一个非零元素(实际上不存在),此时时间复杂度会达到 。平均情况下,时间复杂度介于最好和最坏情况之间,更接近 ,因为存在较多0
元素的情况是比较容易出现的,且每次找下一个非零元素都可能需要遍历剩余的部分数组。
3. 空间复杂度分析
该方法只使用了有限的额外变量(如m
、i
以及一些临时用于判断和交换的变量),没有使用与输入数组规模相关的额外数据结构,所以空间复杂度为 ,属于原地算法,不需要额外的大量存储空间。
4. 相对不足
- 效率不稳定:时间复杂度在不同输入情况下差异较大,特别是在存在较多
0
元素的数组中,效率会变得很低,因为存在大量的内层循环遍历操作。 - 代码逻辑稍复杂:存在内层
while
循环以及多个边界条件判断(如判断i
是否越界等),代码的可读性和可维护性相较于更简洁的实现方式会稍差一些,容易出现边界情况考虑不周全导致的错误。
方法二:双指针交换非零元素
1. 基本思路回顾
这个方法同样使用了两个指针i
和j
,i
用于遍历整个数组,j
用于标记下一个可以放置非零元素的位置。当nums[i]
不为0
时,就将nums[i]
与nums[j]
进行交换,然后j
自增,保证了非零元素会按照顺序依次放置在数组的前部,最终也能实现将零元素移到数组末尾的目的。
2. 时间复杂度分析
无论输入数组的元素分布情况如何,该方法都只需要遍历一遍数组即可完成零元素的移动操作,对于每个元素,最多执行一次交换操作,所以时间复杂度稳定为 ,效率比较稳定,不受数组中零元素具体分布情况的影响。
3. 空间复杂度分析
和方法一类似,只使用了有限的几个额外变量(如i
、j
以及用于交换的临时变量tmp
),空间复杂度为 ,也是原地算法,不会占用额外大量的内存空间。
4. 相对不足
- 功能扩展性稍弱:该方法的代码结构比较简洁地实现了移动零元素这个特定功能,但如果后续需要在移动零元素的同时对非零元素进行更复杂的处理(比如按照特定规则排序非零元素等),可能需要对代码结构进行较大幅度的修改,不像一些更通用的排序或调整算法那样容易扩展功能。
- 通用性局限:相对一些通用的数组元素调整算法而言,它只针对移动零元素这个特定需求进行了优化,如果需求变更为移动其他特定值或者按照更复杂规则调整数组元素顺序,可能无法直接复用,需要重新编写逻辑。
总体比较
综合来看,方法二在时间复杂度方面表现更优,具有稳定的 时间复杂度,能高效地处理各种输入情况。而方法一虽然也能实现功能,但时间复杂度受输入数据影响较大,最坏情况效率很低。在代码可读性和可维护性上,方法二相对简洁清晰一些,方法一由于复杂的边界情况处理和嵌套循环使得代码更易出错。不过在一些特定场景下,如果代码所在的环境对空间复杂度要求极为苛刻且输入数据基本能保证较好的情况(比如基本不会出现大量连续0
的情况),方法一也是可以考虑使用的一种实现方式。