leetcode 283. 移动零
题目描述:
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入:[0,1,0,3,12]
输出:[1,3,12,0,0]
说明:
- 必须在原数组上操作,不能拷贝额外的数组。
- 尽量减少操作次数。
思路分析:
之前刷题的思路通常都是看到简单题直接略过,后来发现其实很多简单题的一步步进阶解法才是在面试中更容易遇到的。首先去思考一个最直接粗暴的解法,再一步一步从空间和时间上做优化。比如这道题:
思路1. 由于必须在原数组上操作,最直接直观的解法,就是从后往前遍历数组,遇到0就将之前遍历过的元素往前移,在末尾补0。这样的时间复杂度为O(n2)。
思路2. 从思路1,我们可以发现,数组移动的过程实际是存在冗余的。这样就需要考虑如何将复杂度降到O(n)。这里发现实际只有两类元素,0和非0。为了保持非0元素的相对位置,可以考虑先从头存非0元素,那么余下的位置只要用0来填充即可。用到双指针,一个指针遍历数组,一个指针表示当前存下的非0元素,最后再从后一个指针开始直到数组长度的部分用一个for,利用0填充即可。
思路3. 注意到这里的另一个限制是尽量减少操作次数。可以发现思路2的实际总操作数为n(元素写入操作),当数组为{0,0,0,0,0,1}这种情况时,对于0的写入需要n-1次。实际可以再次优化。优化到只需要做非0元素个数的写入操作。同样利用双指针,指针i表示其之前的元素都非0,指针j与指针i之前的元素都为0,这样,每一次在j遇到非0元素时,只需要将两个指针所值元素交换,再后移两个指针即可。这样总的交换操作次数仅为非0元素个数。复杂度与思路2一致,但实际的操作相对思路2要少,最坏情况下,二者相同。
代码:
1 class Solution { 2 public: 3 void moveZeroes(vector<int>& nums) { 4 if(nums.size() == 0) 5 return; 6 int n = nums.size(); 7 int i = 0; 8 int j = 0; 9 while(i<n && j<n) 10 { 11 if(nums[j]!=0) 12 { 13 swap(nums[i], nums[j]); 14 i++; 15 j++; 16 } 17 else 18 { 19 j++; 20 } 21 } 22 } 23 };