一、题目描述
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums =[0,1,0,3,12]
输出:[1,3,12,0,0]
示例 2:
输入: nums =[0]
输出:[0]
提示:
1 <= nums.length <= 10^4
-2^31 <= nums[i] <= 2^31 - 1
二、解题思路
这个问题可以通过双指针的方法来解决。我们可以定义两个指针,一个指针(我们可以称之为lastNonZeroFoundAt
)指向最后一个非零元素的索引,另一个指针(我们可以称之为cur
)用于遍历数组。
遍历数组时,如果当前元素不为零,则将其与lastNonZeroFoundAt
指向的元素交换,并将lastNonZeroFoundAt
指针向前移动一位。这样可以保证所有非零元素都被移动到了数组的前面,且相对顺序不变。遍历结束后,所有在lastNonZeroFoundAt
之后的位置都应该设置为0。
以下是具体的步骤:
- 初始化
lastNonZeroFoundAt
为0。 - 遍历数组,使用
cur
指针。 - 如果
nums[cur]
不为0,则交换nums[cur]
和nums[lastNonZeroFoundAt]
,并将lastNonZeroFoundAt
向前移动一位。 - 继续遍历,直到
cur
到达数组末尾。 - 从
lastNonZeroFoundAt
到数组末尾的所有位置设置为0。
三、具体代码
class Solution {
public void moveZeroes(int[] nums) {
if (nums == null || nums.length == 0) return;
int lastNonZeroFoundAt = 0;
// 将所有非零元素移动到数组前面
for (int cur = 0; cur < nums.length; cur++) {
if (nums[cur] != 0) {
// 交换当前非零元素与lastNonZeroFoundAt指向的元素
int temp = nums[cur];
nums[cur] = nums[lastNonZeroFoundAt];
nums[lastNonZeroFoundAt] = temp;
// 更新lastNonZeroFoundAt
lastNonZeroFoundAt++;
}
}
// 将lastNonZeroFoundAt之后的所有元素设置为0
for (int i = lastNonZeroFoundAt; i < nums.length; i++) {
nums[i] = 0;
}
}
}
这段代码实现了将所有0移动到数组末尾,并且保持了非零元素的相对顺序,同时没有使用额外的数组空间,满足了原地对数组进行操作的要求。
四、时间复杂度和空间复杂度
1. 时间复杂度
-
遍历数组:代码中有一个
for
循环,用于遍历数组中的每个元素。这个循环会执行nums.length
次,因此这部分的时间复杂度是O(n),其中n是数组的长度。 -
交换操作:在遍历数组的过程中,每当遇到一个非零元素,就会执行一次交换操作。在最坏的情况下(即数组中所有元素都是非零的),这个操作也会执行
nums.length
次。交换操作是常数时间的,因此这部分的时间复杂度同样是O(n)。 -
设置零操作:在遍历数组之后,有一个
for
循环用于将剩余的元素设置为0。在最坏的情况下(即数组中没有任何零),这个循环也会执行nums.length
次。每次设置操作是常数时间的,因此这部分的时间复杂度也是O(n)。
由于以上三部分操作是顺序执行的,因此总的时间复杂度是这三个操作的累加,即O(n) + O(n) + O(n) = O(n)。
2. 空间复杂度
-
临时变量:代码中使用了一个临时变量
temp
用于交换操作,这个变量占用了常数空间,因此空间复杂度是O(1)。 -
辅助空间:除了输入数组之外,代码没有使用任何额外的数据结构(如数组、列表等)来存储数据,因此没有使用额外的空间。
综上所述,总的空间复杂度是O(1),即代码使用了常数额外空间。
五、总结知识点
-
数组的操作:
- 数组元素的访问:通过索引直接访问数组元素。
- 数组元素的赋值:直接给数组元素赋值。
-
条件判断:
if
语句:用于判断数组元素是否为零。null
和length
属性检查:用于判断数组是否为空或长度为零。
-
循环结构:
for
循环:用于遍历数组中的所有元素。
-
指针/索引概念:
- 使用变量
lastNonZeroFoundAt
作为指针,指向数组中最后一个非零元素的索引位置。 - 使用变量
cur
作为当前遍历的索引。
- 使用变量
-
交换操作:
- 使用临时变量
temp
来交换两个数组元素的值。
- 使用临时变量
-
算法思想:
- 双指针(或双索引)技术:通过两个指针来维护数组中非零元素的位置。
-
原地算法:
- 原地修改数组,不使用额外的存储空间来保存结果。
-
边界条件处理:
- 在函数开始时检查输入数组的边界条件,即是否为
null
或长度为零。
- 在函数开始时检查输入数组的边界条件,即是否为
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。