数组-滑动窗口-不定长度窗口

674: 最长连续递增序列

题目描述:

给定一个未经排序的整数数组 nums,找到最长且连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个或多个整数组成,且这些数组内的每个整数都比前一个整数大。

示例 1:

输入: nums = [1,3,5,4,7]
输出: 3
解释: 最长连续递增序列是 [1,3,5], 长度为 3。
尽管 [1,3,5,7] 也是递增子序列,但它不是连续的。

示例 2:

输入: nums = [2,2,2,2,2]
输出: 1
解释: 最长连续递增序列是 [2], 长度为 1

提示:

  • 1 <= nums.length <= 10^4
  • -10^9 <= nums[i] <= 10^9

解题思路:

这道题的目的是找到数组中最长的连续递增子序列。可以通过一次遍历数组来解决。使用一个变量记录当前连续递增子序列的长度,并用另一个变量记录找到的最大长度。

解题步骤:

  1. 初始化变量:使用两个变量,一个 maxLen 用于记录最长的连续递增序列的长度,另一个 currentLen 用于记录当前的递增序列长度。
  2. 遍历数组:从第二个元素开始,比较当前元素与前一个元素:
    • 如果当前元素大于前一个元素,则递增当前序列长度 currentLen
    • 如果当前元素小于或等于前一个元素,则重置 currentLen 为 1。
  3. 更新最大长度:在每次递增或重置时,更新 maxLen
  4. 返回结果:遍历结束后,返回 maxLen 作为结果。

题解代码:

#include <vector>
using namespace std;

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        if (nums.empty()) return 0;
        
        int maxLen = 1;  // 存储最长连续递增子序列的长度
        int currentLen = 1;  // 当前的连续递增子序列长度
        
        // 遍历数组,从第二个元素开始
        for (int i = 1; i < nums.size(); ++i) {
            if (nums[i] > nums[i - 1]) {
                // 如果当前元素大于前一个元素,递增当前序列长度
                currentLen++;
            } else {
                // 如果当前元素小于或等于前一个元素,重置当前序列长度为 1
                currentLen = 1;
            }
            // 更新最大长度
            maxLen = max(maxLen, currentLen);
        }
        
        return maxLen;
    }
};

复杂度分析:

时间复杂度

  • O(n)n 是数组 nums 的长度。我们只需要遍历一次数组,因此时间复杂度为 O(n)。

空间复杂度

  • O(1):只使用了常数的额外空间来存储 maxLencurrentLen,因此空间复杂度为 O(1)。

485: 最大连续 1 的个数

题目描述:

给定一个二进制数组 nums ,计算其中最大连续 1 的个数。

示例 1:

输入: nums = [1,1,0,1,1,1]
输出: 3
解释: 最大连续 1 的个数是 3

示例 2:

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

提示:

  • 输入的数组只包含 01
  • 1 <= nums.length <= 10^5

解题思路:

这道题的目的是找出数组中最长的连续 1 的序列。我们可以通过一次遍历数组来解决这个问题,维护两个变量,一个用来记录当前的连续 1 的长度,另一个用来记录最长的连续 1 的长度。

具体步骤如下:

  1. 初始化两个变量

    • currentCount:用于记录当前连续 1 的个数。
    • maxCount:用于记录找到的最大连续 1 的个数。
  2. 遍历数组

    • 如果当前元素是 1,将 currentCount 加 1。
    • 如果当前元素是 0,则将 currentCount 置为 0,因为连续的 1 被打断。
  3. 更新最大连续 1 的个数

    • 每次 currentCount 变化时,更新 maxCount,确保它存储了最长的连续 1
  4. 返回结果:遍历结束后,返回 maxCount


题解代码:

#include <vector>
using namespace std;

class Solution {
public:
    int findMaxConsecutiveOnes(vector<int>& nums) {
        int maxCount = 0;  // 存储最大连续 1 的个数
        int currentCount = 0;  // 当前的连续 1 的个数
        
        // 遍历数组
        for (int num : nums) {
            if (num == 1) {
                // 如果当前元素是 1,增加当前连续 1 的计数
                currentCount++;
            } else {
                // 如果当前元素是 0,重置当前计数
                currentCount = 0;
            }
            // 更新最大连续 1 的个数
            maxCount = max(maxCount, currentCount);
        }
        
        return maxCount;
    }
};

复杂度分析:

