雙指針(使用題目:求子數組(可能是連續的或者是數組中某兩個或某三個之和(積等等)等於某個值)特點分析
【切記每道題目的分析都要切合題意】
| 1,盛最多水的容器 2,接雨水 3,和為s的連續正數序列 4,三數之和 5,長度最小的子數組 6,最大子序和 |
1,盛最多水的容器 -------- 2,接雨水 (1、2 思路一致)
3,和為s的連續正數序列 ---------- 4,三數之和(3、4 思路一致)
(1、2、3、4 思路中有共同部分)
5,長度最小的子數組 (滑動窗口,先定右邊,不斷找左邊)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1,盛最多水的容器 -------- 2,接雨水(1、2 思路一致)
(一起分析):
✿盛最多水的容器~題意:
給你 n 個非負整數 a1,a2,...,an,每個數代表坐標中的一個點 (i, ai) 。在坐標內畫 n 條垂直線,垂直線 i 的兩個端點分別為 (i, ai) 和 (i, 0) 。
找出其中的兩條線,使得它們與 x 軸共同構成的容器可以容納最多的水。
說明:你不能傾斜容器。
| 題意分析: 1,題意中已知信息: (1)想求得到的面積最大 ■ 面積 = 寬 * 高 = 下標1和下標2 之差 * 高(對應下標1和下標2的值中比較低的值) ■ 思路:兩個柱子~對應於兩個指針(左右指針),初始化(一般情況):左指針=第一個元素;右指針=最后一個元素(構成了面積的同時、實現了方便的左右移動) ■ while(左 < 右):不斷的更新面積(通過比較左右兩個柱子,得到小的柱子(小柱子決定了面積最終的高)): 而小柱子的值太小不好,拉低了面積,需要移動,找到一個大點的小柱子) |
package 數組; /** * https://leetcode-cn.com/problems/container-with-most-water/ * @author Huangyujun * */ public class _11_盛最多水的容器 { /** * 核心:在比較小的范圍里找到那個最大的值 * 思路:面接的公式~高(取決於左右兩側兩個柱子中比較小的那個柱子) * 但是咱希望高的數值比較大(則需要:在比較小的范圍里找到那個最大的值) * @author Huangyujun * */ //正解:雙指針法 public class Solution { public int maxArea(int[] height) { int l = 0, r = height.length - 1; int ans = 0; while (l < r) { //面積公式 高:最小的 【左柱子,右柱子】 int area = Math.min(height[l], height[r]) * (r - l); ans = Math.max(ans, area); // 需要找小的:(目的:去獲取那個小柱子中的最大值) if (height[l] <= height[r]) { ++l; } else { --r; } } return ans; } } }
✿接雨水~題意:
給定 n 個非負整數表示每個寬度為 1 的柱子的高度圖,計算按此排列的柱子,下雨之后能接多少雨水。

| 題意分析: 1,從題意給的圖示,得知:左右兩側的柱子決定了在起范圍里的柱子可以接收到多少水量 ■ 思路:左右兩個柱子對應於左右兩個指針,初始化:(一般情況)左指針=第一個元素;右指針=最后一個元素;(構成中間可以接水,也方便移動) ■ while(左 < 右): 不斷循環更新柱子的高度(通過比較左右兩個柱子,得到小的柱子(小柱子決定了中間當前的柱子最終接收的水量)): □ 不斷移動小柱子,從而得到那些可能取裝水的小柱子(只是當前的小柱子需要先判斷它是否是作為擋水的“堤壩”,從而更新“堤壩”高度)
|
package 數組; /** * https://leetcode-cn.com/problems/trapping-rain-water/solution/ * @author Huangyujun * 思路: 一個水柱A能接到多少水,取決於左右兩側最低的柱子與當前水柱A 的高度差(單元水量),同時要注意的是左側的柱子的最大值,即可高度差最大值 * 例如首先左邊柱子比較低,找到左邊(在比較低的范圍找到一個最大值 left_max)獲取最大高度差水量 */ public class _42_接雨水 { public int trap(int[] height) { int left = 0, right = height.length - 1; int ans = 0; int left_max = 0, right_max = 0; while (left < right) { if (height[left] < height[right]) { // 需要找小的:(目的:去獲取那個小柱子中的最大值) if (height[left] >= left_max) { left_max = height[left]; } else { ans += (left_max - height[left]); } ++left; } else { if (height[right] >= right_max) { right_max = height[right]; } else { ans += (right_max - height[right]); } --right; } } return ans; } }
3,和為s的連續正數序列 ---------- 4,三數之和(3、4 思路一致)
✿ 和為s的連續正數序列~題意:
輸入一個正整數 target ,輸出所有和為 target 的連續正整數序列(至少含有兩個數)。
序列內的數字由小到大排列,不同序列按照首個數字從小到大排列。
| 題意分析: 1,從題意得知:數組時升序 2,題意要求數組的元素至少有兩個(后邊要作為一個判斷條件) ■ 思路:左右兩個指針(框定了累加的范圍),初始化:(非一般情況)左指針=第一個元素;右指針=第二個元素(因為給的是自然連續的正整數,無法確定最后一個元素在哪里哈哈哈); ■ while(左 < 右): 不斷循環更新累加范圍: □ 當累加的和==目標時:存儲起來,然后左指針往前移動(微調找到下一對合適的左右指針) □ 當累加的和 < 目標時:擴大范圍(右指針往前移動)~為啥是移動右指針呢?考慮初始化值呀,此時的左指針=第一個元素,右指針=第二個元素,不移動右指針擴大范圍,移動誰呀 □ 當累加的和 > 目標時:縮小范圍(左指針往前移動)~ 為啥是左指針移動呢? (因為右指針肯定是在累加的和比目標小時,向前移動,當大於目標時,需要左邊指針做一些微調呀,不然移動右指針,肯定是回退到 小於目標的情況) |
package 數組; /** * 題意:暗示了數據是升序的 * https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/ */ import java.util.ArrayList; import java.util.List; public class _57_和為s的連續正數序列 { public int[][] findContinuousSequence(int target) { List<int[]> vec = new ArrayList<int[]>(); int left = 1, right = 2; while(left < right) { //求和公式 int sum = (left + right) * (right - left + 1) / 2; if (sum == target) { int[] res = new int[right - left + 1]; for (int i = left; i <= right; ++i) { res[i - left] = i; } vec.add(res); left++; //找到之后,左邊指針往前挪動,意味着整個窗口往前挪動 } else if (sum < target) { right++; } else { left++; } } return vec.toArray(new int[vec.size()][]); } }
✿三數之和~題意:
給你一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有和為 0 且不重復的三元組。
注意:答案中不可以包含重復的三元組。

| 題意分析: 1,找三個數之和等於0(目標),若是三層循環太暴力了,時間不允許,需要改為兩層循環,然后通過排序可以優化一下查找的時間 2,題意要求:“不可以包含重復的三元組” (所以,過程中咱需要避免重復,且是三元,則判斷數組長度啦) ■ 思路:兩層循環:第一層循環確定下一個數,target減掉它后的target!,需要在剩下的數找到找到兩個數和為target! (第一層循環:避免重復:if (i > 0 && nums[i] == nums[i - 1])) (邏輯的一個特殊情況:if (nums[i] > 0) break;) ■ 第二層循環:遍歷數組,找到兩個數的和等於target! :兩個數一層循環對應兩個指針(左右指針): □ 初始化:(一般情況)左指針=第一個元素;右指針=最后一個元素; □ 然后不斷累加和與目標target!比較: ● 等於目標時:存儲起來,然后左指針往前移動,右指針往后移動,尋找其他合適的值 (等於目標:避免重復:while (left < right && nums[left] == nums[left - 1]) 和 while (left < right && nums[right] == nums[right + 1])) ● 小於目標時:移動左指針 (往前移動)(因為咱對數組進行了升序處理) ● 大於目標時:移動右指針(往后移動) |
package 數組; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 啟發:可以參考完題解:官網的答案,看不懂,可以結合高贊的答案,或者評論區其他詳細的解說 * https://leetcode-cn.com/problems/3sum/ * * @author Huangyujun * * 其實主要思路:在於避免重復: 然后:通過 第一層循環,暫時確定下來第一個數后,target 更新一下 * 接下來的此情況(只是需要避免重復,然后與57_和為s的連續正數序列 的思考角度一致了): 就是滑動窗口的通用框架2啦 * 細節:有值傳入,特殊值的判斷,如果數組為 null 或者數組長度小於 33,返回 []。 * 處理:對數組進行排序,優化比較查找 * 細節2:第一個值:nums[i] > 0 ,而數組又已經經過了排序,則直接結束了 */ public class _15_三數之和 { public List<List<Integer>> threeSum(int[] nums) {// 總時間復雜度:O(n^2) List<List<Integer>> ans = new ArrayList<>(); if (nums == null || nums.length <= 2) return ans; Arrays.sort(nums); // O(nlogn) for (int i = 0; i < nums.length - 2; i++) { // O(n^2) if (nums[i] > 0) break; // 第一個數大於 0,后面的數都比它大,肯定不成立了 if (i > 0 && nums[i] == nums[i - 1]) continue; // 去掉重復情況 int target = -nums[i]; /** * 注意避免重復的滑動窗口的通用框架2啦 */ int left = i + 1, right = nums.length - 1; while (left < right) { //① 結果 == target if (nums[left] + nums[right] == target) { ans.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right]))); // 現在要增加 left,減小 right,但是不能重復,比如: [-2, -1, -1, -1, 3, 3, 3], i = 0, left = 1, // right = 6, [-2, -1, 3] 的答案加入后,需要排除重復的 -1 和 3 left++; right--; // 接下來的 while (left < right && nums[left] == nums[left - 1]) 和 while (left < right && nums[right] == nums[right + 1]) 都是避免重復 while (left < right && nums[left] == nums[left - 1]) left++; while (left < right && nums[right] == nums[right + 1]) right--; } else if (nums[left] + nums[right] < target) { //② 結果 < target left++; } else { // nums[left] + nums[right] > target //③ 結果 > target right--; } } } return ans; } }
| (1、2、3、4 思路中有共同部分): 根據題意得到使用雙指針(左右指針之間形成一個范圍,然后不斷在范圍更新某個結果,期間根據情況縮小范圍或者擴大范圍吧) 即 while(左<右):不斷更新某個結果。。。。 |
5,長度最小的子數組 (滑動窗口,先定右邊,不斷找左邊)
✿長度最小的子數組~題意:
給定一個含有 n 個正整數的數組和一個正整數 target 。
找出該數組中滿足其和 ≥ target 的長度最小的 連續子數組 [numsl, numsl+1, ..., numsr-1, numsr] ,並返回其長度。如果不存在符合條件的子數組,返回 0 。
| 題意分析: 1,題目給定了具體的值target,但是是要求 >=target,此條件的可以得到的可能情況比較多 2,要求連續的子數組:相當於某片段(~左右指針): ■ 思路:先定右邊(從第一個元素開始找, 找到滿足>=target:),然后在右邊固定的情況下(>=target 的多種情況下),不斷同過移動左邊縮小范圍 直到,不再滿足>=target ,則需要更新右邊,右邊往前繼續找到滿足 >=target,然后又在 >=target 的多種情況下,不斷同過移動左邊縮小范圍。。。 |
package 數組; /** * https://leetcode-cn.com/problems/minimum-size-subarray-sum/ * @author Huangyujun * * 注意細節:當找到滿足條件的窗口時,需要固定右邊界, * 逐漸移動左邊界(縮小窗口大小),直到窗口元素和不滿足要求,再改變右邊界。使用while循環縮小! * */ public class _209_長度最小的子數組 { public int minSubArrayLen(int s, int[] nums) { int n = nums.length; if (nums == null || n == 0) return 0; int ans = Integer.MAX_VALUE; int left = 0, right = 0; int sum = 0; while (right < n) { sum += nums[right++]; while (sum >= s) { ans = Math.min(ans, right - left); sum -= nums[left++]; } } return ans == Integer.MAX_VALUE ? 0 : ans; } }
最后補充一下:什么情況下使用雙指針:
☺ 有左右兩個點,或者左右確定下某范圍,然后給定某些條件,給了移動左指針或者右指針走向的暗示
這里補充一道看着符合使用雙指針的題目,但是缺少了指針移動走向的條件的題目:
6,最大子序和:
題意:
給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。

package 數組; /** * https://leetcode-cn.com/problems/maximum-subarray/ * @author Huangyujun * 思路:在某個變量之前的tmp_sum 如果是大於 0的,則之前這部分tmp_sum 要保留下來 * 題意:找到一個具有最大和的連續子數組 * 解釋一下:這道題目為什么不使用雙指針:假如使用雙指針,初始化:left = 0, right = nums.length - 1; while(left < right): 更新和,這時候應該移動左指針還是右指針呢(題意沒給呀)題意沒給暗示指針走向,導致指針移動不確定呀 * * * 思路:遍歷數組sums: ① 如果當前是nums[i] + 原積累的前 i - 1 個元素的和, 比 nums[i] 還 大,(這里也說明了 原積累的前 i - 1 個元素的和 是大於 0 的 ) 則選擇 積累 否則 從 當前的 nums[i] 開始,重新積累 ② 遍歷過程不斷的更新當前的記錄的最大值max_sum * */ public class _53_最大子序和 { public int maxSubArray(int[] nums) { int max_sum = nums[0]; int sum = 0; int tmp_sum = 0; int len = nums.length; for(int i = 0; i < len; i++) { //當前計算得到的最大tmp_sum tmp_sum = Math.max(tmp_sum + nums[i], nums[i]); //更新記錄中的最大max_sum // if(max_sum < tmp_sum) { // max_sum = tmp_sum; // } max_sum = Math.max(tmp_sum, max_sum); } return max_sum; } }
