廣東炒股配資網(wǎng)站開發(fā)百度關(guān)鍵詞優(yōu)化推廣
👀樊梓慕:個(gè)人主頁
?🎥個(gè)人專欄:《C語言》《數(shù)據(jù)結(jié)構(gòu)》《藍(lán)橋杯試題》《LeetCode刷題筆記》《實(shí)訓(xùn)項(xiàng)目》《C++》《Linux》《算法》
🌝每一個(gè)不曾起舞的日子,都是對(duì)生命的辜負(fù)
目錄
前言
1.數(shù)組分塊(數(shù)組劃分)
移動(dòng)零
復(fù)寫零
2.快慢雙指針(循環(huán)往復(fù))
快樂數(shù)
3.對(duì)撞指針->暴力枚舉的優(yōu)化->利用單調(diào)性
盛最多水的容器
有效三角形的個(gè)數(shù)
4.對(duì)撞指針->兩數(shù)之和、三數(shù)之和、四數(shù)之和
兩數(shù)之和
三數(shù)之和
四數(shù)之和
前言
💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐
《算法》專欄正式掛牌成立
- 《算法》專欄主要是會(huì)系統(tǒng)的梳理一些OJ題的算法思想,將他們按照解題方法的不同劃分出來,然后歸納總結(jié),當(dāng)然希望大家多多收藏,以后忘了可以?;貋砜纯?#xff01;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐💐
本篇文章主要會(huì)講解雙指針的思想,雙指針是一種非常優(yōu)秀的算法思想,有對(duì)撞指針和快慢指針兩種基本用法。
雙指針對(duì)于有序數(shù)據(jù)的處理是比較有優(yōu)勢(shì)的,當(dāng)你遇到有序的數(shù)據(jù)時(shí),你可以嘗試著利用雙指針或者二分來解題,當(dāng)然本篇文章只會(huì)講解雙指針。
那么雙指針?biāo)枷刖唧w的應(yīng)用,以及為什么雙指針適用于有序數(shù)組的處理呢?
歡迎大家📂收藏📂以便未來做題時(shí)可以快速找到思路,巧妙的方法可以事半功倍。
=========================================================================
GITEE相關(guān)代碼:🌟fanfei_c的倉庫🌟
=========================================================================
1.數(shù)組分塊(數(shù)組劃分)
數(shù)組分塊顧名思義,該類題目有一個(gè)特性就是將數(shù)組中的數(shù)據(jù)進(jìn)行分類,然后將分類的數(shù)據(jù)放在不同的區(qū)域上。
移動(dòng)零
移動(dòng)零 - 力扣(LeetCode)https://leetcode.cn/problems/move-zeroes/description/
給定一個(gè)數(shù)組?
nums
,編寫一個(gè)函數(shù)將所有?0
?移動(dòng)到數(shù)組的末尾,同時(shí)保持非零元素的相對(duì)順序。請(qǐng)注意?,必須在不復(fù)制數(shù)組的情況下原地對(duì)數(shù)組進(jìn)行操作。
?利用數(shù)組分塊的思想,我們可以將該數(shù)組劃分為三個(gè)區(qū)域:非零的已處理區(qū)域、零的已處理區(qū)域、待處理區(qū)域。
三個(gè)區(qū)域恰好可以利用兩個(gè)指針進(jìn)行分割得到。
所以我們定義兩個(gè)指針:
- cur:從左向右掃描數(shù)組(遍歷數(shù)組的作用),主要用來分割已處理區(qū)域和待處理區(qū)域用;
- dest:已處理的區(qū)域內(nèi),非零元素的最后一個(gè)位置,主要用來分隔已處理區(qū)域內(nèi)部非零元素和零元素。
得到三個(gè)區(qū)間:
- 非零的已處理區(qū)域:[0,dest]
- 零的已處理區(qū)域:[dest+1,cur-1]
- 待處理區(qū)域:[cur,n-1]
?有了思路,畫圖獨(dú)立完成代碼,不要直接看博主的代碼。
class Solution {
public:void moveZeroes(vector<int>& nums) {for (int dest = -1, cur = 0; cur <= nums.size() - 1; cur++){//如果是零就跳過,不是零進(jìn)入if (nums[cur]){swap(nums[++dest], nums[cur]);}}}
};
復(fù)寫零
復(fù)寫零 - 力扣(LeetCode)https://leetcode.cn/problems/duplicate-zeros/description/
給你一個(gè)長度固定的整數(shù)數(shù)組?
arr
?,請(qǐng)你將該數(shù)組中出現(xiàn)的每個(gè)零都復(fù)寫一遍,并將其余的元素向右平移。注意:請(qǐng)不要在超過該數(shù)組長度的位置寫入元素。請(qǐng)對(duì)輸入的數(shù)組?就地?進(jìn)行上述修改,不要從函數(shù)返回任何東西。
我們可以先嘗試著進(jìn)行異地復(fù)寫,然后嘗試著進(jìn)行原地復(fù)寫,看看會(huì)發(fā)生什么問題?
如果「從前向后」進(jìn)行原地復(fù)寫操作的話,由于0的出現(xiàn)會(huì)復(fù)寫兩次,導(dǎo)致沒有復(fù)寫的數(shù)「被覆
蓋掉」。
因此我們選擇「從后往前」的復(fù)寫策略。
但是「從后向前」復(fù)寫的時(shí)候,我們需要找到「最后一個(gè)復(fù)寫的數(shù)」,因此我們的大體流程分兩
步:
- 先找到最后一個(gè)復(fù)寫的數(shù);
- 然后從后向前進(jìn)行復(fù)寫操作。
?這兩步仍然包含一些細(xì)節(jié)需要處理,比如會(huì)不會(huì)出現(xiàn)越界問題等?
- cur:用來遍歷數(shù)組用。
- dest:根據(jù)cur指向的指進(jìn)行移動(dòng)一步或兩步,如果dest的位置處于最后一位或者已經(jīng)越界,跳出循環(huán),如果是越界的情況,我們需要手動(dòng)將其"拉回",然后進(jìn)行從后向前的復(fù)寫操作。
有了思路,畫圖獨(dú)立完成代碼,不要直接看博主的代碼。
class Solution {
public:void duplicateZeros(vector<int>& arr) {int dest=-1,cur=0,n=arr.size();//1.先找到cur位置while(cur<n){if(arr[cur])dest++;elsedest+=2;if(dest>=n-1)//這里是為了及時(shí)檢測(cè)是否跳出break;cur++; }//1.5判斷dest位置if(dest==n){arr[dest-1]=0;dest-=2;cur--;}//2.然后向前復(fù)寫while(cur>=0){if(arr[cur])arr[dest--]=arr[cur--]; else{arr[dest--]=0;arr[dest--]=0;cur--;}}}
};
2.快慢雙指針(循環(huán)往復(fù))
快慢雙指針基本思想:使用兩個(gè)移動(dòng)速度不同的指針在數(shù)組或鏈表等序列結(jié)構(gòu)上移動(dòng)。
一般什么情況下適用快慢雙指針的題目呢?
這種方法對(duì)于處理環(huán)形鏈表或數(shù)組非常有用,或者說循環(huán)往復(fù)的數(shù)據(jù)都比較適用快慢雙指針?biāo)惴▉磉M(jìn)行解決。
快樂數(shù)
快樂數(shù) - 力扣(LeetCode)https://leetcode.cn/problems/happy-number/description/
編寫一個(gè)算法來判斷一個(gè)數(shù)?
n
?是不是快樂數(shù)。「快樂數(shù)」?定義為:
- 對(duì)于一個(gè)正整數(shù),每一次將該數(shù)替換為它每個(gè)位置上的數(shù)字的平方和。
- 然后重復(fù)這個(gè)過程直到這個(gè)數(shù)變?yōu)?1,也可能是?無限循環(huán)?但始終變不到 1。
- 如果這個(gè)過程?結(jié)果為?1,那么這個(gè)數(shù)就是快樂數(shù)。
如果?
n
?是?快樂數(shù)?就返回?true
?;不是,則返回?false
?。
?請(qǐng)注意題目意義,只會(huì)有兩種情況:
- 情況1:無限循環(huán)但始終變不到1
- 情況2:有限次數(shù)內(nèi),結(jié)果為1
所以對(duì)于這種循環(huán)往復(fù)的數(shù)據(jù)我們就可以聯(lián)想到快慢雙指針來做:
為了方便理解,我抽象的將數(shù)據(jù)做成鏈:
所以必然會(huì)成環(huán),slow與fast必然會(huì)相遇,我們需要做的就是在他們相遇的時(shí)刻,檢測(cè)以下slow或者fast的值是否為1即可。
有了思路,畫圖獨(dú)立完成代碼,不要直接看博主的代碼。
class Solution {
public:int bitSum(int n) {int sum = 0;while (n) {int t = n % 10;sum += t * t;n /= 10;}return sum;}bool isHappy(int n) {int slow = n;int fast = bitSum(n);while (slow != fast) {slow = bitSum(slow);fast = bitSum(bitSum(fast));}return slow == 1;}
};
3.對(duì)撞指針->暴力枚舉的優(yōu)化->利用單調(diào)性
一般用于順序結(jié)構(gòu)中,也稱左右指針。
對(duì)撞指針從兩端向中間移動(dòng)。?個(gè)指針從最左端開始,另?個(gè)從最右端開始,然后逐漸往中間逼
近。
對(duì)撞指針的終止條件一般是兩個(gè)指針相遇或者錯(cuò)開(也可能在循環(huán)內(nèi)部找到結(jié)果直接跳出循
環(huán)),也就是:
- left == right(兩個(gè)指針指向同?個(gè)位置)
- left > right(兩個(gè)指針錯(cuò)開)
?單調(diào)性解題的思路不好想到,但這是一種非常優(yōu)秀的對(duì)暴力枚舉方法的優(yōu)化思想。
盛最多水的容器
盛最多水的容器 - 力扣(LeetCode)https://leetcode.cn/problems/container-with-most-water/description/
給定一個(gè)長度為?
n
?的整數(shù)數(shù)組?height
?。有?n
?條垂線,第?i
?條線的兩個(gè)端點(diǎn)是?(i, 0)
?和?(i, height[i])
?。找出其中的兩條線,使得它們與?
x
?軸共同構(gòu)成的容器可以容納最多的水。返回容器可以儲(chǔ)存的最大水量。
說明:你不能傾斜容器。
?如果說利用暴力枚舉的方式來做,很明顯你需要固定一邊,兩層for循環(huán)解決,時(shí)間復(fù)雜度O(N^2),但這道題目作為一道中等難度的題,利用暴力枚舉必然會(huì)超時(shí)。
我們嘗試?yán)脤?duì)撞指針的方式來做:
w(寬)=right-left;
容積的計(jì)算公式:V=h*w
當(dāng)計(jì)算完一組結(jié)果之后,我們需要將左指針或右指針向中間移動(dòng),這樣如此反復(fù)就能得到最終答案,可是這樣并沒有降低時(shí)間復(fù)雜度,仍然是暴力枚舉的思路。
我們觀察:
當(dāng)左指針或右指針向中間移動(dòng)時(shí)w是必然減小的。
又根據(jù)木桶原理,h取決于左右指針指向的值小的那一個(gè)數(shù)據(jù)。
本題是依據(jù)數(shù)據(jù)分析,進(jìn)而得到單調(diào)性的關(guān)系,需要大家自行畫圖分析,然后將思路轉(zhuǎn)化成代碼。
class Solution {
public:int maxArea(vector<int>& height) {int left=0;int right=height.size()-1;int v=0;int ret=0;while(left<right){int v=min(height[left],height[right])*(right-left);ret=max(v,ret);if(height[left]<height[right]) left++;else right--;}return ret;}
};
有效三角形的個(gè)數(shù)
有效三角形的個(gè)數(shù) - 力扣(LeetCode)https://leetcode.cn/problems/valid-triangle-number/description/
?給定一個(gè)包含非負(fù)整數(shù)的數(shù)組?
nums
?,返回其中可以組成三角形三條邊的三元組個(gè)數(shù)。
構(gòu)成三角形的條件:任意兩邊之和大于第三邊
但這個(gè)條件轉(zhuǎn)化成代碼需要三次判斷未免有些麻煩,所以我們可以將數(shù)組先進(jìn)行排序,排序之后如果較小的兩個(gè)值之和大于第三邊,那么就可以構(gòu)成三角形了。?
暴力枚舉的方式很顯然時(shí)間復(fù)雜度O(N^3)。
那我們嘗試著對(duì)數(shù)據(jù)進(jìn)行分析,看看能否利用單調(diào)性來優(yōu)化。
首先排序,我們將最大的數(shù)固定,然后利用對(duì)撞指針的思想進(jìn)行優(yōu)化。
?有了思路,畫圖獨(dú)立完成代碼,不要直接看博主的代碼。
class Solution {
public:int triangleNumber(vector<int>& nums) {sort(nums.begin(),nums.end());int n=nums.size();int maxIndex=n-1;int ret=0;while(maxIndex>=2){int left=0;int right=maxIndex-1;while(left<right){if(nums[left]+nums[right]>nums[maxIndex]){ ret+=right-left;right--;}else{left++;}}maxIndex--;}return ret;}
};
4.對(duì)撞指針->兩數(shù)之和、三數(shù)之和、四數(shù)之和
兩數(shù)之和
兩數(shù)之和 - 力扣(LeetCode)https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/description/
購物車內(nèi)的商品價(jià)格按照升序記錄于數(shù)組?
price
。請(qǐng)?jiān)谫徫镘囍姓业絻蓚€(gè)商品的價(jià)格總和剛好是?target
。若存在多種情況,返回任一結(jié)果即可。
?首先我們發(fā)現(xiàn)數(shù)組是升序排列的,所以我們想到可以利用雙指針來解決,同樣的我們利用單調(diào)性,看看能否對(duì)暴力枚舉的策略作優(yōu)化。
暴力枚舉的時(shí)間復(fù)雜度很明顯O(N^2)。
兩數(shù)之和大于target時(shí),利用單調(diào)性,令right--即可;
兩數(shù)之和小于target時(shí),利用單調(diào)性,令left++即可;
兩數(shù)之和等于target時(shí),我們將此時(shí)的結(jié)果尾插到結(jié)果數(shù)組中。
class Solution {
public:vector<int> twoSum(vector<int>& price, int target) {int left=0;int right=price.size()-1;vector<int> ret;while(left<right){int sum=price[left]+price[right];if(sum<target) left++;else if(sum>target) right--;else{ret.push_back(price[left]);ret.push_back(price[right]);break;}}return ret;}
};
三數(shù)之和
三數(shù)之和 - 力扣(LeetCode)https://leetcode.cn/problems/3sum/description/
給你一個(gè)整數(shù)數(shù)組?
nums
?,判斷是否存在三元組?[nums[i], nums[j], nums[k]]
?滿足?i != j
、i != k
?且?j != k
?,同時(shí)還滿足?nums[i] + nums[j] + nums[k] == 0
?。請(qǐng)你返回所有和為?
0
?且不重復(fù)的三元組。注意:答案中不可以包含重復(fù)的三元組。
?本題可以借助兩數(shù)之和的思想進(jìn)行解題,無非就是需要多加一層循環(huán),將第三個(gè)數(shù)固定即可。
另外的兩個(gè)數(shù)仍然為兩數(shù)之和的思想,只不過此時(shí)兩數(shù)之和等于負(fù)的第三個(gè)數(shù)。
難點(diǎn):注意本題要求去重,并且要求返回所有滿足的數(shù)據(jù),所以我們需要處理一些細(xì)節(jié)問題。
首先,關(guān)于返回所有:
- 當(dāng)找到一種結(jié)果后,不能直接返回,要繼續(xù)縮小區(qū)間繼續(xù)尋找。
其次,關(guān)于去重:
- 找到一種結(jié)果之后,left和right要跳過重復(fù)元素。
- 當(dāng)使用完一次雙指針?biāo)惴ê?#xff0c;即更換第三個(gè)數(shù)時(shí),也要跳過重復(fù)元素。
- 注意防止越界。
??有了思路,畫圖獨(dú)立完成代碼,不要直接看博主的代碼。
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {sort(nums.begin(), nums.end());vector<vector<int>> ret;int n = nums.size();for (int i = 0; i < n;){if (nums[i] > 0) break;//小優(yōu)化int left = i + 1, right = n - 1, target = -nums[i];while (left < right){int sum = nums[left] + nums[right];if (sum < target) left++;else if (sum > target) right--;else{ret.push_back({ nums[left++],nums[right--],nums[i] });//去重 left 和 rightwhile (left < right && nums[left] == nums[left - 1]) left++;while (left < right && nums[right] == nums[right + 1]) right--;}}//去重 ii++;while (i < n && nums[i] == nums[i - 1]) i++;}return ret;}
};
四數(shù)之和
四數(shù)之和 - 力扣(LeetCode)https://leetcode.cn/problems/4sum/description/
給你一個(gè)由?
n
?個(gè)整數(shù)組成的數(shù)組?nums
?,和一個(gè)目標(biāo)值?target
?。請(qǐng)你找出并返回滿足下述全部條件且不重復(fù)的四元組?[nums[a], nums[b], nums[c], nums[d]]
?(若兩個(gè)四元組元素一一對(duì)應(yīng),則認(rèn)為兩個(gè)四元組重復(fù)):
0 <= a, b, c, d?< n
a
、b
、c
?和?d
?互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按?任意順序?返回答案 。
?四數(shù)之和是三數(shù)之和的升級(jí),本質(zhì)上沒有任何區(qū)別,只不過多加了一個(gè)需要固定的數(shù),多加了一層循環(huán)而已,如果你已經(jīng)掌握了三數(shù)之和,那么這道題對(duì)你來說會(huì)非常簡單。
?有了思路,畫圖獨(dú)立完成代碼,不要直接看博主的代碼。
class Solution {
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {sort(nums.begin(),nums.end());vector<vector<int>> ret;int n=nums.size();for(int i=0;i<n;){for(int j=i+1;j<n;){int left=j+1,right=n-1; long long num=(long long)target-nums[j]-nums[i];//需要注意的細(xì)節(jié)while(left<right){int sum=nums[left]+nums[right];if(sum>num) right--;else if(sum<num) left++;else{ret.push_back({nums[i],nums[j],nums[left++],nums[right--]});//去重 left 和 rightwhile(left<right && nums[left]==nums[left-1]) left++;while(left<right && nums[right]==nums[right+1]) right--;}}//去重 jj++;while(j<n && nums[j]==nums[j-1]) j++;}//去重ii++;while(i<n && nums[i]==nums[i-1]) i++;}return ret;}
};
以上就是雙指針?biāo)惴ㄔ趯?shí)際題目中的應(yīng)用,總的來說,雙指針?biāo)惴ㄊ潜容^基礎(chǔ)并且簡單的算法。
大家只需要記住:當(dāng)所給數(shù)據(jù)為有序時(shí),不妨考慮用雙指針?biāo)惴ㄟM(jìn)行解決。
🐸簡單總結(jié)🐸
雙指針擅于處理有序數(shù)據(jù),可以解決數(shù)組分塊、循環(huán)往復(fù)數(shù)據(jù)可以利用快慢指針?biāo)枷?#xff08;得到某個(gè)值可以理解為在某個(gè)值處循環(huán))、對(duì)撞指針結(jié)合單調(diào)性可以優(yōu)化暴力枚舉(注意細(xì)節(jié):去重和不漏)。
=========================================================================
如果你對(duì)該系列文章有興趣的話,歡迎持續(xù)關(guān)注博主動(dòng)態(tài),博主會(huì)持續(xù)輸出優(yōu)質(zhì)內(nèi)容
🍎博主很需要大家的支持,你的支持是我創(chuàng)作的不竭動(dòng)力🍎
🌟~ 點(diǎn)贊收藏+關(guān)注 ~🌟
=========================================================================