时间复杂度

  • O(n)n 是数组 nums 的长度。我们只需要遍历一次数组,因此时间复杂度为 O(n)。

空间复杂度

  • O(1):只使用了常数空间存储 maxCountcurrentCount,因此空间复杂度为 O(1)。

487: 最大连续 1 的个数 II

题目描述:

给定一个二进制数组 nums,如果最多可以将其中的一个 0 变为 1,返回通过此方式获得的最大连续 1 的个数。

示例 1:

输入: nums = [1,0,1,1,0]
输出: 4
解释: 将第一个 0 变为 1 后,最大连续 1 的个数是 4

示例 2:

输入: nums = [1,0,1,1,0,1]
输出: 4

提示:

  • 输入的数组只包含 01
  • 1 <= nums.length <= 10^5
  • nums[i] 不是 1 就是 0

解题思路:

这道题要求我们找到最多将一个 0 变为 1 后,最长的连续 1 的个数。可以通过滑动窗口技术来解决此问题。

滑动窗口思路

  1. 定义滑动窗口:滑动窗口包含数组中的一段元素,最多可以包含一个 0
  2. 窗口滑动
    • 使用两个指针 leftright 来表示窗口的左右边界。
    • 当窗口内的 0 超过一个时,收缩窗口的左边界。
  3. 统计最大长度:每次更新窗口时,计算窗口的长度,并记录最大长度。

题解代码:

#include <vector>
using namespace std;

class Solution {
public:
    int findMaxConsecutiveOnes(vector<int>& nums) {
        int left = 0;  // 左指针
        int right = 0;  // 右指针
        int zeroCount = 0;  // 窗口中 0 的个数
        int maxLen = 0;  // 最大连续 1 的长度
        
        // 遍历数组,使用滑动窗口
        while (right < nums.size()) {
            // 如果遇到 0,增加 0 的计数
            if (nums[right] == 0) {
                zeroCount++;
            }
            
            // 如果窗口中的 0 超过 1 个,移动左指针收缩窗口
            while (zeroCount > 1) {
                if (nums[left] == 0) {
                    zeroCount--;
                }
                left++;  // 收缩窗口
            }
            
            // 更新最大连续 1 的长度
            maxLen = max(maxLen, right - left + 1);
            
            // 移动右指针扩大窗口
            right++;
        }
        
        return maxLen;
    }
};

复杂度分析:

时间复杂度

  • O(n)n 是数组 nums 的长度。我们使用左右两个指针遍历整个数组,每个元素最多处理两次,因此时间复杂度为 O(n)。

空间复杂度

  • O(1):只使用了固定数量的额外变量(left, right, zeroCount, maxLen),因此空间复杂度为 O(1)。

76: 最小覆盖子串

题目描述:

给你一个字符串 s、一个字符串 t 。请你设计一个函数来找出 s 中的最小子串,该子串包含 t 中的所有字符(包括重复的字符),并返回这一子串。如果不存在符合条件的子串,则返回空字符串 ""

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串是 "BANC"

示例 2:

输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。

提示:

  • 1 <= s.length, t.length <= 10^5
  • st 由英文字母组成

解题思路:

这是一个经典的滑动窗口问题。滑动窗口的核心思想是在字符串 s 中找到一个最小的子串,它包含字符串 t 中的所有字符。

滑动窗口步骤:

  1. 字符频率统计:先统计字符串 t 中每个字符的频率,用哈希表 t_freq 存储。

  2. 初始化滑动窗口:使用两个指针 leftright 来表示滑动窗口。右指针移动扩展窗口,左指针移动缩小窗口。

  3. 维护窗口内的字符频率:在滑动窗口内维护当前窗口中包含的字符频率,当窗口内的字符频率满足 t 的要求时,尝试缩小窗口以获得最小子串。

  4. 找到最小子串:在每次窗口满足条件时,检查是否为当前最小的子串。


题解代码:

#include <string>
#include <unordered_map>
using namespace std;

