日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[力扣刷题总结](双指针篇)

發布時間:2023/12/20 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [力扣刷题总结](双指针篇) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • |||||||||||||||||||| 雙指針 ||||||||||||||||||
  • 905. 按奇偶排序數組
    • 解法1:雙指針+原地交換
    • 解法2:兩次遍歷+保持相對位置
  • 475. 供暖器
    • 解法1:雙指針+貪心
  • 202. 快樂數
    • 解法1:快慢指針
  • 相似題目:141. 環形鏈表
    • 解法1:快慢指針
  • 相似題目:142. 環形鏈表 II
    • 解法1:快慢指針
    • 解法2:哈希表
  • 相似題目:287. 尋找重復數
    • 解法1:快慢指針
    • 解法2:二分查找
  • 15. 三數之和
    • 解法1:雙指針
  • 相似題目:611. 有效三角形的個數
    • 解法1:雙指針
  • 相似題目:16. 最接近的三數之和
    • 解法1:雙指針+排序
  • 相似題目:1. 兩數之和
    • 解法1:哈希表
  • 31. 下一個排列
    • 解法1:兩遍掃描+雙指針
  • 165. 比較版本號
    • 解法1:雙指針
  • 75. 顏色分類
    • 解法1:雙指針+一次遍歷
    • 解法2:雙指針+一次遍歷
  • ~~縮減搜索空間的思想~~
  • 11. 盛最多水的容器
    • 解法1:雙指針
  • 240. 搜索二維矩陣 II
    • 解法1:雙指針+二叉搜索樹
  • 167. 兩數之和 II - 輸入有序數組
    • 解法1:雙指針
  • |||||||||||||||| 滑動窗口 ||||||||||||||||||
  • 992. K 個不同整數的子數組
    • 解法1:雙指針(滑動窗口)
  • 相似題目:904. 水果成籃
    • 解法1:雙指針(滑動窗口)
  • 相似題目:76. 最小覆蓋子串
    • 解法1:滑動窗口
  • 3. 無重復字符的最長子串
    • 解法1:滑動窗口+哈希
  • 424. 替換后的最長重復字符
    • 解法1:滑動窗口
  • 相似題目:485. 最大連續 1 的個數
    • 解法1:數組+一次遍歷
  • 劍指 Offer 59 - I. 滑動窗口的最大值
    • 解法1:滑動窗口+單調隊列+雙向隊列
    • 解法2:優先隊列+滑動窗口
  • 相似題目:劍指 Offer 59 - II. 隊列的最大值
    • 解法1:單調隊列+滑動窗口
  • 1052. 愛生氣的書店老板
    • 解法1:數組+滑動窗口
  • 209. 長度最小的子數組
    • 解法1:滑動窗口
  • 相似題目:718. 最長重復子數組
    • 解法1:動態規劃
  • 567. 字符串的排列
    • 解法1:滑動窗口


|||||||||||||||||||| 雙指針 ||||||||||||||||||

905. 按奇偶排序數組

力扣鏈接
給你一個整數數組 nums,將 nums 中的的所有偶數元素移動到數組的前面,后跟所有奇數元素。

返回滿足此條件的 任一數組 作為答案。

示例 1:

輸入:nums = [3,1,2,4]
輸出:[2,4,3,1]
解釋:[4,2,3,1]、[2,4,1,3] 和 [4,2,1,3] 也會被視作正確答案。
示例 2:

輸入:nums = [0]
輸出:[0]

提示:

1 <= nums.length <= 5000
0 <= nums[i] <= 5000

解法1:雙指針+原地交換

class Solution { public:vector<int> sortArrayByParity(vector<int>& nums) {int left = 0, right = nums.size() - 1;while (left < right) {while (left < right and nums[left] % 2 == 0) {left++;}while (left < right and nums[right] % 2 == 1) {right--;}if (left < right) {swap(nums[left++], nums[right--]);}}return nums;} };

解法2:兩次遍歷+保持相對位置

class Solution { public:vector<int> sortArrayByParity(vector<int>& nums) {vector<int> res;for (auto & num : nums) {if (num % 2 == 0) {res.push_back(num);}}for (auto & num : nums) {if (num % 2 == 1) {res.push_back(num);}}return res;} };

475. 供暖器

力扣鏈接
冬季已經來臨。 你的任務是設計一個有固定加熱半徑的供暖器向所有房屋供暖。

在加熱器的加熱半徑范圍內的每個房屋都可以獲得供暖。

現在,給出位于一條水平線上的房屋 houses 和供暖器 heaters 的位置,請你找出并返回可以覆蓋所有房屋的最小加熱半徑。

說明:所有供暖器都遵循你的半徑標準,加熱的半徑也一樣。

示例 1:

輸入: houses = [1,2,3], heaters = [2]
輸出: 1
解釋: 僅在位置2上有一個供暖器。如果我們將加熱半徑設為1,那么所有房屋就都能得到供暖。

示例 2:

輸入: houses = [1,2,3,4], heaters = [1,4]
輸出: 1
解釋: 在位置1, 4上有兩個供暖器。我們需要將加熱半徑設為1,這樣所有房屋就都能得到供暖。

示例 3:

輸入:houses = [1,5], heaters = [2]
輸出:3

提示:

1 <= houses.length, heaters.length <= 3 * 104
1 <= houses[i], heaters[i] <= 109

解法1:雙指針+貪心

思路:
這是一道很好的貪心+雙指針的應用題

我們需要保證每個房屋至少在一個加熱器的供暖范圍內,那為了讓加熱半徑最小,我們只需要保證每個房屋最近的加熱器的距離小于加熱半徑

那全局最低的加熱半徑;自然也就等于所有房屋到最近加熱器的距離中的最大值。 這是一個min(max)的問題。

怎么求呢?

如果我們的房屋和加熱器都是按照橫坐標排序的;那顯然,我們只需要順次對每個房子找和他相鄰的前后兩個加熱器即可。

兩個指針分別標記房屋和加熱器;不斷移動加熱器,直至加熱器的橫坐標大于房屋橫坐標。 則當前加熱器指針 cur 和 cur-1 就是房屋左邊的加熱器和右邊的加熱器
我們求兩者到房屋距離中的較小值,就是該房屋最近的加熱器到房屋的距離。

遍歷所有的房屋,取最大值即可。

代碼:

class Solution { public:int findRadius(vector<int>& houses, vector<int>& heaters) {int result = 0;sort(heaters.begin(),heaters.end());sort(houses.begin(),houses.end());int cur = 0;for(int i = 0;i<houses.size();i++){int curDis = abs(houses[i]-heaters[cur]);while(cur < heaters.size()-1 && abs(houses[i]-heaters[cur+1]) <= curDis){curDis = min(abs(houses[i]-heaters[cur+1]),curDis);cur++;}result = max(result,curDis);}return result;} };

202. 快樂數

力扣鏈接
編寫一個算法來判斷一個數 n 是不是快樂數。

「快樂數」定義為:

對于一個正整數,每一次將該數替換為它每個位置上的數字的平方和。
然后重復這個過程直到這個數變為 1,也可能是 無限循環 但始終變不到 1。
如果 可以變為 1,那么這個數就是快樂數。
如果 n 是快樂數就返回 true ;不是,則返回 false 。

示例 1:

