Easy
题目描述
给你一个长度固定的整数数组 arr,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。
注意:请不要写超出该数组长度的元素。要求:请对输入的数组就地修改,不要从函数返回任何东西。
示例 1:
输入:arr = [1,0,2,3,0,4,5,0]
输出:[1,0,0,2,3,0,0,4]
解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]
示例 2:
输入:arr = [1,2,3]
输出:[1,2,3]
解释:调用函数后,输入的数组将被修改为:[1,2,3]
提示:
1 <= arr.length <= 10^40 <= arr[i] <= 9
解题思路
这道题要求在原数组上就地修改,将每个零复写一遍。核心挑战是避免覆盖还未处理的元素。
方法一:辅助数组(简单直观)
- 创建临时数组,遍历原数组,遇到零就写入两个零,遇到非零就写入原值
- 将临时数组的内容复制回原数组
- 时间复杂度O(n),空间复杂度O(n)
方法二:双指针法(推荐) 这是最优解法,真正实现就地修改:
- 第一次遍历:统计零的个数,计算扩展后的长度
- 第二次遍历:从后往前处理,避免覆盖未处理的元素
- 使用两个指针:
i指向原数组末尾,writeIdx指向扩展后的写入位置 - 如果当前元素是零,写入两个零;否则写入原值
- 从右往左处理确保不会覆盖还需要处理的元素
- 使用两个指针:
关键思路:通过计算扩展长度,确定每个元素的最终位置,然后反向填充,巧妙避免了元素覆盖问题。
代码实现
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
int n = arr.size();
int zeroCount = 0;
// 统计零的个数
for (int num : arr) {
if (num == 0) zeroCount++;
}
// 从后往前处理
int i = n - 1;
int writeIdx = n + zeroCount - 1;
while (i >= 0) {
if (arr[i] != 0) {
// 非零元素
if (writeIdx < n) {
arr[writeIdx] = arr[i];
}
} else {
// 零元素,需要写入两个零
if (writeIdx < n) {
arr[writeIdx] = 0;
}
writeIdx--;
if (writeIdx < n) {
arr[writeIdx] = 0;
}
}
i--;
writeIdx--;
}
}
};
class Solution:
def duplicateZeros(self, arr: List[int]) -> None:
"""
Do not return anything, modify arr in-place instead.
"""
n = len(arr)
zero_count = arr.count(0)
# 从后往前处理
i = n - 1
write_idx = n + zero_count - 1
while i >= 0:
if arr[i] != 0:
# 非零元素
if write_idx < n:
arr[write_idx] = arr[i]
else:
# 零元素,需要写入两个零
if write_idx < n:
arr[write_idx] = 0
write_idx -= 1
if write_idx < n:
arr[write_idx] = 0
i -= 1
write_idx -= 1
public class Solution {
public void DuplicateZeros(int[] arr) {
int n = arr.Length;
int zeroCount = 0;
// 统计零的个数
foreach (int num in arr) {
if (num == 0) zeroCount++;
}
// 从后往前处理
int i = n - 1;
int writeIdx = n + zeroCount - 1;
while (i >= 0) {
if (arr[i] != 0) {
// 非零元素
if (writeIdx < n) {
arr[writeIdx] = arr[i];
}
} else {
// 零元素,需要写入两个零
if (writeIdx < n) {
arr[writeIdx] = 0;
}
writeIdx--;
if (writeIdx < n) {
arr[writeIdx] = 0;
}
}
i--;
writeIdx--;
}
}
}
var duplicateZeros = function(arr) {
let zeros = 0;
let n = arr.length;
for (let i = 0; i < n - zeros; i++) {
if (arr[i] === 0) {
if (i === n - zeros - 1) {
arr[n - 1] = 0;
n--;
break;
}
zeros++;
}
}
let last = n - zeros - 1;
for (let i = n - 1; i >= 0; i--) {
if (arr[last] !== 0) {
arr[i] = arr[last];
} else {
arr[i] = 0;
i--;
arr[i] = 0;
}
last--;
}
};
复杂度分析
| 复杂度类型 | 双指针法(推荐) | 辅助数组法 |
|---|---|---|
| 时间复杂度 | O(n) | O(n) |
| 空间复杂度 | O(1) | O(n) |
说明:
- 双指针法是最优解,实现了真正的就地修改
- 辅助数组法虽然思路简单,但需要额外空间
- 两种方法时间复杂度相同,但空间复杂度差异明显