class Solution {
public:
    string minWindow(string s, string t) {
        if (s.size() < t.size()) return "";  // 如果 s 长度小于 t,返回空字符串
        
        unordered_map<char, int> t_freq;  // 记录 t 中每个字符的频率
        unordered_map<char, int> window_freq;  // 记录当前窗口中字符的频率
        
        // 初始化 t 中字符的频率
        for (char c : t) {
            t_freq[c]++;
        }
        
        int left = 0, right = 0;  // 左右指针表示滑动窗口
        int required = t_freq.size();  // 需要满足的字符种类数
        int formed = 0;  // 当前窗口中已经满足的字符种类数
        int min_len = INT_MAX;  // 最小子串的长度
        int min_left = 0;  // 最小子串的起始位置
        
        // 开始滑动窗口
        while (right < s.size()) {
            char c = s[right];  // 当前右指针字符
            window_freq[c]++;
            
            // 如果当前字符在 t 中,并且窗口中的该字符频率满足了 t 中的要求
            if (t_freq.count(c) && window_freq[c] == t_freq[c]) {
                formed++;
            }
            
            // 当窗口已经满足所有字符要求时,尝试收缩窗口
            while (left <= right && formed == required) {
                // 更新最小子串的长度和起始位置
                if (right - left + 1 < min_len) {
                    min_len = right - left + 1;
                    min_left = left;
                }
                
                // 移除左指针的字符并缩小窗口
                char l_char = s[left];
                window_freq[l_char]--;
                
                // 如果移除的字符在 t 中并且频率不再满足要求
                if (t_freq.count(l_char) && window_freq[l_char] < t_freq[l_char]) {
                    formed--;
                }
                
                left++;  // 移动左指针,缩小窗口
            }
            
            right++;  // 扩展窗口,移动右指针
        }
        
        // 如果找到了符合要求的子串,返回最小子串,否则返回空字符串
        return min_len == INT_MAX ? "" : s.substr(min_left, min_len);
    }
};

复杂度分析:

时间复杂度

  • O(n + m)n 是字符串 s 的长度,m 是字符串 t 的长度。我们遍历 st 各一次,因此时间复杂度为 O(n + m)。

空间复杂度

  • O(n + m):我们使用了两个哈希表 window_freqt_freq,它们最多各存储 st 中所有字符的频率,因此空间复杂度为 O(n + m)。

718: 最长重复子数组

题目描述:

给两个整数数组 nums1nums2,返回两个数组中公共的、长度最长的子数组的长度。

示例 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

解题思路:

这个问题可以通过动态规划来解决。定义一个二维动态规划表 dp[i][j] 表示在 nums1 的前 i 个元素和 nums2 的前 j 个元素中,最长的公共子数组的长度。

具体步骤如下:

  1. 定义状态

    • dp[i][j] 表示以 nums1[i-1]nums2[j-1] 结尾的最长公共子数组的长度。
  2. 状态转移方程

    • 如果 nums1[i-1] == nums2[j-1],则 dp[i][j] = dp[i-1][j-1] + 1
    • 否则,dp[i][j] = 0,表示没有公共子数组。
  3. 初始条件

    • dp[0][j] = 0dp[i][0] = 0,因为任何空数组与另一个数组没有公共子数组。
  4. 结果

    • 最长公共子数组的长度就是 dp 数组中的最大值。

题解代码:

#include <vector>
using namespace std;

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        int n1 = nums1.size(), n2 = nums2.size();
        // 定义 dp 数组,dp[i][j] 表示 nums1 前 i 个元素与 nums2 前 j 个元素的最长公共子数组的长度
        vector<vector<int>> dp(n1 + 1, vector<int>(n2 + 1, 0));
        int maxLength = 0;  // 记录最长的公共子数组长度
        
        // 遍历数组 nums1 和 nums2,更新 dp 数组
        for (int i = 1; i <= n1; ++i) {
            for (int j = 1; j <= n2; ++j) {
                // 如果 nums1[i-1] == nums2[j-1],更新 dp[i][j]
                if (nums1[i-1] == nums2[j-1]) {
                    dp[i][j] = dp[i-1][j-1] + 1;
                    // 更新最长长度
                    maxLength = max(maxLength, dp[i][j]);
                }
            }
        }
        
        return maxLength;  // 返回最长的公共子数组长度
    }
};

复杂度分析:

时间复杂度

  • O(n1 * n2):其中 n1n2 分别是 nums1nums2 的长度。我们需要填满一个 n1 * n2 的二维数组,因此时间复杂度为 O(n1 * n2)。

空间复杂度

  • O(n1 * n2):我们使用了一个大小为 n1 * n2 的二维动态规划表,因此空间复杂度为 O(n1 * n2)。