輸入:n = 19
輸出:true
解釋:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:

輸入:n = 2
輸出:false

提示:

1 <= n <= 231 - 1

解法1:快慢指針

思路:

(1)使用 “快慢指針” 思想,找出循環:“快指針” 每次走兩步“慢指針” 每次走一步當二者相等時,即為一個循環周期。此時,判斷是不是因為 1 引起的循環,是的話就是快樂數,否則不是快樂數。

(2)這個算法是兩個奔跑選手,一個跑的快,一個跑得慢。在龜兔賽跑的寓言中,跑的慢的稱為 “烏龜”,跑得快的稱為 “兔子”。

不管烏龜和兔子在循環中從哪里開始,它們最終都會相遇。這是因為兔子每走一步就向烏龜靠近一個節點(在它們的移動方向上)。

(3)注意:此題不建議用集合記錄每次的計算結果來判斷是否進入循環,因為這個集合可能大到無法存儲;另外,也不建議使用遞歸,同理,如果遞歸層次較深,會直接導致調用棧崩潰。不要因為這個題目給出的整數是 int 型而投機取巧。

代碼:

class Solution { public:int get_next(int n){int sum = 0;while(n>0){sum += (n%10)*(n%10);n /= 10;}return sum;}bool isHappy(int n) {int slow = n, fast = n;do{slow = get_next(slow);fast = get_next(fast);fast = get_next(fast);} while(slow != fast);//判斷是否有循環return slow == 1;} };

相似題目:141. 環形鏈表

力扣鏈接
給你一個鏈表的頭節點 head ,判斷鏈表中是否有環。

如果鏈表中有某個節點,可以通過連續跟蹤 next 指針再次到達,則鏈表中存在環。 為了表示給定鏈表中的環,評測系統內部使用整數 pos 來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。如果 pos 是 -1,則在該鏈表中沒有環。注意:pos 不作為參數進行傳遞,僅僅是為了標識鏈表的實際情況。

如果鏈表中存在環,則返回 true 。 否則,返回 false 。

示例 1:

輸入:head = [3,2,0,-4], pos = 1
輸出:true
解釋:鏈表中有一個環,其尾部連接到第二個節點。

示例 2:

輸入:head = [1,2], pos = 0
輸出:true
解釋:鏈表中有一個環,其尾部連接到第一個節點。

示例 3:

輸入:head = [1], pos = -1
輸出:false
解釋:鏈表中沒有環。

提示:

鏈表中節點的數目范圍是 [0, 104]
-105 <= Node.val <= 105
pos 為 -1 或者鏈表中的一個 有效索引 。

進階:你能用 O(1)(即,常量)內存解決此問題嗎?

解法1:快慢指針

參考
思路:

**當一個鏈表有環時,快慢指針都會陷入環中進行無限次移動,然后變成了追及問題。**想象一下在操場跑步的場景,只要一直跑下去,快的總會追上慢的。當兩個指針都進入環后,每輪移動使得慢指針到快指針的距離增加一,同時快指針到慢指針的距離也減少一,只要一直移動下去,快指針總會追上慢指針。

代碼:

/*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(NULL) {}* };*/ class Solution { public:bool hasCycle(ListNode *head) {ListNode* slow = head, *fast = head;while(fast != NULL && fast->next != NULL){fast = fast->next->next;slow = slow->next;if (fast == slow) return true;}return false;} };

復雜度分析:

時間復雜度:O(N),其中 N 是鏈表中的節點數。

當鏈表中不存在環時,快指針將先于慢指針到達鏈表尾部,鏈表中每個節點至多被訪問兩次。

當鏈表中存在環時,每一輪移動后,快慢指針的距離將減小一。而初始距離為環的長度,因此至多移動 N輪。

空間復雜度:O(1)。我們只使用了兩個指針的額外空間。

相似題目:142. 環形鏈表 II

力扣鏈接
給定一個鏈表,返回鏈表開始入環的第一個節點。 如果鏈表無環,則返回 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) 空間解決此題?

解法1:快慢指針

思路:

這道題目,不僅考察對鏈表的操作,而且還需要一些數學運算。

主要考察兩知識點:

1.判斷鏈表是否環 2.如果有環,如何找到這個環的入口

(1)
(3)

代碼:

/*** 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, *slow = head;while(fast != NULL && fast->next != NULL){fast = fast->next->next;slow = slow->next;if(slow == fast){ListNode* index = head;while(index != slow){index = index->next;slow = slow->next;}return index;}}return NULL;} };

復雜度分析:

時間復雜度:O(N),其中 N 為鏈表中節點的數目。在最初判斷快慢指針是否相遇時,slow 指針走過的距離不會超過鏈表的總長度;隨后尋找入環點時,走過的距離也不會超過鏈表的總長度。因此,總的執行時間為 O(N)+O(N)=O(N)。

空間復雜度:O(1)。我們只使用了slow,fast,ptr 三個指針。

解法2:哈希表

思路:
一個非常直觀的思路是:我們遍歷鏈表中的每個節點,并將它記錄下來;一旦遇到了此前遍歷過的節點,就可以判定鏈表中存在環。借助哈希表可以很方便地實現。

代碼:

/*** 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) {unordered_map<ListNode*,int> umap;ListNode* ptr = head;while(ptr!=NULL){if(umap[ptr] > 1) return ptr;umap[ptr]++;ptr = ptr->next;}return NULL;} };

復雜度分析:

時間復雜度:O(N),其中 N 為鏈表中節點的數目。我們恰好需要訪問鏈表中的每一個節點。

空間復雜度:O(N),其中 N 為鏈表中節點的數目。我們需要將鏈表中的每個節點都保存在哈希表當中。

相似題目:287. 尋找重復數

力扣鏈接
給定一個包含 n + 1 個整數的數組 nums ,其數字都在 1 到 n 之間(包括 1 和 n),可知至少存在一個重復的整數。

假設 nums 只有 一個重復的整數 ,找出 這個重復的數 。

你設計的解決方案必須不修改數組 nums 且只用常量級 O(1) 的額外空間。

示例 1:

輸入:nums = [1,3,4,2,2]
輸出:2
示例 2:

輸入:nums = [3,1,3,4,2]
輸出:3
示例 3:

輸入:nums = [1,1]
輸出:1
示例 4:

輸入:nums = [1,1,2]
輸出:1

提示:

1 <= n <= 105
nums.length == n + 1
1 <= nums[i] <= n
nums 中 只有一個整數 出現 兩次或多次 ,其余整數均只出現 一次

進階:

如何證明 nums 中至少存在一個重復的數字?
你可以設計一個線性級時間復雜度 O(n) 的解決方案嗎?

解法1:快慢指針

思路:


代碼:

class Solution { public:int findDuplicate(vector<int>& nums) {//快慢指針int slow = 0, fast = 0;while(true){slow = nums[slow];fast = nums[nums[fast]];if(slow == fast){fast = 0;while(nums[slow] != nums[fast]){fast = nums[fast];slow = nums[slow];}return nums[slow];}}} };

復雜度分析:

時間復雜度:O(n)。「Floyd 判圈算法」時間復雜度為線性的時間復雜度。

空間復雜度:O(1)。我們只需要常數空間存放若干變量。

解法2:二分查找

思路:

找到不正常(nums中值為該下標的數重復)的那個數組下標

代碼:

class Solution { public:int findDuplicate(vector<int>& nums) {int left = 0, right = nums.size() - 1;while(left < right){// 猜測中間點數重復,查找小于等于該中間數的個數,如果等于該中間數說明沒有重復,區間上移動,否則該區間有重復數int mid = left + (right - left)/2;int count = 0;for(auto& num:nums){if(num<=mid) count++;}// 如果小于等于該數的值的數量等于該數,則向上滑動區間if(count<=mid){left = mid + 1;}else{right = mid;}}return left;} };

復雜度分析:

時間復雜度:O(nlogn),其中 n 為 nums 數組的長度。二分查找最多需要二分 O(logn) 次,每次判斷的時候需要O(n) 遍歷nums 數組求解小于等于 mid 的數的個數,因此總時間復雜度為 O(nlogn)。
空間復雜度:O(1)。我們只需要常數空間存放若干變量。

15. 三數之和

力扣鏈接
給你一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有和為 0 且不重復的三元組。

注意:答案中不可以包含重復的三元組。

示例 1:

輸入:nums = [-1,0,1,2,-1,-4]
輸出:[[-1,-1,2],[-1,0,1]]

示例 2:

輸入:nums = []
輸出:[]

示例 3:

輸入:nums = [0]
輸出:[]

提示:

0 <= nums.length <= 3000
-105 <= nums[i] <= 105

解法1:雙指針

思路:
(1)這道題目使用哈希法并不十分合適,因為在去重的操作中有很多細節需要注意,在面試中很難直接寫出沒有bug的代碼。

而且使用哈希法 在使用兩層for循環的時候,能做的剪枝操作很有限,雖然時間復雜度是O(n2)O(n^2)O(n2),也是可以在leetcode上通過,但是程序的執行時間依然比較長 。

(2)接下來我來介紹另一個解法:雙指針法,這道題目使用雙指針法 要比哈希法高效一些,那么來講解一下具體實現的思路。

拿這個nums數組來舉例,首先將數組排序,然后有一層for循環,i從下標0的地方開始,同時定一個下標left 定義在i+1的位置上,定義下標right 在數組結尾的位置上。

依然還是在數組中找到 abc 使得a + b +c =0,我們這里相當于 a = nums[i] b = nums[left] c = nums[right]。

接下來如何移動left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就說明 此時三數之和大了,因為數組是排序后了,所以right下標就應該向左移動,這樣才能讓三數之和小一些。

如果 nums[i] + nums[left] + nums[right] < 0 說明 此時 三數之和小了,left 就向右移動,才能讓三數之和大一些,直到left與right相遇為止。

代碼:

class Solution { public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> result;sort(nums.begin(),nums.end());for(int i = 0;i<nums.size();i++){if(nums[i] > 0) return result;//去重if(i>0 && nums[i] == nums[i-1] ) continue;int left = i+1;int right = nums.size() - 1;while(right > left){if(nums[i] + nums[left] + nums[right] < 0) left++;else if(nums[i] + nums[left] + nums[right] > 0) right--;else{result.push_back(vector<int>{nums[i],nums[left],nums[right]}); 去重邏輯應該放在找到一個三元組之后while(right>left && nums[right] == nums[right-1]) right--;while(right>left && nums[left] == nums[left+1]) left++;right--;left++;}}}return result;} };

復雜度分析:

時間復雜度:O(n2)O(n^2)O(n2)

相似題目:611. 有效三角形的個數

力扣鏈接
給定一個包含非負整數的數組 nums ,返回其中可以組成三角形三條邊的三元組個數。

示例 1:

輸入: nums = [2,2,3,4]
輸出: 3
解釋:有效的組合是:
2,3,4 (使用第一個 2)
2,3,4 (使用第二個 2)
2,2,3
示例 2:

輸入: nums = [4,2,3,4]
輸出: 4

提示:

1 <= nums.length <= 1000
0 <= nums[i] <= 1000

解法1:雙指針

class Solution { public:int triangleNumber(vector<int>& nums) {int res = 0;int n = nums.size();sort(nums.begin(),nums.end());for(int i = n-1;i>=2;i--){int left = 0, right = i-1;while(left < right){if(nums[left] + nums[right] > nums[i]){res+= right - left;//i, r 和從l到r-1都可組成三角形,個數為 (r-1) - l + 1 = r - lright--;}else{left++;}}}return res;} };

相似題目:16. 最接近的三數之和

力扣鏈接
給你一個長度為 n 的整數數組 nums 和 一個目標值 target。請你從 nums 中選出三個整數,使它們的和與 target 最接近。

返回這三個數的和。

假定每組輸入只存在恰好一個解。

示例 1:

輸入:nums = [-1,2,1,-4], target = 1
輸出:2
解釋:與 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
示例 2:

輸入:nums = [0,0,0], target = 1
輸出:0

提示:

3 <= nums.length <= 1000
-1000 <= nums[i] <= 1000
-104 <= target <= 104

解法1:雙指針+排序

class Solution { public:int threeSumClosest(vector<int>& nums, int target) {sort(nums.begin(),nums.end());int n = nums.size();int clostSum = nums[0] + nums[1] + nums[2];for(int i = 0;i<n-2;i++){if(i > 0 && nums[i-1] == nums[i]) continue;int left = i+1, right = n - 1;while(left<right){int threeSum = nums[i] + nums[left] + nums[right];if(abs(threeSum-target)<abs(clostSum-target)){clostSum = threeSum;}if(threeSum > target){right--;while(left < right && nums[right] == nums[right+1]){right--;}}else if(threeSum < target){left++;while(left < right && nums[left] == nums[left-1]){left++;}}else return target;}}return clostSum;} };

相似題目:1. 兩數之和

力扣鏈接

給定一個整數數組 nums 和一個整數目標值 target,請你在該數組中找出 和為目標值 target 的那 兩個 整數,并返回它們的數組下標。

你可以假設每種輸入只會對應一個答案。但是,數組中同一個元素在答案里不能重復出現。

你可以按任意順序返回答案。

示例 1:

輸入:nums = [2,7,11,15], target = 9
輸出:[0,1]
解釋:因為 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:

輸入:nums = [3,2,4], target = 6
輸出:[1,2]

示例 3:

輸入:nums = [3,3], target = 6
輸出:[0,1]

提示:

2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只會存在一個有效答案
進階:你可以想出一個時間復雜度小于 O(n2) 的算法嗎?

解法1:哈希表

思路:

代碼:

class Solution { public:vector<int> twoSum(vector<int>& nums, int target) {unordered_map<int,int> umap;for(int i = 0;i<nums.size();i++){auto iter = umap.find(target-nums[i]);if(iter != umap.end()) return {iter->second,i};umap.insert(pair<int,int>(nums[i],i));}return {};} }; class Solution { public:vector<int> twoSum(vector<int>& nums, int target) {unordered_map<int,int> umap;//target-i ifor(int i = 0;i<nums.size();i++){umap[target-nums[i]] = i;}vector<int> result;for(int i = 0;i<nums.size();i++){if(umap.count(nums[i]) > 0 && umap[nums[i]] != i) {result.push_back(i);result.push_back(umap[nums[i]]);break;}}return result;} };

31. 下一個排列

likou
整數數組的一個 排列 就是將其所有成員以序列或線性順序排列。

例如,arr = [1,2,3] ,以下這些都可以視作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整數數組的 下一個排列 是指其整數的下一個字典序更大的排列。更正式地,如果數組的所有排列根據其字典順序從小到大排列在一個容器中,那么數組的 下一個排列 就是在這個有序容器中排在它后面的那個排列。如果不存在下一個更大的排列,那么這個數組必須重排為字典序最小的排列(即,其元素按升序排列)。

例如,arr = [1,2,3] 的下一個排列是 [1,3,2] 。
類似地,arr = [2,3,1] 的下一個排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一個排列是 [1,2,3] ,因為 [3,2,1] 不存在一個字典序更大的排列。
給你一個整數數組 nums ,找出 nums 的下一個排列。

必須 原地 修改,只允許使用額外常數空間。

示例 1:

輸入:nums = [1,2,3]
輸出:[1,3,2]
示例 2:

輸入:nums = [3,2,1]
輸出:[1,2,3]
示例 3:

輸入:nums = [1,1,5]
輸出:[1,5,1]

提示:

1 <= nums.length <= 100
0 <= nums[i] <= 100

解法1:兩遍掃描+雙指針

思路:

代碼:

class Solution { public:void nextPermutation(vector<int>& nums) {int i = nums.size() - 2;while(i >= 0 && nums[i+1] <= nums[i]) i--;int firstIndex = i;if(firstIndex >= 0){int j = nums.size() - 1;while(nums[j] <= nums[firstIndex]) j--;int secondIndex = j;swap(nums[firstIndex],nums[secondIndex]);}reverse(nums.begin()+firstIndex+1,nums.end());} };

165. 比較版本號

力扣鏈接
給你兩個版本號 version1 和 version2 ,請你比較它們。

版本號由一個或多個修訂號組成,各修訂號由一個 ‘.’ 連接。每個修訂號由 多位數字 組成,可能包含 前導零 。每個版本號至少包含一個字符。修訂號從左到右編號,下標從 0 開始,最左邊的修訂號下標為 0 ,下一個修訂號下標為 1 ,以此類推。例如,2.5.33 和 0.1 都是有效的版本號。

比較版本號時,請按從左到右的順序依次比較它們的修訂號。比較修訂號時,只需比較 忽略任何前導零后的整數值 。也就是說,修訂號 1 和修訂號 001 相等 。如果版本號沒有指定某個下標處的修訂號,則該修訂號視為 0 。例如,版本 1.0 小于版本 1.1 ,因為它們下標為 0 的修訂號相同,而下標為 1 的修訂號分別為 0 和 1 ,0 < 1 。

返回規則如下:

如果 version1 > version2 返回 1,
如果 version1 < version2 返回 -1,
除此之外返回 0。

示例 1:

輸入:version1 = “1.01”, version2 = “1.001”
輸出:0
解釋:忽略前導零,“01” 和 “001” 都表示相同的整數 “1”
示例 2:

輸入:version1 = “1.0”, version2 = “1.0.0”
輸出:0
解釋:version1 沒有指定下標為 2 的修訂號,即視為 “0”
示例 3:

輸入:version1 = “0.1”, version2 = “1.1”
輸出:-1
解釋:version1 中下標為 0 的修訂號是 “0”,version2 中下標為 0 的修訂號是 “1” 。0 < 1,所以 version1 < version2

提示:

1 <= version1.length, version2.length <= 500
version1 和 version2 僅包含數字和 ‘.’
version1 和 version2 都是 有效版本號
version1 和 version2 的所有修訂號都可以存儲在 32 位整數 中

解法1:雙指針

class Solution { public:int compareVersion(string version1, string version2) {int i = 0, j = 0;int m = version1.size(), n = version2.size();while(i < m || j < n){long a = 0, b = 0;while(i < m && version1[i] != '.') a = a*10 + version1[i++] - '0';while(j < n && version2[j] != '.') b = b*10 + version2[j++] - '0';if(a > b) return 1;else if(a < b) return -1;i++;j++;}return 0;} };

75. 顏色分類

力扣鏈接
給定一個包含紅色、白色和藍色、共 n 個元素的數組 nums ,原地對它們進行排序,使得相同顏色的元素相鄰,并按照紅色、白色、藍色順序排列。

我們使用整數 0、 1 和 2 分別表示紅色、白色和藍色。

必須在不使用庫的sort函數的情況下解決這個問題。

示例 1:

輸入:nums = [2,0,2,1,1,0]
輸出:[0,0,1,1,2,2]
示例 2:

輸入:nums = [2,0,1]
輸出:[0,1,2]

提示:

n == nums.length
1 <= n <= 300
nums[i] 為 0、1 或 2

進階:

你可以不使用代碼庫中的排序函數來解決這道題嗎?
你能想出一個僅使用常數空間的一趟掃描算法嗎?

解法1:雙指針+一次遍歷

思路:
0,1,2 排序。一次遍歷,如果是0,則移動到表頭,如果是2,則移動到表尾,不用考慮1。0和2處理完,1還會有錯嗎?

代碼:

class Solution { public:void sortColors(vector<int>& nums) {//雙指針 一次遍歷int p0 = 0, p2 = nums.size() - 1;for(int i = 0;i<=p2;i++){while(i <= p2 && nums[i] == 2){swap(nums[i],nums[p2]);p2--;}if(nums[i] == 0){swap(nums[i],nums[p0]);p0++;}}} };

解法2:雙指針+一次遍歷

class Solution { public:void sortColors(vector<int>& nums) {//雙指針 一次遍歷int p0 = 0, p1 = 0;for(int i = 0;i<nums.size();i++){if(nums[i] == 1){swap(nums[i],nums[p1]);p1++;}if(nums[i] == 0){swap(nums[i],nums[p0]);if(p0 < p1){//這個時候的nums[i]有可能是1swap(nums[i],nums[p1]);}p0++;p1++;}}} };

縮減搜索空間的思想

11. 盛最多水的容器

力扣鏈接

給你 n 個非負整數 a1,a2,…,an,每個數代表坐標中的一個點 (i, ai) 。在坐標內畫 n 條垂直線,垂直線 i 的兩個端點分別為 (i, ai) 和 (i, 0) 。找出其中的兩條線,使得它們與 x 軸共同構成的容器可以容納最多的水。

說明:你不能傾斜容器。

示例 1:

輸入:[1,8,6,2,5,4,8,3,7]
輸出:49
解釋:圖中垂直線代表輸入數組 [1,8,6,2,5,4,8,3,7]。在此情況下,容器能夠容納水(表示為藍色部分)的最大值為 49。

示例 2:
輸入:height = [1,1]
輸出:1

示例 3:
輸入:height = [4,3,2,1,4]
輸出:16

示例 4:
輸入:height = [1,2,1]
輸出:2

提示:
n == height.length
2 <= n <= 105
0 <= height[i] <= 104

解法1:雙指針

思路:



代碼:

class Solution { public:int maxArea(vector<int>& height) {//雙指針int left = 0, right = height.size()-1;int result = (right-left)*min(height[left],height[right]);while(left < right){if (height[left] < height[right]) left++;else right--;result = max((right-left)*min(height[left],height[right]),result);}return result;} };

復雜度分析:
時間復雜度 O(N)? : 雙指針遍歷一次底邊寬度 N?? 。
空間復雜度 O(1) : 變量 i , j , res 使用常數額外空間。

240. 搜索二維矩陣 II

力扣鏈接
編寫一個高效的算法來搜索 m x n 矩陣 matrix 中的一個目標值 target 。該矩陣具有以下特性:

每行的元素從左到右升序排列。 每列的元素從上到下升序排列。

示例 1:

輸入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
輸出:true

示例 2:

輸入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
輸出:false

提示:

m == matrix.length
n == matrix[i].length
1 <= n, m <= 300
-109 <= matrix[i][j] <= 109
每行的所有元素從左到右升序排列
每列的所有元素從上到下升序排列
-109 <= target <= 109

解法1:雙指針+二叉搜索樹

思路:

該做法則與 (題解)74. 搜索二維矩陣 的「解法二」完全一致。

我們可以將二維矩陣抽象成「以右上角為根的 BST」:

那么我們可以從根(右上角)開始搜索,如果當前的節點不等于目標值,可以按照樹的搜索順序進行:

當前節點「大于」目標值,搜索當前節點的「左子樹」,也就是當前矩陣位置的「左方格子」,即 c–
當前節點「小于」目標值,搜索當前節點的「右子樹」,也就是當前矩陣位置的「下方格子」,即 r++

代碼:

class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int m = matrix.size(), n = matrix[0].size();int row = 0, col = n -1;while(row<m && col >=0){if(matrix[row][col] < target) row++;else if(matrix[row][col] > target) col--;else return true;}return false;} };

復雜度分析:

時間復雜度:O(m + n)
空間復雜度:O(1)

167. 兩數之和 II - 輸入有序數組

力扣鏈接
給定一個已按照 非遞減順序排列 的整數數組 numbers ,請你從數組中找出兩個數滿足相加之和等于目標數 target 。

函數應該以長度為 2 的整數數組的形式返回這兩個數的下標值。numbers 的下標 從 1 開始計數 ,所以答案數組應當滿足 1 <= answer[0] < answer[1] <= numbers.length 。

你可以假設每個輸入 只對應唯一的答案 ,而且你 不可以 重復使用相同的元素。

示例 1:

輸入:numbers = [2,7,11,15], target = 9
輸出:[1,2]
解釋:2 與 7 之和等于目標數 9 。因此 index1 = 1, index2 = 2 。

示例 2:

輸入:numbers = [2,3,4], target = 6
輸出:[1,3]

示例 3:

輸入:numbers = [-1,0], target = -1
輸出:[1,2]

提示:

2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers 按 非遞減順序 排列
-1000 <= target <= 100

解法1:雙指針

思路:


代碼:

class Solution { public:vector<int> twoSum(vector<int>& numbers, int target) {vector<int> result;int right = numbers.size() -1;for(int i = 0;i<numbers.size();i++){if(numbers[i] > target) return result;while(numbers[right] + numbers[i] > target) right--;if (numbers[right] + numbers[i] == target){result.push_back(i+1);result.push_back(right+1);return result;}}return result;} };

|||||||||||||||| 滑動窗口 ||||||||||||||||||

992. K 個不同整數的子數組

力扣鏈接
給定一個正整數數組 A,如果 A 的某個子數組中不同整數的個數恰好為 K,則稱 A 的這個連續、不一定不同的子數組為好子數組。

(例如,[1,2,3,1,2] 中有 3 個不同的整數:1,2,以及 3。)

返回 A 中好子數組的數目。

示例 1:

輸入:A = [1,2,1,2,3], K = 2
輸出:7
解釋:恰好由 2 個不同整數組成的子數組:[1,2], [2,1], [1,2], [2,3], [1,2,1], [2,1,2], [1,2,1,2].

示例 2:

輸入:A = [1,2,1,3,4], K = 3
輸出:3
解釋:恰好由 3 個不同整數組成的子數組:[1,2,1,3], [2,1,3], [1,3,4].

提示:

1 <= A.length <= 20000
1 <= A[i] <= A.length
1 <= K <= A.length

解法1:雙指針(滑動窗口)

思路:
(1)


(2)實現函數 atMostWithKDistinct(A, K) ,表示**「最多存在 KK 個不同整數的子區間的個數」**。于是 atMostWithKDistinct(A, K) - atMostWithKDistinct(A, K - 1) 即為所求。

代碼:

class Solution { public:int mostDistanct(vector<int>& nums, int k){unordered_map<int,int> umap;int left = 0, right = 0, result = 0;while(right < nums.size()){// [left, right) umap[nums[right]]++;right++;while(umap.size() > k){umap[nums[left]]--;if(umap[nums[left]] == 0) umap.erase(nums[left]);left++;}result += right - left;}return result;}int subarraysWithKDistinct(vector<int>& nums, int k) {return mostDistanct(nums, k) - mostDistanct(nums, k - 1);} };

為什么可以用新子數組的長度即 【right - left】來表示增加的子數組個數呢?

可以借鑒動態規劃的思想,舉個例子就好理解了:

當滿足條件的子數組從 [A,B,C] 增加到 [A,B,C,D] 時,新子數組的長度為 4,同時增加的子數組為 [D], [C,D], [B,C,D], [A,B,C,D] 也為4。

復雜度分析:

時間復雜度:O(n),其中 n是數組長度。我們至多只需要遍歷該數組2次(右指針和左指針各一次)。

空間復雜度:O(n),其中 n是數組長度。我們需要記錄每一個數的出現次數,本題中數的大小不超過數組長度。

相似題目:904. 水果成籃

力扣鏈接
你正在探訪一家農場,農場從左到右種植了一排果樹。這些樹用一個整數數組 fruits 表示,其中 fruits[i] 是第 i 棵樹上的水果 種類 。

你想要盡可能多地收集水果。然而,農場的主人設定了一些嚴格的規矩,你必須按照要求采摘水果:

你只有 兩個 籃子,并且每個籃子只能裝 單一類型 的水果。每個籃子能夠裝的水果總量沒有限制。
你可以選擇任意一棵樹開始采摘,你必須從 每棵 樹(包括開始采摘的樹)上 恰好摘一個水果 。采摘的水果應當符合籃子中的水果類型。每采摘一次,你將會向右移動到下一棵樹,并繼續采摘。
一旦你走到某棵樹前,但水果不符合籃子的水果類型,那么就必須停止采摘。
給你一個整數數組 fruits ,返回你可以收集的水果的 最大 數目。

示例 1:

輸入:fruits = [1,2,1]
輸出:3
解釋:可以采摘全部 3 棵樹。

示例 2:

輸入:fruits = [0,1,2,2]
輸出:3
解釋:可以采摘 [1,2,2] 這三棵樹。
如果從第一棵樹開始采摘,則只能采摘 [0,1] 這兩棵樹。

示例 3:

輸入:fruits = [1,2,3,2,2]
輸出:4
解釋:可以采摘 [2,3,2,2] 這四棵樹。
如果從第一棵樹開始采摘,則只能采摘 [1,2] 這兩棵樹。

示例 4:

輸入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
輸出:5
解釋:可以采摘 [1,2,1,1,2] 這五棵樹。

提示:

1 <= fruits.length <= 105
0 <= fruits[i] < fruits.length

解法1:雙指針(滑動窗口)

思路:
代碼:

class Solution { public:int totalFruit(vector<int>& fruits) {unordered_map<int,int> umap;int left = 0, right = 0, result = 0;while(right < fruits.size()){umap[fruits[right]]++;right++;while(umap.size() > 2){umap[fruits[left]]--;if(umap[fruits[left]] == 0) umap.erase(fruits[left]);left++;}result = max(result,right-left);}return result;} };

復雜度分析:

時間復雜度:O(N),其中 N 是 tree 的長度。
空間復雜度:O(N)。

相似題目:76. 最小覆蓋子串

力扣鏈接
給你一個字符串 s 、一個字符串 t 。返回 s 中涵蓋 t 所有字符的最小子串。如果 s 中不存在涵蓋 t 所有字符的子串,則返回空字符串 “” 。

注意:

對于 t 中重復字符,我們尋找的子字符串中該字符數量必須不少于 t 中該字符數量。
如果 s 中存在這樣的子串,我們保證它是唯一的答案。

示例 1:

輸入:s = “ADOBECODEBANC”, t = “ABC”
輸出:“BANC”

示例 2:

輸入:s = “a”, t = “a”
輸出:“a”

示例 3:

輸入: s = “a”, t = “aa”
輸出: “”
解釋: t 中兩個字符 ‘a’ 均應包含在 s 的子串中,
因此沒有符合條件的子字符串,返回空字符串。

提示:

1 <= s.length, t.length <= 105
s 和 t 由英文字母組成

進階:你能設計一個在 o(n) 時間內解決此問題的算法嗎?

解法1:滑動窗口

思路:

(滑動窗口) O(n)

這道題要求我們返回字符串 s中包含字符串 t 的全部字符的最小窗口,我們利用滑動窗口的思想解決這個問題。因此我們需要兩個哈希表,hs哈希表維護的是s字符串中滑動窗口中各個字符出現多少次,ht哈希表維護的是t字符串各個字符出現多少次。如果hs哈希表中包含ht哈希表中的所有字符,并且對應的個數都不小于ht哈希表中各個字符的個數,那么說明當前的窗口是可行的,可行中的長度最短的滑動窗口就是答案。

過程如下:

1、遍歷t字符串,用ht哈希表記錄t字符串各個字符出現的次數。

2、定義兩個指針j和i,j指針用于收縮窗口,i指針用于延伸窗口,則區間[j,i]表示當前滑動窗口。首先讓i和j指針都指向字符串s開頭,然后枚舉整個字符串s ,枚舉過程中,不斷增加i使滑動窗口增大,相當于向右擴展滑動窗口。


3、每次向右擴展滑動窗口一步,將s[i]加入滑動窗口中,而新加入了s[i],相當于滑動窗口維護的字符數加一,即hs[s[i]]++。

4、對于新加入的字符s[i],如果hs[s[i]] <= ht[s[i]],說明當前新加入的字符s[i]是必需的,且還未到達字符串t所要求的數量。我們還需要事先定義一個cnt變量, cnt維護的是s字符串[j,i]區間中滿足t字符串的元素的個數,記錄相對應字符的總數。新加入的字符s[i]必需,則cnt++。

5、我們向右擴展滑動窗口的同時也不能忘記收縮滑動窗口。因此當hs[s[j]] > ht[s[j]時,說明hs哈希表中s[j]的數量多于ht哈希表中s[j]的數量,此時我們就需要向右收縮滑動窗口,j++并使hs[s[j]]–,即hs[s[j ++ ]] --。

6、當cnt == t.size時,說明此時滑動窗口包含符串 t 的全部字符。我們重復上述過程找到最小窗口即為答案。


時間復雜度分析: 兩個指針都嚴格遞增,最多移動 n 次,所以總時間復雜度是 O(n)。

代碼:

class Solution { public:string minWindow(string s, string t) {unordered_map<char,int> sMap,tMap;for(auto& c:t) tMap[c]++;string result;int cnt = 0;int left = 0, right = 0;while(right < s.size()){//[left,right)sMap[s[right]]++;if(sMap[s[right]] <= tMap[s[right]]) cnt++;//必須加入的元素right++;while(sMap[s[left]] > tMap[s[left]]){sMap[s[left]]--;left++;}if(cnt == t.size()){if(result.empty() || right-left<result.size()){//result為空或遇到了更短的長度result = s.substr(left,right-left);}}}return result;} };

3. 無重復字符的最長子串

力扣鏈接
給定一個字符串 s ,請你找出其中不含有重復字符的 最長子串 的長度。

示例 1:

輸入: s = “abcabcbb”
輸出: 3
解釋: 因為無重復字符的最長子串是 “abc”,所以其長度為 3。

示例 2:

輸入: s = “bbbbb”
輸出: 1
解釋: 因為無重復字符的最長子串是 “b”,所以其長度為 1。

示例 3:

輸入: s = “pwwkew”
輸出: 3
解釋: 因為無重復字符的最長子串是 “wke”,所以其長度為 3。
請注意,你的答案必須是 子串 的長度,“pwke” 是一個子序列,不是子串。

示例 4:

輸入: s = “”
輸出: 0

提示:

0 <= s.length <= 5 * 104
s 由英文字母、數字、符號和空格組成

解法1:滑動窗口+哈希

思路:
這道題主要用到思路是:滑動窗口

什么是滑動窗口?

其實就是一個隊列,比如例題中的 abcabcbb,進入這個隊列(窗口)為 abc 滿足題目要求,當再進入 a,隊列變成了 abca,這時候不滿足要求。所以,我們要移動這個隊列!

如何移動?

我們只要把隊列的左邊的元素移出就行了,直到滿足題目要求!

一直維持這樣的隊列,找出隊列出現最長的長度時候,求出解!

時間復雜度:O(n)

代碼1:

class Solution { public:int lengthOfLongestSubstring(string s) {unordered_map<char,int> umap;int left = 0, right = 0, result = 0;while(right < s.size()){if(umap[s[right]] == 0){//不是重復字符,右指針右移umap[s[right]]++;right++;}else{//是重復字符,左指針右移umap[s[left]]--;left++;}result = max(result,right-left);}return result;} };

代碼2:

class Solution { public:int lengthOfLongestSubstring(string s) {if (s.size() == 0) return 0;unordered_set<char> uset;int left = 0;int count = 0;for(int right = 0;right<s.size();right++){while(uset.find(s[right])!=uset.end()) {uset.erase(s[left]);left++;} count = max(count,right-left+1); uset.insert(s[right]);}return count;} };

424. 替換后的最長重復字符

力扣鏈接
給你一個僅由大寫英文字母組成的字符串,你可以將任意位置上的字符替換成另外的字符,總共可最多替換 k 次。在執行上述操作后,找到包含重復字母的最長子串的長度。

注意:字符串長度 和 k 不會超過 104。

示例 1:

輸入:s = “ABAB”, k = 2
輸出:4
解釋:用兩個’A’替換為兩個’B’,反之亦然。

示例 2:

輸入:s = “AABABBA”, k = 1
輸出:4
解釋:
將中間的一個’A’替換為’B’,字符串變為 “AABBBBA”。
子串 “BBBB” 有最長重復字母, 答案為 4。

解法1:滑動窗口

思路:

我們可以枚舉字符串中的每一個位置作為右端點然后找到其最遠的左端點的位置,滿足該區間內除了出現次數最多的那一類字符之外,剩余的字符(即非最長重復字符)數量不超過 k個

代碼:

class Solution { public:int characterReplacement(string s, int k) {unordered_map<char,int> umap;int left = 0, right = 0, result = 0, maxCnt = 0;while(right < s.size()){umap[s[right]]++;maxCnt = max(maxCnt,umap[s[right]]);right++;while(right - left > maxCnt + k){umap[s[left]]--;left++;}result = max(result,right - left);}return result;} };

相似題目:485. 最大連續 1 的個數

力扣鏈接
給定一個二進制數組, 計算其中最大連續 1 的個數。

示例:

輸入:[1,1,0,1,1,1]
輸出:3
解釋:開頭的兩位和最后的三位都是連續 1 ,所以最大連續 1 的個數是 3.

提示:

輸入的數組只包含 0 和 1 。
輸入數組的長度是正整數,且不超過 10,000。

解法1:數組+一次遍歷

思路:
為了得到數組中最大連續 1 的個數,需要遍歷數組,并記錄最大的連續 11的個數和當前的連續 1 的個數如果當前元素是 1,則將當前的連續 1 的個數加 1否則,使用之前的連續 1 的個數更新最大的連續 1 的個數,并將當前的連續 1 的個數清零。

遍歷數組結束之后,需要再次使用當前的連續 1的個數更新最大的連續 1 的個數,因為數組的最后一個元素可能是 1,且最長連續 1 的子數組可能出現在數組的末尾,如果遍歷數組結束之后不更新最大的連續 1 的個數,則會導致結果錯誤。

代碼:

class Solution { public:int findMaxConsecutiveOnes(vector<int>& nums) {int count = 0, result = 0;for(int i = 0;i<nums.size();i++){if(nums[i] == 1) count++;else{result = max(result,count);count = 0;}}result = max(result,count);return result;} };

劍指 Offer 59 - I. 滑動窗口的最大值

力扣鏈接
給定一個數組 nums 和滑動窗口的大小 k,請找出所有滑動窗口里的最大值。

示例:

輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
輸出: [3,3,5,5,6,7]
解釋:

滑動窗口的位置 最大值


[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

提示:

你可以假設 k 總是有效的,在輸入數組不為空的情況下,1 ≤ k ≤ 輸入數組的大小。

注意:本題與主站 239 題相同:https://leetcode-cn.com/problems/sliding-window-maximum/

解法1:滑動窗口+單調隊列+雙向隊列

思路:

這是使用單調隊列的經典題目。

(1)我們需要一個隊列,這個隊列呢,放進去窗口里的元素,然后隨著窗口的移動,隊列也一進一出,每次移動之后,隊列告訴我們里面的最大值是什么。

這個隊列應該長這個樣子:

class MyQueue { public:void pop(int value) {}void push(int value) {}int front() {return que.front();} };

每次窗口移動的時候,調用que.pop(滑動窗口中移除元素的數值),que.push(滑動窗口添加元素的數值),然后que.front()就返回我們要的最大值。

這么個隊列香不香,要是有現成的這種數據結構是不是更香了!可惜了,沒有! 我們需要自己實現這么個隊列。

然后在分析一下,隊列里的元素一定是要排序的,而且要**最大值放在出隊口,**要不然怎么知道最大值呢。

但如果把窗口里的元素都放進隊列里,窗口移動的時候,隊列需要彈出元素。那么問題來了,已經排序之后的隊列 怎么能把窗口要移除的元素(這個元素可不一定是最大值)彈出呢。

其實隊列沒有必要維護窗口里的所有元素,只需要維護有可能成為窗口里最大值的元素就可以了,同時保證隊里里的元素數值是由大到小的。

那么這個維護元素單調遞減的隊列就叫做單調隊列,即單調遞減或單調遞增的隊列。C++中沒有直接支持單調隊列,需要我們自己來一個單調隊列

不要以為實現的單調隊列就是 對窗口里面的數進行排序,如果排序的話,那和優先級隊列又有什么區別了呢。

(2)對于窗口里的元素{2, 3, 5, 1 ,4},單調隊列里只維護{5, 4} 就夠了,保持單調隊列里單調遞減,此時隊列出口元素就是窗口里最大元素。

此時大家應該懷疑單調隊列里維護著{5, 4} 怎么配合窗口經行滑動呢?

設計單調隊列的時候,pop,和push操作要保持如下規則:

1.pop(value):如果窗口移除的元素value等于單調隊列的出口元素,那么隊列彈出元素,否則不用任何操作
2.push(value):如果push的元素value大于入口元素的數值,那么就將隊列入口的元素彈出,直到push元素的數值小于等于隊列入口元素的數值為止
保持如上規則,每次窗口移動的時候,只要問que.front()就可以返回當前窗口的最大值。

那么我們用什么數據結構來實現這個單調隊列呢?使用deque最為合適,常用的queue在沒有指定容器的情況下,deque就是默認底層容器。

class MyQueue { //單調隊列(從大到小) public:deque<int> que; // 使用deque來實現單調隊列// 每次彈出的時候,比較當前要彈出的數值是否等于隊列出口元素的數值,如果相等則彈出。// 同時pop之前判斷隊列當前是否為空。void pop(int value) {if (!que.empty() && value == que.front()) {que.pop_front();}}// 如果push的數值大于入口元素的數值,那么就將隊列后端的數值彈出,直到push的數值小于等于隊列入口元素的數值為止。// 這樣就保持了隊列里的數值是單調從大到小的了。void push(int value) {while (!que.empty() && value > que.back()) {que.pop_back();}que.push_back(value);}// 查詢當前隊列里的最大值 直接返回隊列前端也就是front就可以了。int front() {return que.front();} };

代碼:

class Solution { public:vector<int> maxSlidingWindow(vector<int>& nums, int k) {//單調隊列deque<int> dq;for(int i = 0;i<k;i++){while(!dq.empty() && nums[i] >= nums[dq.back()]){dq.pop_back();}dq.push_back(i);}vector<int> res = {nums[dq.front()]};for(int i = k;i<nums.size();i++){while(!dq.empty() && nums[i] >= nums[dq.back()]){dq.pop_back();}dq.push_back(i);while(dq.front() <= i-k) dq.pop_front();res.push_back(nums[dq.front()]);}return res;} };

復雜度分析:

時間復雜度,使用單調隊列的時間復雜度是 O(n)O(n)O(n)

有的同學可能想了,在隊列中 push元素的過程中,還有pop操作呢,感覺不是純粹的O(n)O(n)O(n)

其實,大家可以自己觀察一下單調隊列的實現,nums 中的每個元素最多也就被 push_back 和 pop_back 各一次,沒有任何多余操作,所以整體的復雜度還是 O(n)O(n)O(n)

空間復雜度因為我們定義一個輔助隊列,所以是O(k)O(k)O(k)

解法2:優先隊列+滑動窗口

思路:

代碼:

class Solution { public:vector<int> maxSlidingWindow(vector<int>& nums, int k) {if(nums.size() == 0) return {};//大根堆priority_queue<pair<int,int>> pq;//num[i]--位置for(int i = 0;i<k;i++){pq.push({nums[i],i});}vector<int> result;result.push_back(pq.top().first);for(int i = k;i<nums.size();i++){pq.push({nums[i],i});while(pq.top().second <= i-k){pq.pop();}result.push_back(pq.top().first);}return result;} };

相似題目:劍指 Offer 59 - II. 隊列的最大值

力扣鏈接
請定義一個隊列并實現函數 max_value 得到隊列里的最大值,要求函數max_value、push_back 和 pop_front 的均攤時間復雜度都是O(1)。

若隊列為空,pop_front 和 max_value 需要返回 -1

示例 1:

輸入:
[“MaxQueue”,“push_back”,“push_back”,“max_value”,“pop_front”,“max_value”]
[[],[1],[2],[],[],[]]
輸出: [null,null,null,2,1,2]
示例 2:

輸入:
[“MaxQueue”,“pop_front”,“max_value”]
[[],[],[]]
輸出: [null,-1,-1]

限制:

1 <= push_back,pop_front,max_value的總操作數 <= 10000
1 <= value <= 10^5

解法1:單調隊列+滑動窗口

思路:

代碼:

class MaxQueue { public:queue<int> q;deque<int> dq;MaxQueue() {}int max_value() {if(dq.empty()) return -1;return dq.front();}void push_back(int value) {while(!dq.empty() && value > dq.back()){dq.pop_back();}dq.push_back(value);q.push(value);}int pop_front() {if(q.empty()) return -1;int result = q.front();if(result == dq.front()){dq.pop_front();}q.pop();return result;} };/*** Your MaxQueue object will be instantiated and called as such:* MaxQueue* obj = new MaxQueue();* int param_1 = obj->max_value();* obj->push_back(value);* int param_3 = obj->pop_front();*/

1052. 愛生氣的書店老板

力扣鏈接
有一個書店老板,他的書店開了 n 分鐘。每分鐘都有一些顧客進入這家商店。給定一個長度為 n 的整數數組 customers ,其中 customers[i] 是在第 i 分鐘開始時進入商店的顧客的編號,所有這些顧客在第 i 分鐘結束后離開。

在某些時候,書店老板會生氣。 如果書店老板在第 i 分鐘生氣,那么 grumpy[i] = 1,否則 grumpy[i] = 0。

當書店老板生氣時,那一分鐘的顧客就會不滿意,若老板不生氣則顧客是滿意的。

書店老板知道一個秘密技巧,能抑制自己的情緒,可以讓自己連續 minutes 分鐘不生氣,但卻只能使用一次。

請你返回 這一天營業下來,最多有多少客戶能夠感到滿意 。

示例 1:

輸入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], minutes = 3
輸出:16
解釋:書店老板在最后 3 分鐘保持冷靜。
感到滿意的最大客戶數量 = 1 + 1 + 1 + 1 + 7 + 5 = 16.
示例 2:

