雙指標技巧秒殺七道陣列題目
雙指標技巧秒殺七道陣列題目
在處理陣列和連結串列相關問題時,雙指標技巧是經常用到的,雙指標技巧主要分為兩類:左右指標和快慢指標。
所謂左右指標,就是兩個指標相向而行或者相背而行;而所謂快慢指標,就是兩個指標同向而行,一快一慢。
一、快慢指標技巧
陣列問題中比較常見且難度不高的的快慢指標技巧,是讓你原地修改陣列。
力扣第 26 題「 刪除有序陣列中的重複項」
我的C++程式碼:
讓慢指標 slow
走在後面,快指標 fast
走在前面探路,找到一個不重複的元素就賦值給 slow
並讓 slow
前進一步。
這樣,就保證了 nums[0..slow]
都是無重複的元素,當 fast
指標遍歷完整個陣列 nums
nums[0..slow]
就是整個陣列去重之後的結果。
class Solution { public: int removeDuplicates(vector<int>& nums) { int slow=0,fast=1; while(fast<nums.size()){ if(nums[fast]!=nums[slow]){ slow++; nums[slow]=nums[fast]; } fast++; } return slow+1; } };
演算法執行的過程如下 GIF 圖:
力扣第 83 題「 刪除排序連結串列中的重複元素」
與上題思路相同,或許更簡單些?
class Solution { public: ListNode* deleteDuplicates(ListNode* head) { if(head==nullptr)return nullptr; ListNode* slow=head; ListNode* fast=head; while(fast!=nullptr){ if(fast->val!=slow->val) { slow->next=fast; slow=fast; } fast=fast->next; } slow->next=fast; return head; } };
演算法執行的過程請看下面這個 GIF:
力扣第 27 題「 移除元素」
我的C++程式碼
如果 fast
遇到值為 val
的元素,則直接跳過,否則就賦值給 slow
指標,並讓 slow
前進一步。
注意這裡和有序陣列去重的解法有一個細節差異,我們這裡是先給 nums[slow]
賦值然後再給 slow++
,這樣可以保證 nums[0..slow-1]
是不包含值為 val
的元素的,最後的結果陣列長度就是 slow
。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow=0,fast=0;
while(fast<nums.size()){
if(nums[fast]!=val){
nums[slow]=nums[fast];
slow++;
}
fast++;
}
return slow;
}
};
力扣第 283 題「 移動零」
我的C++程式碼:
相當於移除 nums
中的所有 0,然後再把後面的元素都賦值為 0 即可。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int slow=0,fast=0;
while(fast<nums.size()){
if(nums[fast]!=0){
nums[slow]=nums[fast];
slow++;
}
fast++;
}
while(slow<nums.size()){
nums[slow]=0;
slow++;
}
}
};
二、左右指標的常用演算法
1、二分查詢
框架
int binarySearch(int[] nums, int target) {
// 一左一右兩個指標相向而行
int left = 0, right = nums.length - 1;
while(left <= right) {
int mid = (right + left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
return -1;
}
2、兩數之和
力扣第 167 題「 兩數之和 II」
C++程式碼:
只要陣列有序,就應該想到雙指標技巧。這道題的解法有點類似二分查詢,通過調節 left
和 right
就可以調整 sum
的大小:
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
//左右雙指標 大了就調整右指標 小了就調整左指標
int le=0,ri=numbers.size()-1;
while(le<ri){
if(numbers[le]+numbers[ri]==target){
return {le+1,ri+1};
}
//和小了
if(numbers[le]+numbers[ri]<target){
le++;
}else{//和大了
ri--;
}
}
return {le+1,ri+1};
}
};
3、反轉陣列
一般程式語言都會提供 reverse
函式,其實這個函式的原理非常簡單,力扣第 344 題「 反轉字串」就是類似的需求,讓你反轉一個 char[]
型別的字元陣列
void reverseString(char[] s) {
// 一左一右兩個指標相向而行
int left = 0, right = s.length - 1;
while (left < right) {
// 交換 s[left] 和 s[right]
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
4、迴文串判斷
首先明確一下,迴文串就是正著讀和反著讀都一樣的字串。
比如說字串 aba
和 abba
都是迴文串,因為它們對稱,反過來還是和本身一樣;反之,字串 abac
就不是迴文串。也是兩個指標相向而行即可
力扣第 5 題「 最長迴文子串」:
C++程式碼:
最長迴文子串使用的左右指標和之前題目的左右指標有一些不同:之前的左右指標都是從兩端向中間相向而行,而回文子串問題則是讓左右指標從中心向兩端擴充套件。
class Solution {
public:
string longestPalindrome(string s) {
int sz=s.size();
string res="";
for(int i=0;i<sz;i++){
string a1=Palindrome(s,i,i);
string a2=Palindrome(s,i,i+1);
res=res.size()>a1.size()?res:a1;
res=res.size()>a2.size()?res:a2;
}
return res;
}
string Palindrome(string s,int le,int ri){
// 防止索引越界
while (le >= 0 && ri < s.size()&& s[le] == s[ri]) {
// 雙指標,向兩邊展開
le--; ri++;
}
// 返回以 s[l] 和 s[r] 為中心的最長迴文串
return s.substr(le+1,ri-le-1);
}
};