209: 长度最小的子数组

题目描述:

给定一个含有 n 个正整数的数组和一个正整数 target,找出该数组中满足其和 ≥ target 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,则返回 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 <= 10^9
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

解题思路:

这道题可以使用滑动窗口技术来解决,滑动窗口的核心思想是动态调整窗口的大小,使得窗口内的数字和满足条件,并尽可能缩小窗口。

  1. 定义滑动窗口:使用两个指针 leftright 表示滑动窗口的左右边界。

  2. 滑动窗口的扩展与收缩

    • 当窗口内的数字和小于 target 时,移动右边界扩展窗口。
    • 当窗口内的数字和大于等于 target 时,尝试收缩左边界以获得更短的子数组。
  3. 记录最小子数组长度:在每次窗口满足条件时,记录当前窗口的长度,并尝试更新最小长度。


题解代码:

#include <vector>
#include <algorithm>
using namespace std;

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int left = 0;  // 左指针
        int currentSum = 0;  // 当前窗口的总和
        int minLength = INT_MAX;  // 初始化最小长度为一个很大的值
        
        // 右指针遍历整个数组
        for (int right = 0; right < nums.size(); ++right) {
            currentSum += nums[right];  // 扩展窗口,增加当前元素到总和
            
            // 当窗口内的和大于等于 target 时,尝试收缩窗口
            while (currentSum >= target) {
                minLength = min(minLength, right - left + 1);  // 更新最小长度
                currentSum -= nums[left];  // 移出左边的元素,收缩窗口
                left++;  // 移动左指针
            }
        }
        
        // 如果没有找到满足条件的子数组,返回 0,否则返回最小长度
        return minLength == INT_MAX ? 0 : minLength;
    }
};

复杂度分析:

时间复杂度

  • O(n):我们只遍历了数组一次,每个元素至多被访问两次(一次加入窗口,一次移出窗口),因此时间复杂度为 O(n)。

空间复杂度

  • O(1):只使用了常数空间来存储变量 leftcurrentSumminLength,因此空间复杂度为 O(1)。

1004: 最大连续 1 的个数 III

题目描述:

给定一个二进制数组 nums 和一个整数 k,如果可以将最多 k0 翻转为 1,请返回数组中包含 1 的最长连续子数组的长度。

示例 1:

输入:nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2
输出:6
解释:翻转 20 后,最长的连续 1 的子数组为 [1,1,1,0,0,1,1,1,1,1],长度为 6

示例 2:

输入:nums = [0,0,1,1,1,0,0], k = 0
输出:3

提示:

  • 1 <= nums.length <= 10^5
  • nums[i] 不是 1 就是 0
  • 0 <= k <= nums.length

解题思路:

这道题的目标是通过翻转最多 k0,找到包含最多连续 1 的子数组。可以使用滑动窗口技术来解决问题。

  1. 滑动窗口的核心

    • 使用两个指针 leftright 表示窗口的左右边界。
    • 在滑动窗口内,保持最多包含 k0
    • 当窗口内的 0 的个数超过 k 时,收缩窗口,直到窗口内的 0 的个数不超过 k
  2. 统计最大长度

    • 每次窗口符合条件(即窗口内 0 的个数不超过 k)时,更新最大连续子数组长度。

题解代码:

#include <vector>
using namespace std;

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int left = 0;  // 左指针
        int right = 0;  // 右指针
        int zeroCount = 0;  // 窗口内 0 的个数
        int maxLen = 0;  // 最大连续 1 的长度
        
        // 滑动窗口遍历整个数组
        while (right < nums.size()) {
            // 如果遇到 0,增加 0 的计数
            if (nums[right] == 0) {
                zeroCount++;
            }
            
            // 如果窗口内的 0 超过了 k 个,移动左指针以收缩窗口
            while (zeroCount > k) {
                if (nums[left] == 0) {
                    zeroCount--;
                }
                left++;  // 收缩窗口
            }
            
            // 更新最大连续 1 的长度
            maxLen = max(maxLen, right - left + 1);
            
            // 右指针继续扩展窗口
            right++;
        }
        
        return maxLen;
    }
};

复杂度分析:

时间复杂度

  • O(n)n 是数组 nums 的长度。每个元素最多被处理两次(一次进入窗口,一次移出窗口),因此时间复杂度为 O(n)。

