題目:
給定一個整數數組 nums,編寫一個算法將所有的0移到數組的末尾,同時保持非零元素的相對順序。
示例:
輸入: [0,1,0,3,12]
輸出: [1,3,12,0,0]
注意:必須在原數組上操作,不能拷貝額外的數組。盡量減少操作次數。
這個問題考察候選人處理數組的能力,以及他們編寫高效、優雅代碼的能力。在解決問題時,請考慮如何避免不必要的元素移動。
思路:
1、雙指針法:
使用兩個指針,一個用來遍歷數組(遍歷指針),另一個指向最新發現的非零元素應當存放的位置(非零指針)。
當遍歷指針指向的值不為0時,將其與非零指針指向的值交換,然后非零指針前移。
通過整個數組的遍歷,所有非零元素都被推向數組的開頭,而0都被留在了數組的末尾。
2、計數零元素法:
首先遍歷一次數組,統計出0的個數。
在第二次遍歷期間,使用一個新的索引(比如 insertPos),它基于0的計數從數組開頭開始移動。
如果遍歷到非零元素,就將它放到insertPos的位置,并將insertPos向前移動。
遍歷完成后,按照統計出的0的個數,在數組末尾補上0。
3、移動非零元素法:
遍歷數組,一旦遇到非0數,就將其移到數組最前方現有非0數的后面。
記錄最后一個非0數的位置。
在數組剩余的部分填充0。
每種方法都有其特點,但雙指針法在空間和操作復雜度上通常是最優的。這個方法只需要( O(n) )的時間復雜度和( O(1) )的額外空間復雜度。
時間復雜度
1. **雙指針法**:時間復雜度為 ( O(n) ),因為只需要遍歷一遍數組,n 為數組長度。
2. **計數零元素法**:時間復雜度為 ( O(n) ),即便需要兩次遍歷(一次計數0的個數,一次移動非零元素),但兩次遍歷的時間復雜度都是線性的。
3. **移動非零元素法**:時間復雜度也為 ( O(n) ),一次線性遍歷即可完成所有非零元素的移動和0的填充。
值得注意的是,盡管所有方法的時間復雜度都是線性的,但實際執行時間可能會因為常數因子和元素實際移動次數的差異而有所不同。在決定使用哪一種方法時,這也是需要考慮的因素之一。
實現代碼
1、雙指針法
#include <stdio.h>
void moveZeroes(int* nums, int numsSize) {int j = 0; // 指向當前非0元素應該插入的位置for (int i = 0; i < numsSize; ++i) {if (nums[i] != 0) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;j++;}}
}
2、計數零元素法:
#include <stdio.h>
void moveZeroes(int* nums, int numsSize) {int zeroCount = 0; // 計算數組中0的個數for (int i = 0; i < numsSize; i++) {if (nums[i] == 0) {zeroCount++;}}int index = 0;for (int i = 0; i < numsSize; i++) {if (nums[i] != 0) {nums[index++] = nums[i];}}for (int i = index; i < numsSize; i++) {nums[i] = 0;}
}
3、移動非零元素法:
#include <stdio.h>
void moveZeroes(int* nums, int numsSize) {int insertPos = 0; // 指向當前已處理數組的末尾for (int i = 0; i < numsSize; i++) {if (nums[i] != 0) {nums[insertPos++] = nums[i];}}while (insertPos < numsSize) {nums[insertPos++] = 0;}
}