LeetCode刷題之雙指標
阿新 • • 發佈:2021-08-02
leetcode26:刪除排序陣列中的重複元素(留一個) 給你一個有序陣列 nums ,請你 原地 刪除重複出現的元素,使每個元素只出現一次 ,返回刪除後數 組的新長度。 不要使用額外的陣列空間,你必須在 原地 修改輸入陣列 並在使用 O(1) 額外空間的條件下完成。 輸入:nums = [1,1,2] 輸出:2, nums = [1,2] 解釋:函式應該返回新的長度 2 ,並且原陣列 nums 的前兩個元素被修改為 1, 2 。不需要考慮陣列 中超出新長度後面的元素。 解法分析: 既然陣列是有序的,那麼可以考慮雙指標。定義一個指標 pos 指向需要修改的位置,另一個指標 cur 指向當前位置。 當 cur 指向的數與 pos 的前一個數不同時,將 pos 的數賦值為 cur 指向的數,完成更改。最後的 陣列長度就是 pos 的大小。 實現細節描述: 這種解法相當於"快慢指標",當迴圈結束時,快指標對整個陣列進行遍歷,因此可以採用 for 迴圈。 由於cur的數需要與pos的前一個數進行比較,因此需要從下標為1的位置開始索引,因此需要單獨考慮 當陣列長度為0的情況。 int removeDuplicates(vector<int>& nums){ if(nums.size() < 1) return 0; // 單獨判斷陣列長度小於pos的情況 int pos=1; for(int cur=1; cur<nums.size(); cur++){ if(nums[cur] != nums[pos-1]) // 賦值替換 nums[pos++] = nums[cur]; } return pos; }
Leetcode80:刪除排序陣列中的重複元素(留兩個) 給你一個有序陣列 nums ,請你 原地 刪除重複出現的元素,使每個元素 最多出現兩次 ,返回刪除後 陣列的新長度。 不要使用額外的陣列空間,你必須在 原地 修改輸入陣列 並在使用 O(1) 額外空間的條件下完成。 輸入:nums = [0,0,1,1,1,1,2,3,3] 輸出:7, nums = [0,0,1,1,2,3,3] 解釋:函式應返回新長度 length = 7, 並且原陣列的前五個元素被修改為 0, 0, 1, 1, 2, 3, 3 。 不需要考慮陣列中超出新長度後面的元素。 解法分析: 這個題目使每個元素最多出現兩次,那麼結合到上一題,只需要調整判斷標準即可: nums[pos-1] != nums[cur] ====> nums[pos-2] != nums[cur] 其餘分析同上。 int removeDuplicates(vector<int>& nums) { int n = nums.size(); if(n<2) return n; // 單獨判斷陣列長度小於pos的情況 int pos = 2; for(int cur=2; cur<n; cur++){ if(nums[cur] != nums[pos-2]) nums[pos++] = nums[cur]; } return pos; }
Leetcode27:刪除陣列中的元素 給你一個數組 nums 和一個值 val,你需要 原地 移除所有數值等於 val 的元素,並返回移除後數 組的新長度。 1 2不要使用額外的陣列空間,你必須僅使用 O(1) 額外空間並 原地 修改輸入陣列。 元素的順序可以改變。你不需要考慮陣列中超出新長度後面的元素。 輸入:nums = [0,1,2,2,3,0,4,2], val = 2 輸出:5, nums = [0,1,4,0,3] 解釋:函式應該返回新的長度 5, 並且 nums 中的前五個元素為 0, 1, 3, 0, 4。注意這五個元素 可為任意順序。你不需要考慮陣列中超出新長度後面的元素。 題目分析: 這個題目同樣可以用"快慢指標"來解 int removeElement(vector<int>& nums, int val){ int pos=0; for(int cur=0; cur<nums.size(); cur++){ if(nums[cur] != val) nums[pos++] = nums[cur]; } return pos; }
Leetcode83:刪除排序連結串列中的重複元素(留一個)
存在一個按升序排列的連結串列,給你這個連結串列的頭節點 head ,請你刪除所有重複的元素,使每個元素
只出現一次 。返回同樣按升序排列的結果連結串列。
輸入:head = [1,1,2]
輸出:[1,2]
題目分析:
這個題目同樣也可以用"快慢指標"來求解
ListNode* deleteDuplicates(ListNode* head) {
if(head == nullptr) return head; // 判斷邊界條件
ListNode* slow = head;
ListNode* fast = head;
while(fast != nullptr){
if(slow->val != fast->val){ // 通過fast指標,找到下一個與 slow->val 的值不一樣的位置
slow->next = fast; // 完成賦值
slow = slow->next;
}
fast = fast->next; // fast 指標自增
}
/* 最後會出現兩種情況
1.當最後一個元素沒有重複值時,通過if語句能夠指向最後一個元素,從而具有正確的結尾指標
nullptr
2.當最後一個元素與前面的重複時,那麼slow指向的就不是最後一個節點,需要手動賦予結尾指
針。
而不管上面兩種情況如何,最後fast都是指向nullptr的,因此可以統一使用 slow->next =
fast 來保證
*/
slow->next = fast;
return head;
}
Leetcode82:刪除排序連結串列中的重複元素(一個不留)
存在一個按升序排列的連結串列,給你這個連結串列的頭節點 head ,請你刪除連結串列中所有存在數字重複情況的
節點,只保留原始連結串列中 沒有重複出現 的數字。返回同樣按升序排列的結果連結串列。
輸入:head = [1,2,3,3,4,4,5]
輸出:[1,2,5]
題目分析:
1 2 3 4 5這道題目與上一道題目不同,需要提前記錄第一個重複數字出現的前一個位置和最後一個重複數字出現的
最後一個位置,但是考慮到可能會有連續的數字重複比如 [1,2,2,2,3,3,4], 因此需要考慮到
[2,2,2]和[3,3]是否相連。
ListNode* deleteDuplicates(ListNode* head) {
if(!head) return head; // 首先判斷head,如果是空連結串列直接返回
ListNode* dummpy = new ListNode; // 由於連結串列的head可能會去掉,因此定義一個啞結點方便表示
dummpy->next = nullptr;
ListNode* pre = dummpy; // 指向啞結點的指標
ListNode* cur = head; // 指向head的頭指標
// 邊界條件,既然判斷的是cur->val 和cur->next->val,那麼cur和cur->next均不應為nullptr
while(cur && cur->next != nullptr){
// 如果 x=cur->val 與下一個節點的val相同,那麼跳過所有val為x的節點,找到下一個不為x的節點
// 如果下一個節點的val仍然與下下個節點的val相同,那麼繼續,因此可以跳過所有類似[2,2,2,3,3]結構
if(cur->val == cur->next->val){
int x = cur->val;
// 邊界條件:既然判斷的是cur->val==x,那麼cur就不應該是nullptr
while(cur && cur->val == x)
cur = cur->next;
}
// 如果找到了 cur->val != cur->next->val 的節點,那麼該節點就是獨立的, 因此進行賦值即可
else{
pre->next = cur;
pre = pre->next;
cur = cur->next;
}
}
// 在這個題目中同樣定義了兩個指標,因此就會存在上一題中的情況,需要給dummpy連結串列進行封尾
pre->next = cur;
return dummpy->next;
};
Leetcode167:兩數之和
給定一個已按照升序排列的整數陣列 numbers,請你從陣列中找出兩個數滿足相加之和等於目標數
target。
輸入:numbers = [2,7,11,15], target = 9
輸出:[1,2]
解釋:2 與 7 之和等於目標數 9 。因此 index1 = 1, index2 = 2 。
題目分析:
首先最暴力的解法就是對陣列進行二重迴圈,固定左端,移動右端點,計算所有組合的和,返回和與
`target` 相同的下標組合。但是由於陣列是升序排序,因此可以考慮用雙指標來簡化時間複雜度。雙
指標的更新方式為:
如果 nums[left]+nums[right]<target 和比目標值小,左端值要變大因此右移;
否則需要減小sum, 右端值要變小因此左移
vector<int> FindTarget(vector<int>& nums, int target){
int n = nums.size();
int left=0, right=nums.size()-1;
vector<int> res;
while(left < right){
int sum = nums[left]+nums[right];
if(sum == target){
res.push_back({left, right});
return res;
}
else if(sum > target){
right--;
}
else{
left++;
}
}
return res;
}
Leetcode15:三數之和
給你一個包含 n 個整數的陣列 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b +
c = 0 ?請你找出所有和為 0 且不重複的三元組。注意:答案中不可以包含重複的三元組。
輸入:nums = [-1,0,1,2,-1,-4]
輸出:[[-1,-1,2],[-1,0,1]]
題目分析:
首先最暴力的解法就是對陣列進行三重迴圈,計算所有組合的和,返回和與 `target` 相同的下標組
合。但是這樣的話時間複雜度就是 O(n^{3}) 。我們可以利用雙指標來抵消掉一重迴圈,參考兩數和,
但是由於輸入陣列沒有規律,因此需要對陣列進行排序後重新制定雙指標更新規則。
vector<vector<int>> TribleSum(vector<int>& nums){
int n=nums.size();
if(n < 3) return nums; //如果長度小於3
sort(nums.begin(), nums.end()); //排序,利用雙指標(對撞指標)來減少時間複雜度
vector<vector<int>> res;
// 先固定一個位置,然後利用雙指標找到該位置的首尾兩端,進行查詢
for(int i=0; i<n; i++){
// 去重,本次固定的數字與上次一樣,則重複
if(i > 0 && nums[i] == nums[i-1]) continue;
// 定義雙指標的首尾位置
int left=i+1, right=n-1;
while(left<right){
int sum = nums[i]+nums[left]+nums[right];
if(sum==0){
// 新增進結果
res.push_back({nums[i], nums[left], nums[right]});
// 去重,原理是:找到最後一個相同的左端元素和右端元素
// 比如[-4, 1, 1, 1, 2, 3, 3,4] 將 [-4,1,3] 填入,然後left指向最後一個1,right指向第一個3
while(left<right && nums[left]==nums[left+1]) left++;
while(left<right && nums[right]==nums[right-1]) right--;
// 繼續移動,left和right都指向2,跳出迴圈
left++, right--;
}
else if(nums[left]+nums[right] < target)
left++;
else if(nums[left]+nums[right] > target)
right--;
}
}
return res;
}
Leetcode11:盛水最多的容器
給你 n 個非負整數 a1,a2,...,an,每個數代表座標中的一個點 (i, ai) 。在座標內畫 n 條垂
直線,垂直線 i 的兩個端點分別為 (i, ai) 和 (i, 0) 。找出其中的兩條線,使得它們與 x 軸共
同構成的容器可以容納最多的水。
輸入:[1,8,6,2,5,4,8,3,7]
輸出:49
解釋:圖中垂直線代表輸入陣列 [1,8,6,2,5,4,8,3,7]。在此情況下,容器能夠容納水(表示為藍色
部分)的最大值為 49。
題目分析:
首先最暴力的解法就是對陣列進行二重迴圈,固定左端,移動右端點,計算所有圍成的面積,記錄下最大
的那個。然而,這種二重迴圈的演算法複雜度往往可以通過雙指標來避免。即一個指標指向頭部,另一個指
針指向尾部,所圍成的面積計算公式為(right-left)*min(nums[left], nums[right])
那麼,指向較小的值的指標更新,通過滑動視窗的方式來將演算法複雜度降為O(n)。
int MaxContainer(vector<int>& nums){
int n = nums.size();
int left=0, right=n-1;
int max = INT_Min;
while(left<right) {
int height=0;
if(nums[left]<nums[right]){
h=nums[left];
left++;
}
else{
h=nums[right];
right--;
}
int area = h*(right-left);
max = max>area ? max:area;
}
return max;
}
Leetcode142:環形連結串列(返回入口節點)
給定一個連結串列,返回連結串列開始入環的第一個節點。 如果連結串列無環,則返回 null。為了表示給定連結串列中
的環,我們使用整數 pos 來表示連結串列尾連線到連結串列中的位置(索引從 0 開始)。 如果 pos 是
-1,則在該連結串列中沒有環。注意,pos 僅僅是用於標識環的情況,並不會作為引數傳遞到函式中。
題目分析:
方法一:利用雜湊表來進行查詢
我們可以建立一個雜湊表,使用節點的地址為key, 節點的值為val。定義 ListNode* cur=head,
在遍歷時查詢cur對應的val是否存在,如果存在那麼cur便為入口節點,然後返回;否則將cur新增進
雜湊表中。由於不知道迴圈次數,因此採用while(cur!=nullptr) 作為判斷條件。
ListNode *detectCycle(ListNode *head) {
ListNode* cur=head;
map<ListNode*, int> dp; // 建立雜湊表,key為ListNode*型別,value是int型別,記錄該地址的出現次數
while(cur){
if(dp[cur]==1) // 證明當前cur已經出現過,即為環形連結串列入口節點
return cur;
else // 否則,將此節點地址儲存下來
dp[cur]++;
cur = cur->next; // 更新cur,當cur指向nullptr時退出迴圈,證明連結串列無環
}
return nullptr;
}
方法二:快慢指標
我們可以使用快慢指標來查詢連結串列中是否有環,即快指標 fast 前進的速度是慢指標 slow 的兩倍,
當兩者相遇時,肯定是 fast 比 slow 多走了從相遇點到環入口節點的距離。那麼在相遇時重新定義
一個指標 pos 從head處出發,當 pos 與 slow再次相遇時,相遇的位置即是環的入口節點。
ListNode *detectCycle(ListNode *head) {
ListNode* fast=head;
ListNode* slow=head;
// 邊界條件:首先fast!=nullptr;
// 當fast->next!=nullptr時, fast=fast->next->next最多為nullptr, 可以退出迴圈
while(fast != nullptr && fast->next != nullptr){
slow = slow->next;
fast = fast->next->next;
if(fast == slow){
ListNode* pos = head;
while(pos != slow){
pos = pos->next;
slow = slow->next;
}
return pos;
}
return nullptr;
}