空间复杂度

  • O(1):只使用了固定的额外空间用于存储指针和计数器,因此空间复杂度为 O(1)。

1658: 将 x 减到 0 的最小操作数

题目描述:

给你一个整数数组 nums 和一个整数 x。每次操作时,你应当从数组的开头或末尾移除一个元素,减少 x 的值。请注意,需要 恰好 减少 x 的值到 0,如果不能得到 0,返回 -1

返回将 x 减到 0最小操作数,如果无法做到,则返回 -1

示例 1:

输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最优解决方案是移除最后两个元素(3 和 2),操作数为 2

示例 2:

输入:nums = [5,6,7,8,9], x = 4
输出:-1

示例 3:

输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最优解决方案是移除最后 3 个元素和最前面 2 个元素(共 5 个操作)。

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^4
  • 1 <= x <= 10^9

解题思路:

可以通过滑动窗口技术来解决这个问题。

由于每次只能从数组两端移除元素,问题等价于在数组中找到一个最长的子数组,该子数组的和是 sum(nums) - x。也就是说,我们希望在数组中找到一个子数组,使得移除该子数组后,剩余部分的和等于 x

步骤:

  1. 目标和:计算数组的总和 sum(nums),目标是找到一个和为 sum(nums) - x 的最长子数组。
  2. 滑动窗口:使用滑动窗口来找到数组中和为 sum(nums) - x 的最长子数组。
  3. 计算结果:如果找到了这个子数组,则最小操作数就是数组总长度减去这个子数组的长度;如果没有找到,返回 -1

题解代码:

#include <vector>
#include <numeric>
using namespace std;

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int totalSum = accumulate(nums.begin(), nums.end(), 0);
        int target = totalSum - x;
        
        // 如果 target 小于 0,说明没有可能达成
        if (target < 0) return -1;
        if (target == 0) return nums.size(); // 如果 target 等于 0,则需要移除整个数组
        
        int currentSum = 0;  // 当前窗口的和
        int maxLen = -1;  // 记录最长的子数组长度
        int left = 0;  // 滑动窗口左边界
        
        // 滑动窗口遍历数组
        for (int right = 0; right < nums.size(); ++right) {
            currentSum += nums[right];  // 扩展窗口,加入当前元素到窗口和
            
            // 当窗口的和大于 target 时,收缩窗口
            while (currentSum > target) {
                currentSum -= nums[left];
                left++;
            }
            
            // 如果找到了和为 target 的子数组,更新最长子数组长度
            if (currentSum == target) {
                maxLen = max(maxLen, right - left + 1);
            }
        }
        
        // 如果没有找到这样的子数组,返回 -1
        return maxLen == -1 ? -1 : nums.size() - maxLen;
    }
};

复杂度分析:

时间复杂度

  • O(n):我们使用滑动窗口遍历数组,每个元素最多被访问两次,因此时间复杂度为 O(n),其中 n 是数组的长度。

空间复杂度

  • O(1):只使用了常数级别的额外空间(如 currentSum, maxLen, left 等变量),因此空间复杂度为 O(1)。

3: 无重复字符的最长子串

题目描述:

给定一个字符串 s,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 **子串的长度**,"pwke" 是一个子序列,不是子串。

提示:

  • 0 <= s.length <= 5 * 10^4
  • s 由英文字母、数字、符号和空格组成

解题思路:

这道题可以通过滑动窗口技术来解决。滑动窗口用于维护当前不含重复字符的子串,并通过扩展窗口来找到更长的不重复子串。

滑动窗口思路

  1. 使用两个指针 leftright 来表示窗口的左右边界,窗口用于记录当前不含重复字符的子串。
  2. 维护一个哈希表或数组来记录窗口中字符的出现频率或位置。
  3. 当窗口内出现重复字符时,移动左指针缩小窗口,直到窗口不再有重复字符。
  4. 在滑动过程中,更新最长不重复子串的长度。

题解代码:

#include <string>
#include <unordered_map>
using namespace std;

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> charIndexMap;  // 记录字符最后出现的位置
        int maxLength = 0;  // 记录最长不重复子串的长度
        int left = 0;  // 滑动窗口左边界
        
        // 右指针遍历字符串
        for (int right = 0; right < s.size(); ++right) {
            char currentChar = s[right];
            
            // 如果当前字符之前已经在窗口中出现过,移动左边界
            if (charIndexMap.find(currentChar) != charIndexMap.end()) {
                // 确保 left 不会往回移动
                left = max(left, charIndexMap[currentChar] + 1);
            }
            
            // 更新当前字符的最后出现位置
            charIndexMap[currentChar] = right;
            
            // 更新最长子串的长度
            maxLength = max(maxLength, right - left + 1);
        }
        
        return maxLength;  // 返回最长不重复子串的长度
    }
};

