6.12.雙指標專題

七龙猪發表於2024-06-12

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;
}

其實很多陣列填充類的問題,其做法都是先預先給陣列擴容帶填充後的大小,然後在從後向前進行操作。

這麼做有兩個好處:

  1. 不用申請新陣列。
  2. 從後向前填充元素,避免了從前向後填充元素時,每次新增元素都要將新增元素之後的所有元素向後移動的問題

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) 額外空間複雜度的 原地 解法。

思路:

  1. 去除多餘空格,思路與移除元素相同,val = ‘ ’;
  2. 用reverse函式將整個字串翻轉
  3. 翻轉單詞

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:

19.刪除連結串列的倒數第N個節點

輸入: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同時走,fast走到結尾時,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. 連結串列相交

題意描述:

給你兩個單連結串列的頭節點 headAheadB ,請你找出並返回兩個單連結串列相交的起始節點。如果兩個連結串列沒有交點,返回 null

圖示兩個連結串列在節點 c1 開始相交

[img

題目資料 保證 整個鏈式結構中不存在環。

注意,函式返回結果後,連結串列必須 保持其原始結構

示例 1:

img

輸入: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:

[img

輸入: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:

[img

輸入: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
  • 如果 listAlistB 沒有交點,intersectVal0
  • 如果 listAlistB 有交點,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:

img

輸入:head = [3,2,0,-4], pos = 1
輸出:返回索引為 1 的連結串列節點
解釋:連結串列中有一個環,其尾部連線到第二個節點。

示例 2:

img

輸入:head = [1,2], pos = 0
輸出:返回索引為 0 的連結串列節點
解釋:連結串列中有一個環,其尾部連線到第一個節點。

示例 3:

img

輸入:head = [1], pos = -1
輸出:返回 null
解釋:連結串列中沒有環。

提示:

  • 連結串列中節點的數目範圍在範圍 [0, 104]
  • -105 <= Node.val <= 105
  • pos 的值為 -1 或者連結串列中的一個有效索引

進階:你是否可以使用 O(1) 空間解決此題?

思路:

主要考察兩知識點:

  • 判斷連結串列是否環
  • 如果有環,如何找到這個環的入口
  1. 判斷是否有環

定義快慢指標fast slowfast每次走兩步 , slow 每次走一步 , 若迴圈中相遇則有環。

  1. 環的入口

假設從頭結點到環形入口節點 的節點數為x。 環形入口節點到 fast指標與slow指標相遇節點 節點數為y。 從相遇節點 再到環形入口節點節點數為 z。 如圖所示:

img

那麼相遇時: 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

index1index2同時移動,每次移動一個節點, 那麼他們相遇的地方就是 環形入口的節點。

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 != ji != kj != 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那裡括號較多,應分行寫。
  • resreturn在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
  • abcd 互不相同
  • 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]為確定值,然後迴圈內有leftright下標作為雙指標,找到nums[i] + nums[left] + nums[right] == 0

四數之和的雙指標解法是兩層for迴圈nums[k] + nums[i]為確定值,依然是迴圈內有leftright下標作為雙指標,找出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;
    
    }
};

相關文章