輸入:customers = [1], grumpy = [0], minutes = 1
輸出:1

提示:

n == customers.length == grumpy.length
1 <= minutes <= n <= 2 * 104
0 <= customers[i] <= 1000
grumpy[i] == 0 or 1

解法1:數組+滑動窗口

思路:

代碼:

class Solution { public:int maxSatisfied(vector<int>& customers, vector<int>& grumpy, int minutes) {int n = customers.size();int sum = 0, maxN = 0;//不生氣時的總數 生氣區間內使用技巧的最大值for(int i = 0;i<n;i++){if(grumpy[i] == 0){sum += customers[i];customers[i] = 0;}}int num = 0;for(int i = 0, j = 0;i<n;i++){num += customers[i];if(i-j+1>minutes){num -= customers[j];j++;}maxN = max(maxN,num);}return sum + maxN;} };

209. 長度最小的子數組

力扣鏈接
給定一個含有 n 個正整數的數組和一個正整數 target 。

找出該數組中滿足其和 ≥ target 的長度最小的 連續子數組 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其長度。如果不存在符合條件的子數組,返回 0 。

示例 1:

輸入:target = 7, nums = [2,3,1,2,4,3]
輸出:2
解釋:子數組 [4,3] 是該條件下的長度最小的子數組。
示例 2:

輸入:target = 4, nums = [1,4,4]
輸出:1
示例 3:

輸入:target = 11, nums = [1,1,1,1,1,1,1,1]
輸出:0

提示:

1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105

進階:

如果你已經實現 O(n) 時間復雜度的解法, 請嘗試設計一個 O(n log(n)) 時間復雜度的解法。

解法1:滑動窗口

class Solution { public:int minSubArrayLen(int target, vector<int>& nums) {//滑動窗口int result = INT_MAX;int sum = 0;for(int i = 0, j = 0;j<nums.size();j++){sum += nums[j];while(sum >= target){result = min(j-i+1,result);sum -= nums[i];i++;}}return result == INT_MAX ? 0:result;} };

相似題目:718. 最長重復子數組

力扣鏈接
給兩個整數數組 nums1 和 nums2 ,返回 兩個數組中 公共的 、長度最長的子數組的長度 。

示例 1:

輸入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
輸出:3
解釋:長度最長的公共子數組是 [3,2,1] 。
示例 2:

輸入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
輸出:5

提示:

1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 100

解法1:動態規劃

確定dp數組(dp table)以及下標的含義
dp[i][j] :以下標i - 1為結尾的A,和以下標j - 1為結尾的B,最長重復子數組長度為dp[i][j]。 (特別注意: “以下標i - 1為結尾的A” 標明一定是 以A[i-1]為結尾的字符串 )

class Solution { public:int findLength(vector<int>& nums1, vector<int>& nums2) {int n1 = nums1.size(), n2 = nums2.size();vector<vector<int>> dp(n1+1,vector<int>(n2+1,0));int res = 0;for(int i = 1;i<=n1;i++){for(int j = 1;j<=n2;j++){if(nums1[i-1]== nums2[j-1]){dp[i][j] = dp[i-1][j-1] + 1;}res = max(res, dp[i][j]);}}return res;} };

567. 字符串的排列

力扣鏈接
給你兩個字符串 s1 和 s2 ,寫一個函數來判斷 s2 是否包含 s1 的排列。如果是,返回 true ;否則,返回 false 。

換句話說,s1 的排列之一是 s2 的 子串 。

示例 1:

輸入:s1 = “ab” s2 = “eidbaooo”
輸出:true
解釋:s2 包含 s1 的排列之一 (“ba”).
示例 2:

輸入:s1= “ab” s2 = “eidboaoo”
輸出:false

提示:

1 <= s1.length, s2.length <= 104
s1 和 s2 僅包含小寫字母

解法1:滑動窗口

class Solution { public:bool checkInclusion(string s1, string s2) {//滑動窗口unordered_map<char,int> s1Map, s2Map;for(char c:s1) s1Map[c]++;int slow = 0, fast = 0;for(;fast<s2.size();fast++){s2Map[s2[fast]]++;while(s2Map[s2[fast]] > s1Map[s2[fast]]){s2Map[s2[slow]]--;slow++;}if(fast - slow + 1 == s1.size()) return true;}return false;} };

總結

以上是生活随笔為你收集整理的[力扣刷题总结](双指针篇)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。