复杂度分析:

时间复杂度

  • O(n)n 是字符串的长度。每个字符在滑动窗口中最多被访问两次(一次是扩展窗口,一次是移动左边界),因此时间复杂度为 O(n)。

空间复杂度

  • O(1):最多维护一个大小为 128 的哈希表(针对 ASCII 字符集),因此空间复杂度为 O(1)。

1695: 删除子数组的最大得分

题目描述:

给你一个正整数数组 nums,请你从中删除一个含有 若干不同元素 的子数组。删除子数组的得分是该子数组各元素之

返回 只删除一个子数组 可获得的最大得分。

如果数组 b 是数组 a 的一个子数组,那么 ba 中一段连续的元素(即 b = a[i], a[i+1], ..., a[j])组成。

示例 1:

输入:nums = [4,2,4,5,6]
输出:17
解释:最优子数组是 [2,4,5,6],删除该子数组可获得的得分 17

示例 2:

输入:nums = [5,2,1,2,5,2,1,2,5]
输出:8
解释:最优子数组是 [5,2,1],删除该子数组可获得的得分 8

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^4

解题思路:

这道题可以通过滑动窗口技术来解决。我们需要维护一个没有重复元素的滑动窗口,并且在窗口中找到和最大的子数组。

滑动窗口步骤:

  1. 定义滑动窗口:使用两个指针 leftright 来表示滑动窗口的左右边界。窗口内的元素应当是唯一的。

  2. 维护一个哈希表:通过哈希表或集合来记录当前窗口内的元素。当发现一个重复元素时,移动左指针缩小窗口,直到窗口中不再有重复元素。

  3. 计算最大得分:每次窗口移动时,计算当前窗口内的元素和,并更新最大得分。


题解代码:

#include <vector>
#include <unordered_set>
using namespace std;

class Solution {
public:
    int maximumUniqueSubarray(vector<int>& nums) {
        unordered_set<int> seen;  // 用于记录当前窗口内的元素
        int left = 0, right = 0;
        int currentSum = 0;  // 当前窗口内元素的和
        int maxSum = 0;  // 最大得分
        
        // 使用滑动窗口遍历数组
        while (right < nums.size()) {
            // 如果窗口内存在重复元素,移动左指针缩小窗口
            while (seen.count(nums[right])) {
                currentSum -= nums[left];  // 移除左边的元素
                seen.erase(nums[left]);  // 从集合中移除
                left++;  // 移动左指针
            }
            
            // 将新元素加入窗口
            seen.insert(nums[right]);
            currentSum += nums[right];
            right++;  // 移动右指针
            
            // 更新最大得分
            maxSum = max(maxSum, currentSum);
        }
        
        return maxSum;
    }
};

复杂度分析:

时间复杂度

  • O(n)n 是数组的长度。每个元素最多被处理两次(一次是加入窗口,一次是移出窗口),因此时间复杂度为 O(n)。

空间复杂度

  • O(n):用于存储滑动窗口内的元素,最坏情况下滑动窗口内包含所有元素,因此空间复杂度为 O(n)。

1208: 尽可能使字符串相等

题目描述:

给你两个长度相同的字符串,st。每一个步骤中,你可以选择将字符串 s 中的一个字符改为字符串 t 中的对应字符。你的目标是尽可能使 s 相等于 t,但是你 最多 可以有 maxCost 的转换成本。

转换成本是两个字符串中对应字符的 ASCII 码值的差的绝对值,即对于每对索引 i,转换成本为 |s[i] - t[i]|(绝对值)。

请你返回使 s 相等于 t最大长度的 子字符串。如果没有可以转换的子字符串,则返回 0

示例 1:

输入:s = "abcd", t = "bcdf", maxCost = 3
输出:3
解释:s 中的 "abc" 可以变为 "bcd"。开销为 3,所以最大长度为 3

示例 2:

输入:s = "abcd", t = "cdef", maxCost = 3
输出:1
解释:s 中的 "a" 可以变为 "c"。开销为 2,所以最大长度为 1

