循環不變量原理
第一次看到循環不變量是在算法導論的快排裏。一般是針對數組問題求解,需要2個指針。
快排
快排最核心的思想是分區,也就是將給定數組分成2部分,左邊比區間點小,右邊比區間點大。使用循環不變量原理,以快排爲例,指針 i, j 將數組分爲3個部分。
其中 0 < i <= j ; i<= j < len(arr);
對所有 x < i 的元素,都有 a[x] < a[i];
對所有 i < x < j 的元素,都有 a[i] < a[x] < a[j] ;
接下來只要遍歷數組,保證在遍歷過程中,始終滿足上述條件。
如果不滿足,就通過交換來達到滿足。
int partition(int *arr, int start, int end)
{
int i = start-1;
int j = start;
int base = arr[end]; //基準值
for (; j < end; ++j) {
//不滿足循環不變量的原理,需要交換,保證不變量成立
if (arr[j] <= base) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i+1], &arr[end]);
return i+1;
}
根據上面的思路,可以求解一些數組問題。
移動 0
將一個數組中的 0 全部移動到最後。參考快排的思想。設置2個指針,lastNotZeroPos 和 j。
lastNotZeroPos 表示數組中所有 x <= lastNotZeroPos 的元素,都不爲0。
j 則表示已處理的區間。這裏跟 快排不太一樣的是,不滿足循環不變量的時候才進行交換。這裏是滿足的時候就要交換。
void moveZeroes(int* nums, int numsSize) {
int lastNotZeroPos = -1;
for (int j=0; j < numsSize; j++){
if (nums[j]!=0){
nums[++lastNotZeroPos]=nums[j];
}
}
return lastNotZeroPos+1;
}
去掉指定元素
跟移動 0 一樣的原理
int removeElement(int* nums, int numsSize, int val) {
int lastNotValPos = -1;
for (int j=0; j < numsSize; j++){
if (nums[j]!=val){
nums[++lastNotValPos]=nums[j];
}
}
return lastNotValPos+1;
}
排序數組去重
從移除0變成了移除重複項。比較的值從定值變成了動態的,即始終與循環中當前元素的前一個元素比較。
int removeDuplicates(int* nums, int numsSize) {
int lastNotDuplicatePos = 0; //初始化爲第一個元素
for (int j=1 /*從第二個元素開始*/ ; j < numsSize; j++){
if (nums[j]!=nums[lastNotDuplicatePos]){
nums[++lastNotDuplicatePos]=nums[j];
}
}
return lastNotDuplicatePos+1;
}
排序數組只保留最多2個重複的元素
int removeDuplicates(int* nums, int numsSize) {
//處理邊界條件
if (numsSize<3){
return numsSize;
}else if (numsSize==3){
if (nums[2]==nums[1]&&nums[1]==nums[0])
return 2;
else
return 3;
}
int i = 0;
int j = 1;
for (int k=2; k<numsSize;k++){
if (nums[k]!=nums[i] || nums[k]!=nums[j]){
++i; // equals ==> nums[++i]=nums[j];
nums[++j] = nums[k];
}
}
return j+1;
}