leetcode209.長度最小子數組(Java)

題目

給定一個含有 n 個正整數的數組和一個正整數 s ,找出該數組中滿足其和 ≥ s 的長度最小的連續子數組,並返回其長度。如果不存在符合條件的連續子數組,返回 0。

示例:

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

思路分析

暴力法

  最先想到的自然是暴力法,這也是正常的思路。我認爲許多算法問題都是對暴力搜索的優化,這幾次參加leetcode周賽感受很深刻。每次做題都是暴力搜索之後超時,優化的步驟每次都有問題。對於該題暴力法的思路如下,兩次循環遍歷所有可能的連續子數組,然後計算出每個連續子數組的和,該方法的時間複雜度位O(n3)。顯然可以事先計算每段(0, i)的和 dp[i],然後再計算連續子數據的和時,其時間複雜度優化爲O(1),總的時間複雜度變爲O(n2)這是很容易就能想到的優化方法。代碼如下:

class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int len = nums.length;
        if (len == 0) return 0;
        int[] dp = new int[len];
        dp[0] = nums[0];
        for (int i = 1; i < len; i++) {
            dp[i] = dp[i-1] + nums[i];
        }
        if (dp[len - 1] < s) return 0;
        int res = len;
        for (int i = 0; i < len; i++) {
            for (int j = i; j < len; j++) {
                if (dp[j] - dp[i] + nums[i] >= s) {
                    if (j - i + 1 < res) {
                        res = j - i + 1;
                    }
                }
            }
        }
        return res;
    }
}

利用二分搜索進行優化

在循環的內層,是一個遍歷操作,時間複雜度爲O(n)。目標是找出一個 j 使得:
dp[j]dp[i]+nums[i]>=sdp[j] - dp[i] +nums[i] >= s
同樣可以轉換爲找到一個 j 使得:
dp[j]>=s+dp[i]nums[i]dp[j] >= s + dp[i] - nums[i]
即找到一個大於等於某值 target 的 dp[j],顯然這可以利用二分搜索進行查找。
這是一個典型的二分搜索程序:

 private int binarySearch(int[] nums, int target) {
     int left = 0;
     int right = nums.length - 1;
     while (left <= right) {
         int mid = left + (right - left) / 2;
         if (nums[mid] == target) {
             return mid;
         } else if (nums[mid] < target) {
             left = mid + 1;
         } else
             right = mid - 1;
     }
     return -1;
 }

要對二分搜索有一定的理解:

  1. 該二分搜索結束時,left 和 right 有兩種情況。找到了target,此時 left=right ;未找到target,此時left < right。
  2. 未找到target時,nums[left] 爲第一個大於target的值,nums[right] 爲第一個小於target的值。
  3. 當target不在nums的(min, max) 範圍內時,沒有大於或者小於target的nums[i],此時left = nums.length 或者 right = -1。同樣,有一種更加直觀的理解,right 在target 值所在位置的左邊,left 在target值所在位置的右邊。

基於以上理解,left就是大於等於target值的索引。此時,left 等於 nums.length 時,說明該索引不合法;left < nums.length 時,該索引合法,能找到一個大於等於某值target的 dp[j]。其餘步驟較爲簡單,代碼如下:

class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int len = nums.length;
        if (len == 0) return 0;
        int[] dp = new int[len];
        dp[0] = nums[0];
        for (int i = 1; i < len; i++) {
            dp[i] = dp[i-1] + nums[i];
        }
        if (dp[len - 1] < s) return 0; // 如果不存在
        int res = len;
        for (int i = 0; i < len; i++) {
            // 從 i 到 j : dp[j] - dp[i] + nums[i] >= s
            int target = s + dp[i] - nums[i];
            // 找到大於等於 target的那個j
            int j = binarySearch(dp, target);
            if (j < len) {
                if (j - i + 1 < res)
                    res = j - i + 1;
            }
        }
        return res;
    }
    
    private int binarySearch(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else
                right = mid - 1;
        }
        return left;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章