示例 3:

输入:s = "abcd", t = "acde", maxCost = 0
输出:1
解释:你无法作出任何改动,所以最大长度为 1

提示:

  • 1 <= s.length == t.length <= 10^5
  • 0 <= maxCost <= 10^6
  • st 都只含小写英文字母

解题思路:

这道题的目标是在给定最大成本 maxCost 的情况下,找到字符串 st 在对应位置字符转换时,转换成本不超过 maxCost 的最长子字符串。

可以使用滑动窗口技术来解决此问题。

滑动窗口步骤:

  1. 定义滑动窗口:使用两个指针 leftright 来表示窗口的左右边界。

  2. 计算转换成本:对于每对字符 s[i]t[i],计算它们的转换成本 |s[i] - t[i]|,并维护当前窗口内的转换成本总和 currentCost

  3. 调整窗口:当 currentCost 超过 maxCost 时,移动左指针收缩窗口,直到总成本不超过 maxCost

  4. 更新最大长度:在每次窗口满足条件时,更新最长子字符串的长度。


题解代码:

#include <string>
#include <cmath>
using namespace std;

class Solution {
public:
    int equalSubstring(string s, string t, int maxCost) {
        int left = 0, right = 0;  // 滑动窗口的左右边界
        int currentCost = 0;  // 当前窗口内的总转换成本
        int maxLength = 0;  // 最长满足条件的子字符串长度
        
        // 遍历字符串 s 和 t
        while (right < s.size()) {
            // 计算当前字符的转换成本并加到 currentCost
            currentCost += abs(s[right] - t[right]);
            
            // 如果当前窗口的总成本超过 maxCost,则移动左边界
            while (currentCost > maxCost) {
                currentCost -= abs(s[left] - t[left]);
                left++;  // 移动左指针
            }
            
            // 更新最长子字符串长度
            maxLength = max(maxLength, right - left + 1);
            
            right++;  // 移动右指针扩展窗口
        }
        
        return maxLength;
    }
};

复杂度分析:

时间复杂度

  • O(n)n 是字符串 st 的长度。每个字符最多被处理两次(一次是加入窗口,一次是移出窗口),因此时间复杂度为 O(n)。

空间复杂度

  • O(1):除了几个用于记录指针和转换成本的变量外,算法只使用了常数级别的额外空间,因此空间复杂度为 O(1)。

1493: 删掉一个元素以后全为 1 的最长子数组

题目描述:

给你一个二进制数组 nums,你可以删掉一个元素。请你返回删掉一个元素后,数组中最长的全为 1 的子数组的长度。

示例 1:

输入:nums = [1,1,0,1]
输出:3
解释:删掉位置 20 后,[1,1,1] 是最长的全 1 子数组。

示例 2:

输入:nums = [0,1,1,1,0,1,1,0,1]
输出:5
解释:删掉位置 40 后,[1,1,1,1,1] 是最长的全 1 子数组。

示例 3:

输入:nums = [1,1,1]
输出:2
解释:你必须删除一个元素。

提示:

  • 1 <= nums.length <= 10^5
  • nums[i] 要么是 0,要么是 1

解题思路:

可以使用滑动窗口技术来解决这道题,维护一个最多包含一个 0 的窗口,找到删掉一个 0 后,全为 1 的最长子数组。

  1. 滑动窗口思路

    • 使用两个指针 leftright 表示滑动窗口的左右边界。
    • 窗口内最多只能包含一个 0,每次移动右指针扩展窗口,如果窗口内的 0 超过 1 个,则移动左指针缩小窗口,直到窗口内的 0 不超过 1 个。
  2. 计算最长子数组长度

    • 每次窗口满足条件时,记录当前窗口的长度。
    • 需要注意,最终结果应该是窗口长度减去 1,因为题目要求删除一个元素后计算最长子数组。

题解代码:

#include <vector>
using namespace std;

