雙指針(使用題目:求子數組(可能是連續的或者是數組中某兩個或某三個之和(積等等)等於某個值)特點分析【切記每道題目的分析都要切合題意】


雙指針(使用題目:求子數組(可能是連續的或者是數組中某兩個或某三個之和(積等等)等於某個值)特點分析

【切記每道題目的分析都要切合題意】

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

 

 

 



 

 



 


 



 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM