題目1:https://leetcode.cn/problems/move-zeroes
小編剛看到這道題的時候,想到的第一個方法就是建立一個與原數組等大的新的數組,然后遍歷原數組,如果遇到元素值不為0的元素,就將這個元素放到新數組中,直到遍歷完整個數組,那么當遍歷完整個數組以后,新數組的剩余位置就應該都存放0,直接通過循環存放0即可,最后將新數組中的元素依次賦值給原數組即可。
但是,題目要求,必須在不復制數組的情況下對數組進行原地操作。
這要咋辦?
還是請出我們的老朋友——雙指針法,看利用它是否能解決這道問題。
我們可以這么想,這道題的本質無非就是實現數組的分塊,以某一個位置為界限,這個位置左邊的元素都是不為0的,右邊的元素都等于0.
那我們就可以這樣操作:設定一個界限dest,然后通過算法使得dest左邊的元素都是不為0的。
現在我們來考慮實現這個算法:定義一個整形指針pcur來遍歷數組,初始pcur指向0;而剛開始的時候dest所包含的區域中肯定是一個元素都沒有的,所以我們可以將它初始化為-1。
進行一下操作:如果pcur指向的元素不等于0,那就讓dest先往前走一步,然后再讓dest位置的元素賦值為pcur所指向的元素,如果pcur指向的元素等于0,那直接讓pcur往前走一步即可。
這樣一來,pcur越界以后,dest之前的元素都是不為0的元素,我們只需要再將dest之后的元素通過循環都變成0即可。
void moveZeroes(int* nums, int numsSize)
{//雙指針,定義prev和pcur指針//pcur指向的元素如果不等于0,就讓prev指向的位置的值等于pcur指向位置的值,prev往前走,pcur也往前走,反之,prev不動,pcur往前走int pcur = 0, prev = -1;while (pcur < numsSize){if (nums[pcur] != 0){nums[++prev] = nums[pcur];pcur++;}else {pcur++;}}for (int i = prev+1; i < numsSize; i++){nums[i] = 0;}}
我們來畫圖演示一下這個過程:
上面的思路中我們分別用了兩次循環才實現代碼,有沒有更簡潔的方法呢?能不能一次就搞定啊?
包有的,現在我們就來看一下:
我們還是讓dest初始指向-1,pcur初始指向0,如果pcur指向的元素不為0,那就先讓dest往前走一步,然后交換dest和pcur所指向的元素,反之,直接讓pcur往前走一步,到了最后pcur越界,此時就已經將數組變成預期的效果了
void moveZeroes(int* nums, int numsSize)
{int pcur=0,dest=-1;while(pcur<numsSize){if(nums[pcur]){dest++;int tmp=nums[dest];nums[dest]=nums[pcur];nums[pcur]=tmp;}pcur++;}
}
題目2:https://leetcode.cn/problems/duplicate-zeros
這道題就有點意思了,雖然這道題的難度設置是簡單,但他的整體思路還是比較難想的。
如果我們異地修改整個數組,即創建一個新的數組來完成任務,那這道題就非常簡單,但是這道題要求不能使用這個方法。
小編就不賣關子了,直接給出這道題的整體思路。
以上面的實例1為例,我們呢來講解一下這道題的整體思路。
我們可以根據輸出結果看到數組中要保留的最后一個元素是4,我們可以定義一個整形指針pcur指向要保留的最后一個數據,再定義一個整形指針dest指向數組中的最后一個位置,執行以下操作"
1.根據pcur指向的元素的值判斷dest應該向前走多少步:
- 如果pcur指向的值是0,那么就進行兩次這個操作:讓dest指向的位置的值賦值為0,同時讓dest往前走一步;之后還要再讓pcur往前走一步
- 如果pcur指向非0值x,那么就進行以下操作:讓dest指向的位置的值賦值為x,同時讓dest和pcur往前走一步
如以下示意圖:
可以看到,這種算法確實可以幫我們完成任務,但是現在的問題就轉換為我們如何寫一個算法能夠得到最后一個要保留的元素的值。
這個方法比較難想到,我們直接給出算法:
定義兩個指針pcur和dest,pcur初始指向0,dest指向-1,執行以下操作:
1.如果pcur指向的值為非0值,就讓dest往前走一步;判斷dest是否越界(dest>=numssize-1),如果dest越界,那么就直接結束遍歷,如果沒有越界,就讓pcur往前走一步
2.如果pcur指向的值等于0,就讓dest往前走兩步,判斷dest是否越界(dest>=numssize-1),如果dest越界,那么就直接結束遍歷,如果沒有越界,就讓pcur往前走一步
當dest越界的時候,pcur指向的位置就是我們要找的位置。
我們可以畫圖來驗證一下:
可以看到,通過以上算法步驟,pcur最終指向的位置確實就是我們要保留的最后一個數據。
但是,還有一個需要考慮的特殊情況,如以下案例:
我們可以發現,在上面的例子中,dest超過了數組可以表示的范圍,其實原因就是最后一個要保留的數據是0,所以dest要走兩步發生越界,這種情況下,我們需要進行特殊處理:
直接讓數組下標為n-1的位置的值等于0,然后讓pcur往前走一步,dest往前走兩步即可:
我們結合上面的思路來寫一下代碼:
void duplicateZeros(int* arr, int arrSize)
{int n=arrSize;//先找到數組中最后一個要保留的數據的位置int pcur=0,dest=-1;while(pcur<n){if(arr[pcur])dest++;elsedest+=2;if(dest>=n-1)break;pcur++;} //進行特殊處理if(dest>n-1){arr[n-1]=0;dest-=2;pcur--;} //進行元素的復寫while(pcur>=0){if(arr[pcur]){arr[dest--]=arr[pcur--];}else{arr[dest--]=0;arr[dest--]=0;pcur--;}}
}
小結:這一小節我們主要通過兩個算法題對應用雙指針的解題思路進行了講解,小編認為第二道題還是比較有難度的。小伙伴們一定要自己在草稿紙或者電腦上多畫一下圖,理解算法的思想。
歡迎小伙伴們在評論區提出問題和討論!!!