刷题 | 数组移动元素
题目描述
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1
解法1
算法思想:
- 创建一个数组tmps用于存储nums中所有的非零元素(按原有顺序存储),遍历nums,将非零元素存于tmps中。
- 遍历tmps,将tmps中元素覆盖nums元素(从0号下标开始依次覆盖)。
- 将nums剩余位置元素置零。
时间复杂度=O(n)
空间复杂度=O(n)
代码实现:
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
void moveZeroes(vector<int>& nums) {
vector<int> tmps;
int i;
for(i = 0; i < nums.size(); i++){
if(nums[i] != 0){
tmps.push_back(nums[i]);
}
}
for(i = 0; i < tmps.size(); i++){
nums[i] = tmps[i];
}
for(; i < nums.size(); i++){
nums[i] = 0;
}
}
};
int main()
{
int arr[] = {0, 1, 0, 3, 12};
vector<int> vec(arr, arr + sizeof(arr) / sizeof(int));
Solution().moveZeroes(vec);
for(int i = 0; i < vec.size(); i++){
cout << vec[i] << " ";
}
cout << endl;
system("pause");
return 0;
}
运行效果:
1 3 12 0 0
解法2
算法思想:
原地操作,不开辟新的内存空间
-
声明两个指针i和k,i:用于遍历数组,k:保证[0...k)中保存所有当前已遍历过的所有非0元素;
即:遍历完第i个元素后,[0...i]中所有非0元素都按照原有顺序排列再[0...k)中。
-
若当前元素nums[i]为0元素,忽略,继续向前遍历;
-
若当前元素nums[i]为非0元素,将该元素插入到nums[k]的位置,然后i++,k++;
-
重复上述操作,直至指针i遍历结束;
-
从位置k开始遍历数组,将元素值置零。
代码实现:
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int i, k = 0;
for(i = 0; i < nums.size(); i++){
if(nums[i] == 0){
continue;
}else{
nums[k++] = nums[i];
}
}
for(i = k; i < nums.size(); i++){
nums[i] = 0;
}
}
};
相当于把数组nums分成左右两部分:
- 左部分:已处理元素集合,存储数组中已经处理完毕的非零元素(处理完毕即元素均为非0元素且按原有顺序排列),左右边界为[0...k),初始时k=0代表左部分为空;
- 右部分:待处理元素集合,左右边界为[k...nums.size()-1],初始时k=0代表整个数组。
解法3:
算法思想:
在解法2的基础上进一步优化,去掉解法2最后一步将数组剩余元素置零的操作。
-
声明两个指针i和k,i:用于遍历数组,k:保证[0...k)中保存所有当前已遍历过的所有非0元素;
即:遍历完第i个元素后,[0...i]中所有非0元素都按照原有顺序排列再[0...k)中。
-
若当前元素nums[i]为0元素,忽略,继续向前遍历;
-
若当前元素nums[i]为非0元素,将该元素与nums[k]对应元素交换位置,然后i++,k++;
-
重复上述操作,直至指针i遍历结束;
代码实现:
class Solution {
public:
void swap(int &a, int &b){
int tmp = a;
a = b;
b = tmp;
}
void moveZeroes(vector<int>& nums) {
int i, k = 0;
for(i = k; i < nums.size(); i++){
if(nums[i] == 0){
continue;
}else{
swap(nums[k++], nums[i]);
}
}
}
};
进一步优化:
若数组nums全是非零元素,按照上述代码,每个元素(均为非0元素)都会和自己执行一次交换,耗时。
class Solution {
public:
void swap(int &a, int &b){
int tmp = a;
a = b;
b = tmp;
}
void moveZeroes(vector<int>& nums) {
int i, k = 0;
for(i = k; i < nums.size(); i++){
if(nums[i] == 0){
continue;
}else{
if(nums[k] == 0){
swap(nums[k++], nums[i]);
}else{
k++;
}
}
}
}
};
相似题目
leetcode.27. 移除元素
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int k = 0;
for(int i = k; i < nums.size(); i++){
if(nums[i] != val){
nums[k++] = nums[i];
}
}
return k;
}
};
leetcode.26. 删除有序数组中的重复项
/*
算法思想:
声明指针k,将数组划分成两部分:nums[0...k)为已处理序列,nums[k...]为待处理序列,k初始时为0
声明指针i遍历数组:
1. 若已处理序列为空(即k==0),则将当前元素nums[i]插入到k位置,并执行k++;
2. 若已处理序列非空,将当前元素nums[i]与已处理序列尾元素nums[k-1]比较:
(1)若二者相等,忽略当前元素,继续遍历;
(2)若二者不等,将当前元素nums[i]插入到k位置,并执行k++。
*/
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int k = 0;
for(int i = 0; i < nums.size(); i++){
if(k == 0){
nums[k++] = nums[i];
}else{
if(nums[i] != nums[k-1]){
nums[k++] = nums[i];
}
}
}
return k;
}
};
leetcode.80. 删除有序数组中的重复项 II
将leetcode.26中的nums[i] != nums[k-1]
改为nums[i] != nums[k-2]
即可。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int k = 0;
for(int i = 0; i < nums.size(); i++){
if(k == 0){
nums[k++] = nums[i];
}else{
if(nums[i] != nums[k-2]){
nums[k++] = nums[i];
}
}
}
return k;
}
};