class Solution {
public:
    int longestSubarray(vector<int>& nums) {
        int left = 0;  // 左指针
        int zeroCount = 0;  // 窗口内 0 的个数
        int maxLen = 0;  // 最长全为 1 的子数组长度
        
        // 滑动窗口遍历数组
        for (int right = 0; right < nums.size(); ++right) {
            // 如果当前元素是 0,增加 zeroCount
            if (nums[right] == 0) {
                zeroCount++;
            }
            
            // 如果窗口内的 0 的数量超过 1,移动左指针收缩窗口
            while (zeroCount > 1) {
                if (nums[left] == 0) {
                    zeroCount--;
                }
                left++;  // 移动左指针
            }
            
            // 更新最大长度
            maxLen = max(maxLen, right - left);
        }
        
        return maxLen;  // 返回最长子数组长度
    }
};

复杂度分析:

时间复杂度

  • O(n)n 是数组 nums 的长度。我们使用滑动窗口遍历数组,每个元素最多被访问两次(一次是进入窗口,一次是移出窗口),因此时间复杂度为 O(n)。

空间复杂度

  • O(1):只使用了固定的额外空间用于存储指针和计数器,因此空间复杂度为 O(1)。

727: 最小窗口子序列

题目描述:

给定字符串 ST,在 S 中找出包含 T 所有字符的最短子序列。

如果 S 中没有这样的子序列,返回空字符串。如果有这样的子序列,返回最短的子序列。

示例 1:

输入:S = "abcdebdde", T = "bde"
输出:"bcde"
解释:"bcde" 是答案,因为它在 S 中出现,且包含 T 的所有字符。
最短子序列是 "bcde"

示例 2:

输入:S = "jmeqksfrsdcmsiwvaovztaqenprpvnbstl", T = "k"
输出:"ksfrsdcmsiwv"

提示:

  • 1 <= S.length, T.length <= 1000
  • ST 只含有小写英文字母。

解题思路:

这道题的目标是在字符串 S 中找到包含 T 所有字符的最短子序列。我们可以通过双指针结合滑动窗口的方式来解决。

双指针滑动窗口步骤:

  1. 找到第一个匹配的子序列

    • 使用两个指针 ij,分别指向字符串 STi 用来遍历 Sj 用来遍历 T
    • 首先移动 i,找到第一个包含 T 的子序列。当 j 到达 T 的末尾时,表示找到了一个匹配的子序列。
  2. 找到最短的匹配子序列

    • 一旦找到匹配的子序列,逆向移动 i,尝试缩小窗口,找到最短的匹配子序列。
    • 每次更新最短子序列时,记录子序列的起始和结束位置。
  3. 重复上述步骤直到遍历完整个字符串 S


题解代码:

#include <string>
#include <climits>
using namespace std;

class Solution {
public:
    string minWindow(string S, string T) {
        int sLen = S.size(), tLen = T.size();
        int minLen = INT_MAX;
        int startIdx = -1;
        
        // 遍历 S,使用双指针找到包含 T 的最短子序列
        for (int i = 0; i < sLen; ++i) {
            // 只在 S 的字符与 T 的第一个字符匹配时开始寻找
            if (S[i] == T[0]) {
                int sPtr = i, tPtr = 0;
                
                // 向前匹配子序列
                while (sPtr < sLen && tPtr < tLen) {
                    if (S[sPtr] == T[tPtr]) {
                        tPtr++;
                    }
                    sPtr++;
                }
                
                // 如果成功匹配了整个 T,则尝试缩小窗口
                if (tPtr == tLen) {
                    int end = sPtr - 1;  // 当前窗口的右边界
                    sPtr--;  // 回退到找到完整 T 的最后一个字符
                    tPtr = tLen - 1;
                    
                    // 逆向缩小窗口
                    while (tPtr >= 0) {
                        if (S[sPtr] == T[tPtr]) {
                            tPtr--;
                        }
                        sPtr--;
                    }
                    
                    // 更新最小窗口长度和起始位置
                    if (end - (sPtr + 1) + 1 < minLen) {
                        minLen = end - (sPtr + 1) + 1;
                        startIdx = sPtr + 1;
                    }
                }
            }
        }
        
        // 如果找到了最短子序列,返回它,否则返回空字符串
        return startIdx == -1 ? "" : S.substr(startIdx, minLen);
    }
};

复杂度分析:

时间复杂度

  • O(n * m)n 是字符串 S 的长度,m 是字符串 T 的长度。每次找到 T 的完整匹配子序列时,双指针进行一次遍历,因此总体时间复杂度为 O(n * m)。

空间复杂度

  • O(1):除了几个用于存储子序列长度和指针位置的变量外,算法没有使用额外的空间,因此空间复杂度为 O(1)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值