27. 移除元素
題意描述:
給你一個陣列
nums
和一個值val
,你需要 原地 移除所有數值等於val
的元素。元素的順序可能發生改變。然後返回nums
中與val
不同的元素的數量。假設
nums
中不等於val
的元素數量為k
,要透過此題,您需要執行以下操作:
- 更改
nums
陣列,使nums
的前k
個元素包含不等於val
的元素。nums
的其餘元素和nums
的大小並不重要。- 返回
k
。
思路:
定義快慢指標fast、slow,fast記錄新陣列元素,slow記錄新陣列下標。
AC程式碼:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for(int fast = 0 ; fast < nums.size() ; fast ++){
if(nums[fast] != val) nums[slow++] = nums[fast];
}
return slow;
}
};
344.反轉字串
題意描述:
編寫一個函式,其作用是將輸入的字串反轉過來。輸入字串以字元陣列
s
的形式給出。不要給另外的陣列分配額外的空間,你必須原地修改輸入陣列、使用 O(1) 的額外空間解決這一問題。
示例 1:
輸入:s = ["h","e","l","l","o"] 輸出:["o","l","l","e","h"]
示例 2:
輸入:s = ["H","a","n","n","a","h"] 輸出:["h","a","n","n","a","H"]
提示:
1 <= s.length <= 105
s[i]
都是 ASCII 碼錶中的可列印字元
思路:
定義雙指標i 從左邊開始遍歷、j從右邊開始遍歷,每次交換字元元素。
AC程式碼:
class Solution{
public:
void reverseString(vector<char>& s){
for(int i = 0 , j = s.size() - 1 ; i < s.size() / 2 ; i ++ , j --) swap(s[i] , s[j]);
}
};//函式是void,故不用return
替換數字
題意描述:
給定一個字串 s,它包含小寫字母和數字字元,請編寫一個函式,將字串中的字母字元保持不變,而將每個數字字元替換為number。
例如,對於輸入字串 "a1b2c3",函式應該將其轉換為 "anumberbnumbercnumber"。
對於輸入字串 "a5b",函式應該將其轉換為 "anumberb"
輸入:一個字串 s,s 僅包含小寫字母和數字字元。
輸出:列印一個新的字串,其中每個數字字元都被替換為了number
樣例輸入:a1b2c3
樣例輸出:anumberbnumbercnumber
資料範圍:1 <= s.length < 10000。
思路:
先統計原字串
s
中數字的個數cnt
, 將s
擴容到目標大小,即s.size() + 5 * cnt
,然後定義新舊指標(我這裡用的快慢)分別指向新舊字串陣列的最後一個元素,如果遇到數字,新陣列倒序輸入number
,其他情況複製舊陣列元素到新陣列。
AC程式碼:
#include<iostream>
using namespace std;
int main(){
string s;
cin >> s;
int n = s.size(), cnt = 0;
for(int fast = 0 ; fast < n ; fast++){
if(s[fast] >= '0' && s[fast] <= '9') cnt++;
}
s.resize( n + 5 * cnt);
for(int fast = s.size() - 1 , slow = n - 1; slow >= 0 ;slow -- ){
if(s[slow] >= '0' && s[slow] <= '9'){
s[fast --] = 'r';
s[fast --] = 'e';
s[fast --] = 'b';
s[fast --] = 'm';
s[fast --] = 'u';
s[fast --] = 'n';
}
else {
s[fast --] = s[slow];
}
}
cout << s;
}
其實很多陣列填充類的問題,其做法都是先預先給陣列擴容帶填充後的大小,然後在從後向前進行操作。
這麼做有兩個好處:
- 不用申請新陣列。
- 從後向前填充元素,避免了從前向後填充元素時,每次新增元素都要將新增元素之後的所有元素向後移動的問題。
M:151.翻轉字串裡的單詞
題意描述:
給你一個字串
s
,請你反轉字串中 單詞 的順序。單詞 是由非空格字元組成的字串。
s
中使用至少一個空格將字串中的 單詞 分隔開。返回 單詞 順序顛倒且 單詞 之間用單個空格連線的結果字串。
注意:輸入字串
s
中可能會存在前導空格、尾隨空格或者單詞間的多個空格。返回的結果字串中,單詞間應當僅用單個空格分隔,且不包含任何額外的空格。示例 1:
輸入:s = "the sky is blue" 輸出:"blue is sky the"
示例 2:
輸入:s = " hello world " 輸出:"world hello" 解釋:反轉後的字串中不能存在前導空格和尾隨空格。
示例 3:
輸入:s = "a good example" 輸出:"example good a" 解釋:如果兩個單詞間有多餘的空格,反轉後的字串需要將單詞間的空格減少到僅有一個。
提示:
1 <= s.length <= 104
s
包含英文大小寫字母、數字和空格' '
s
中 至少存在一個 單詞進階:如果字串在你使用的程式語言中是一種可變資料型別,請嘗試使用
O(1)
額外空間複雜度的 原地 解法。
思路:
- 去除多餘空格,思路與移除元素相同,val = ‘ ’;
- 用reverse函式將整個字串翻轉
- 翻轉單詞
AC程式碼:
removeExtraSpaces
函式,從去除前面,中間,後面空格的順序來寫。
//版本一
void removeExtraSpaces(string& s){
int slow = 0, fast = 0;
// 去掉字串前面的空格
while(s.size() > 0 && fast < s.size() && s[fast] == ' ') fast ++;
// 去掉字串中間部分的冗餘空格
for( ; fast < s.size() ; fast++){
if(fast - 1 > 0 && s[fast - 1] == s[fast] && s[fast] == ' ') continue;
else s[slow ++] = s[fast];
}
//後面此時最多有一個空格,重新設定字串大小
if(slow - 1 > 0 && s[slow - 1] == ' ') s.resize(slow - 1)
else s.resize(slow);
}
// 版本二
void removeExtraSpaces(string& s){
int slow = 0;
for(int fast = 0 ; fast < s.size() ; fast ++){
//遇到非空格就處理,否則跳出到下一吃迴圈,這裡刪除了所有空格。
if(s[fast] != ' '){
//給單詞之間新增空格。slow != 0說明不是第一個單詞,需要在單詞前新增空格。
if(slow != 0) s[slow ++] = ' ';
//補上該單詞,遇到空格說明單詞結束。
while(fast < s.size() && s[fast] != ' ') s[slow ++] = s[fast ++];
}
}
//slow的大小即為去除多餘空格後的大小。
s.resize(slow);
}
整體程式碼:
class Solution{
public:
/*翻轉,區間寫法:左閉右閉 []
void reverse(string& s , int start , int end){
for(int i = start , j = end ; i < j ; i ++ , j --) swap(s[i] , s[j]);
}
我這裡用庫函式reverse
*/
void removeExtraSpaces(string& s){
int slow = 0;
for(int fast = 0 ; fast < s.size() ; fast ++){
//遇到非空格就處理,否則跳出到下一吃迴圈,這裡刪除了所有空格。
if(s[fast] != ' '){
//給單詞之間新增空格。slow != 0說明不是第一個單詞,需要在單詞前新增空格。
if(slow != 0) s[slow ++] = ' ';
//補上該單詞,遇到空格說明單詞結束。
while(fast < s.size() && s[fast] != ' ') s[slow ++] = s[fast ++];
}
}
//slow的大小即為去除多餘空格後的大小。
s.resize(slow);
}
string reverseWords(string s) {
//去除多餘空格,保證單詞之間之只有一個空格,且字串首尾沒空格。
removeExtraSpaces(s);
reverse(s.begin() , s.end());//STL中reverse函式是左閉右開
//removeExtraSpaces後保證第一個單詞的開始下標一定是0。
int start = 0;
for(int i = 0 ; i <= s.size() ; i++){
//到達空格或者串尾,說明一個單詞結束。進行翻轉。
if(i == s.size() || s[i] == ' ') {
//翻轉,注意是左閉右閉 []的翻轉。start ~ i - 1
reverse(s.begin() + start , s.begin() + i);
//更新下一個單詞的開始下標start
start = i + 1;
}
}
return s;
}
};
206.反轉連結串列
題意描述:
題意:反轉一個單連結串列。
示例: 輸入: 1->2->3->4->5->NULL 輸出: 5->4->3->2->1->NULL
思路:
只需改變next指標朝向,原地翻轉即可,定義pre , cur , tmp 指標分別指向 前, 現在, 下 節點。
AC程式碼:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* tmp; // 儲存cur的下一個節點
ListNode* cur = head;
ListNode* pre = NULL;
while(cur) {
tmp = cur->next; // 儲存一下 cur的下一個節點,因為接下來要改變cur->next
cur->next = pre; // 翻轉操作
// 更新pre 和 cur指標
pre = cur;
cur = tmp;
}
return pre;
}
};
另解:遞迴法
遞迴法相對抽象一些,但是其實和雙指標法是一樣的邏輯,同樣是當
cur
為空的時候迴圈結束,不斷將cur
指向pre
的過程。關鍵是初始化的地方,可能有的同學會不理解, 可以看到雙指標法中初始化
cur = head``,pre = NULL
,在遞迴法中可以從如下程式碼看出初始化的邏輯也是一樣的,只不過寫法變了。具體可以看程式碼(已經詳細註釋),雙指標法寫出來之後,理解如下遞迴寫法就不難了,程式碼邏輯都是一樣的。
class Solution {
public:
ListNode* reverse(ListNode* pre,ListNode* cur){
if(cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
// 可以和雙指標法的程式碼進行對比,如下遞迴的寫法,其實就是做了這兩步
// pre = cur;
// cur = temp;
return reverse(cur,temp);
}
ListNode* reverseList(ListNode* head) {
// 和雙指標法初始化是一樣的邏輯
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(NULL, head);
}
};
- 時間複雜度: O(n), 要遞迴處理連結串列的每個節點
- 空間複雜度: O(n), 遞迴呼叫了 n 層棧空間
我們可以發現,上面的遞迴寫法和雙指標法實質上都是從前往後翻轉指標指向,其實還有另外一種與雙指標法不同思路的遞迴寫法:從後往前翻轉指標指向。
具體程式碼如下(帶詳細註釋):
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 邊緣條件判斷
if(head == NULL) return NULL;
if (head->next == NULL) return head;
// 遞迴呼叫,翻轉第二個節點開始往後的連結串列,這一步仔細體會
ListNode *last = reverseList(head->next);
// 翻轉頭節點與第二個節點的指向
head->next->next = head;
// 此時的 head 節點為尾節點,next 需要指向 NULL
head->next = NULL;
return last;
}
};
19.刪除連結串列的倒數第N個節點
題意描述:
給你一個連結串列,刪除連結串列的倒數第
n
個結點,並且返回連結串列的頭結點。示例 1:
輸入:head = [1,2,3,4,5], n = 2 輸出:[1,2,3,5]
示例 2:
輸入:head = [1], n = 1 輸出:[]
示例 3:
輸入:head = [1,2], n = 1 輸出:[1]
思路:
定義雙指標
fast 、 slow
,先讓fast
走n + 1 步 ,然後fast 、 slow
同時走,fas
t走到結尾時,slow
的下一個元素恰好是待刪除元素,此時令slow-> next = slow -> next -> next
即可。
AC程式碼:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* slow = dummyHead;
ListNode* fast = dummyHead;
//while(n --)執行順序說明:先判斷n>0? 如果是則 -1
while(n-- && fast != NULL) {
fast = fast->next;
}
fast = fast->next; // fast再提前走一步,因為需要讓slow指向刪除節點的上一個節點,結合上面的圖可以理解,這一步很重要
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
// ListNode *tmp = slow->next; C++釋放記憶體的邏輯
// slow->next = tmp->next;
// delete tmp;
return dummyHead->next;
}
};
面試題 02.07. 連結串列相交
題意描述:
給你兩個單連結串列的頭節點
headA
和headB
,請你找出並返回兩個單連結串列相交的起始節點。如果兩個連結串列沒有交點,返回null
。圖示兩個連結串列在節點
c1
開始相交:[
題目資料 保證 整個鏈式結構中不存在環。
注意,函式返回結果後,連結串列必須 保持其原始結構 。
示例 1:
輸入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 輸出:Intersected at '8' 解釋:相交節點的值為 8 (注意,如果兩個連結串列相交則不能為 0)。 從各自的表頭開始算起,連結串列 A 為 [4,1,8,4,5],連結串列 B 為 [5,0,1,8,4,5]。 在 A 中,相交節點前有 2 個節點;在 B 中,相交節點前有 3 個節點。
示例 2:
[
輸入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 輸出:Intersected at '2' 解釋:相交節點的值為 2 (注意,如果兩個連結串列相交則不能為 0)。 從各自的表頭開始算起,連結串列 A 為 [0,9,1,2,4],連結串列 B 為 [3,2,4]。 在 A 中,相交節點前有 3 個節點;在 B 中,相交節點前有 1 個節點。
示例 3:
[
輸入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 輸出:null 解釋:從各自的表頭開始算起,連結串列 A 為 [2,6,4],連結串列 B 為 [1,5]。 由於這兩個連結串列不相交,所以 intersectVal 必須為 0,而 skipA 和 skipB 可以是任意值。 這兩個連結串列不相交,因此返回 null 。
提示:
listA
中節點數目為m
listB
中節點數目為n
0 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
- 如果
listA
和listB
沒有交點,intersectVal
為0
- 如果
listA
和listB
有交點,intersectVal == listA[skipA + 1] == listB[skipB + 1]
進階:你能否設計一個時間複雜度
O(n)
、僅用O(1)
記憶體的解決方案?
思路:
定義雙指標
curA 、curB
, 目前curA
指向連結串列A的頭結點,curB
指向連結串列B的頭結點,計算AB連結串列長度差gap
假如lenA > lenB
(如果不是就swap(lenA,lenB), swap(curA , curB)
。讓curA
先走gap
步,然後curA、curB
同時走到末尾,過程中比較指標是否相等即可。
AC程式碼:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0, lenB = 0;
while (curA != NULL) { // 求連結串列A的長度
lenA++;
curA = curA->next;
}
while (curB != NULL) { // 求連結串列B的長度
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
// 讓curA為最長連結串列的頭,lenA為其長度
if (lenB > lenA) {
swap (lenA, lenB);
swap (curA, curB);
}
// 求長度差
int gap = lenA - lenB;
// 讓curA和curB在同一起點上(末尾位置對齊)
while (gap--) {
curA = curA->next;
}
// 遍歷curA 和 curB,遇到相同則直接返回
while (curA != NULL) {
if (curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
M:142.環形連結串列II
題意描述:
給定一個連結串列的頭節點
head
,返回連結串列開始入環的第一個節點。 如果連結串列無環,則返回null
。如果連結串列中有某個節點,可以透過連續跟蹤
next
指標再次到達,則連結串列中存在環。 為了表示給定連結串列中的環,評測系統內部使用整數pos
來表示連結串列尾連線到連結串列中的位置(索引從 0 開始)。如果pos
是-1
,則在該連結串列中沒有環。注意:pos
不作為引數進行傳遞,僅僅是為了標識連結串列的實際情況。不允許修改 連結串列。
示例 1:
輸入:head = [3,2,0,-4], pos = 1 輸出:返回索引為 1 的連結串列節點 解釋:連結串列中有一個環,其尾部連線到第二個節點。
示例 2:
輸入:head = [1,2], pos = 0 輸出:返回索引為 0 的連結串列節點 解釋:連結串列中有一個環,其尾部連線到第一個節點。
示例 3:
輸入:head = [1], pos = -1 輸出:返回 null 解釋:連結串列中沒有環。
提示:
- 連結串列中節點的數目範圍在範圍
[0, 104]
內-105 <= Node.val <= 105
pos
的值為-1
或者連結串列中的一個有效索引進階:你是否可以使用
O(1)
空間解決此題?
思路:
主要考察兩知識點:
- 判斷連結串列是否環
- 如果有環,如何找到這個環的入口
- 判斷是否有環
定義快慢指標
fast
、slow
,fast
每次走兩步 ,slow
每次走一步 , 若迴圈中相遇則有環。
- 環的入口
假設從頭結點到環形入口節點 的節點數為
x
。 環形入口節點到fast
指標與slow
指標相遇節點 節點數為y
。 從相遇節點 再到環形入口節點節點數為z
。 如圖所示:那麼相遇時:
slow
指標走過的節點數為:x + y
,fast
指標走過的節點數:x + y + n (y + z)
,n為fast指標在環內走了n圈才遇到slow
指標, (y+z)為 一圈內節點的個數A。因為fast指標是一步走兩個節點,
slow
指標一步走一個節點, 所以 fast指標走過的節點數 = slow指標走過的節點數 * 2:(x + y) * 2 = x + y + n (y + z)
兩邊消掉一個(x+y):
x + y = n (y + z)
因為要找環形的入口,那麼要求的是x,因為x表示 頭結點到 環形入口節點的的距離。
所以要求x ,將x單獨放在左面:
x = n (y + z) - y
,再從n(y+z)中提出一個 (y+z)來,整理公式之後為如下公式:
x = (n - 1) (y + z) + z
注意這裡n一定是大於等於1的,因為 fast指標至少要多走一圈才能相遇slow指標。取n為1,意味著fast指標在環形裡轉了一圈之後,就遇到了 slow指標了。
當 n為1的時候,公式就化解為
x = z
,這就意味著,從頭結點出發一個指標,從相遇節點 也出發一個指標,這兩個指標每次只走一個節點, 那麼當這兩個指標相遇的時候就是 環形入口的節點。
也就是在相遇節點處,定義一個指標
index1
,在頭結點處定一個指標index2
。讓
index1
和index2
同時移動,每次移動一個節點, 那麼他們相遇的地方就是 環形入口的節點。
AC程式碼:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
//這裡的判斷條件是fast和fast->next不為空
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
// 快慢指標相遇,此時從head 和 相遇點,同時查詢直至相遇。上面推導n = 1的情況。
if (slow == fast) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index2; // 返回環的入口
}
}
return NULL;
}
};
M:第15題. 三數之和
題意描述:
給你一個整數陣列
nums
,判斷是否存在三元組[nums[i], nums[j], nums[k]]
滿足i != j
、i != k
且j != k
,同時還滿足nums[i] + nums[j] + nums[k] == 0
。請你返回所有和為
0
且不重複的三元組。注意:答案中不可以包含重複的三元組。
示例 1:
輸入:nums = [-1,0,1,2,-1,-4] 輸出:[[-1,-1,2],[-1,0,1]] 解釋: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元組是 [-1,0,1] 和 [-1,-1,2] 。 注意,輸出的順序和三元組的順序並不重要。
示例 2:
輸入:nums = [0,1,1] 輸出:[] 解釋:唯一可能的三元組和不為 0 。
示例 3:
輸入:nums = [0,0,0] 輸出:[[0,0,0]] 解釋:唯一可能的三元組和為 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
思路:
滑動視窗思想,先排序陣列,然後去重處理。一層for迴圈控制i遍歷整個陣列,然後定義雙指標l、 r分別在
nums[i] + nums[l] + nums[j] < 0
和>
0時右滑/左滑。如果三數之和=0,則儲存進res,然後去重處理處理,將r左移、l右移。程式碼注意的幾點:
- 結果返回的是[] 、[]的三元組形式,故定義
res
為二重陣列vector<vector<int>>
,然後在res.push_back
的時候,引數是{vector<int>{nums[i] , nums[l] , nums[r]}}
else
的邏輯,在三數之和 = 0時需將左右視窗分別移動,else push_back那裡括號較多,應分行寫。res
的return
在for迴圈之後,寫完要檢查
AC程式碼:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin() , nums.end());
for(int i = 0 ; i < nums.size() ; i++){
if(nums[i] > 0) return res;
if(i > 0 && nums[i] == nums[i - 1]) continue;
int l = i + 1 , r = nums.size() - 1;
while(r > l){
if(nums[i] + nums[l] + nums[r] > 0) r--;
else if(nums[i] + nums[l] + nums[r] <0) l++;
else {
res.push_back(vector<int>{nums[i] , nums[l] , nums[r]});
while(r > l && nums[r] == nums[r - 1]) r--;
while(r > l && nums[l] == nums[l + 1]) l ++;
r --;
l ++;
}
}
}
return res;
}
};
第18題. 四數之和
題意描述:
給你一個由
n
個整陣列成的陣列nums
,和一個目標值target
。請你找出並返回滿足下述全部條件且不重複的四元組[nums[a], nums[b], nums[c], nums[d]]
(若兩個四元組元素一一對應,則認為兩個四元組重複):
0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意順序 返回答案 。
示例 1:
輸入:nums = [1,0,-1,0,-2,2], target = 0 輸出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
輸入:nums = [2,2,2,2,2], target = 8 輸出:[[2,2,2,2]]
提示:
1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109
思路:
四數之和,和三數之和 是一個思路,都是使用雙指標法, 基本解法就是再套一層for迴圈。
但是有一些細節需要注意,例如: 不要判斷
nums[k] > target
就返回了,三數之和 可以透過nums[i] > 0
就返回了,因為 0 已經是確定的數了,四數之和這道題目target
是任意值。比如:陣列是[-4, -3, -2, -1]
,target
是-10
,不能因為-4 > -10
而跳過。但是我們依舊可以去做剪枝,邏輯變成nums[i] > target && (nums[i] >=0 || target >= 0)
就可以了。類似的二級剪枝:
nums[i] + nums[j] > target && (nums[i] + nums[j] >= 0)
去重操作還是一樣的。
三數之和 的雙指標解法是一層for迴圈
num[i]
為確定值,然後迴圈內有left
和right
下標作為雙指標,找到nums[i] + nums[left] + nums[right] == 0
。四數之和的雙指標解法是兩層for迴圈
nums[k] + nums[i]
為確定值,依然是迴圈內有left
和right
下標作為雙指標,找出nums[k] + nums[i] + nums[left] + nums[right] == target
的情況,三數之和的時間複雜度是O(n2),四數之和的時間複雜度是O(n3) 。那麼一樣的道理,五數之和、六數之和等等都採用這種解法。
AC程式碼:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin() , nums.end());
for(int i = 0 ; i < nums.size() ; i++){
// 剪枝處理
if(nums[i] > target && nums[i] >= 0) break;// 這裡使用break,統一透過最後的return返回
// 對nums[i]去重
if(i > 0 && nums[i] == nums[i - 1]) continue;
for(int j = i + 1 ; j < nums.size() ; j++){
// 二級剪枝處理
if(nums[i] + nums[j] > target && nums[i] + nums[j] >= 0) break;
// 對nums[i]去重
if(j > i + 1 && nums[j] == nums[j - 1]) continue;
int l = j + 1 , r = nums.size() - 1;
while(r > l){
// nums[k] + nums[i] + nums[left] + nums[right] > target 會溢位
if((long)nums[i] + nums[j] + nums[l] + nums[r] > target) r--;
else if((long)nums[i] + nums[j] + nums[l] + nums[r] < target) l++;
else {
res.push_back(vector<int>{nums[i] , nums[j] , nums[l] , nums[r]});
// 對nums[left]和nums[right]去重
while(r > l && nums[r] == nums[r - 1]) r--;
while(r > l && nums[l] == nums[l + 1]) l ++;
// 找到答案時,雙指標同時收縮
r --;
l ++;
}
}
}
}
return res;
}
};