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
解题思路:
这道题的目的是找到数组中最长的连续递增子序列。可以通过一次遍历数组来解决。使用一个变量记录当前连续递增子序列的长度,并用另一个变量记录找到的最大长度。
解题步骤:
- 初始化变量:使用两个变量,一个
maxLen用于记录最长的连续递增序列的长度,另一个currentLen用于记录当前的递增序列长度。 - 遍历数组:从第二个元素开始,比较当前元素与前一个元素:
- 如果当前元素大于前一个元素,则递增当前序列长度
currentLen。 - 如果当前元素小于或等于前一个元素,则重置
currentLen为 1。
- 如果当前元素大于前一个元素,则递增当前序列长度
- 更新最大长度:在每次递增或重置时,更新
maxLen。 - 返回结果:遍历结束后,返回
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):只使用了常数的额外空间来存储
maxLen和currentLen,因此空间复杂度为 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
提示:
- 输入的数组只包含
0和1。 1 <= nums.length <= 10^5
解题思路:
这道题的目的是找出数组中最长的连续 1 的序列。我们可以通过一次遍历数组来解决这个问题,维护两个变量,一个用来记录当前的连续 1 的长度,另一个用来记录最长的连续 1 的长度。
具体步骤如下:
-
初始化两个变量:
currentCount:用于记录当前连续1的个数。maxCount:用于记录找到的最大连续1的个数。
-
遍历数组:
- 如果当前元素是
1,将currentCount加 1。 - 如果当前元素是
0,则将currentCount置为 0,因为连续的1被打断。
- 如果当前元素是
-
更新最大连续
1的个数:- 每次
currentCount变化时,更新maxCount,确保它存储了最长的连续1。
- 每次
-
返回结果:遍历结束后,返回
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):只使用了常数空间存储
maxCount和currentCount,因此空间复杂度为 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
提示:
- 输入的数组只包含
0和1。 1 <= nums.length <= 10^5nums[i]不是1就是0。
解题思路:
这道题要求我们找到最多将一个 0 变为 1 后,最长的连续 1 的个数。可以通过滑动窗口技术来解决此问题。
滑动窗口思路:
- 定义滑动窗口:滑动窗口包含数组中的一段元素,最多可以包含一个
0。 - 窗口滑动:
- 使用两个指针
left和right来表示窗口的左右边界。 - 当窗口内的
0超过一个时,收缩窗口的左边界。
- 使用两个指针
- 统计最大长度:每次更新窗口时,计算窗口的长度,并记录最大长度。
题解代码:
#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^5s和t由英文字母组成
解题思路:
这是一个经典的滑动窗口问题。滑动窗口的核心思想是在字符串 s 中找到一个最小的子串,它包含字符串 t 中的所有字符。
滑动窗口步骤:
-
字符频率统计:先统计字符串
t中每个字符的频率,用哈希表t_freq存储。 -
初始化滑动窗口:使用两个指针
left和right来表示滑动窗口。右指针移动扩展窗口,左指针移动缩小窗口。 -
维护窗口内的字符频率:在滑动窗口内维护当前窗口中包含的字符频率,当窗口内的字符频率满足
t的要求时,尝试缩小窗口以获得最小子串。 -
找到最小子串:在每次窗口满足条件时,检查是否为当前最小的子串。
题解代码:
#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的长度。我们遍历s和t各一次,因此时间复杂度为 O(n + m)。
空间复杂度:
- O(n + m):我们使用了两个哈希表
window_freq和t_freq,它们最多各存储s和t中所有字符的频率,因此空间复杂度为 O(n + m)。
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 <= 10000 <= nums1[i], nums2[i] <= 100
解题思路:
这个问题可以通过动态规划来解决。定义一个二维动态规划表 dp[i][j] 表示在 nums1 的前 i 个元素和 nums2 的前 j 个元素中,最长的公共子数组的长度。
具体步骤如下:
-
定义状态:
dp[i][j]表示以nums1[i-1]和nums2[j-1]结尾的最长公共子数组的长度。
-
状态转移方程:
- 如果
nums1[i-1] == nums2[j-1],则dp[i][j] = dp[i-1][j-1] + 1。 - 否则,
dp[i][j] = 0,表示没有公共子数组。
- 如果
-
初始条件:
dp[0][j] = 0和dp[i][0] = 0,因为任何空数组与另一个数组没有公共子数组。
-
结果:
- 最长公共子数组的长度就是
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):其中
n1和n2分别是nums1和nums2的长度。我们需要填满一个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^91 <= nums.length <= 10^51 <= nums[i] <= 10^5
解题思路:
这道题可以使用滑动窗口技术来解决,滑动窗口的核心思想是动态调整窗口的大小,使得窗口内的数字和满足条件,并尽可能缩小窗口。
-
定义滑动窗口:使用两个指针
left和right表示滑动窗口的左右边界。 -
滑动窗口的扩展与收缩:
- 当窗口内的数字和小于
target时,移动右边界扩展窗口。 - 当窗口内的数字和大于等于
target时,尝试收缩左边界以获得更短的子数组。
- 当窗口内的数字和小于
-
记录最小子数组长度:在每次窗口满足条件时,记录当前窗口的长度,并尝试更新最小长度。
题解代码:
#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):只使用了常数空间来存储变量
left、currentSum和minLength,因此空间复杂度为 O(1)。
1004: 最大连续 1 的个数 III
题目描述:
给定一个二进制数组 nums 和一个整数 k,如果可以将最多 k 个 0 翻转为 1,请返回数组中包含 1 的最长连续子数组的长度。
示例 1:
输入:nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2
输出:6
解释:翻转 2 个 0 后,最长的连续 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^5nums[i]不是1就是00 <= k <= nums.length
解题思路:
这道题的目标是通过翻转最多 k 个 0,找到包含最多连续 1 的子数组。可以使用滑动窗口技术来解决问题。
-
滑动窗口的核心:
- 使用两个指针
left和right表示窗口的左右边界。 - 在滑动窗口内,保持最多包含
k个0。 - 当窗口内的
0的个数超过k时,收缩窗口,直到窗口内的0的个数不超过k。
- 使用两个指针
-
统计最大长度:
- 每次窗口符合条件(即窗口内
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^51 <= nums[i] <= 10^41 <= x <= 10^9
解题思路:
可以通过滑动窗口技术来解决这个问题。
由于每次只能从数组两端移除元素,问题等价于在数组中找到一个最长的子数组,该子数组的和是 sum(nums) - x。也就是说,我们希望在数组中找到一个子数组,使得移除该子数组后,剩余部分的和等于 x。
步骤:
- 目标和:计算数组的总和
sum(nums),目标是找到一个和为sum(nums) - x的最长子数组。 - 滑动窗口:使用滑动窗口来找到数组中和为
sum(nums) - x的最长子数组。 - 计算结果:如果找到了这个子数组,则最小操作数就是数组总长度减去这个子数组的长度;如果没有找到,返回
-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^4s由英文字母、数字、符号和空格组成
解题思路:
这道题可以通过滑动窗口技术来解决。滑动窗口用于维护当前不含重复字符的子串,并通过扩展窗口来找到更长的不重复子串。
滑动窗口思路:
- 使用两个指针
left和right来表示窗口的左右边界,窗口用于记录当前不含重复字符的子串。 - 维护一个哈希表或数组来记录窗口中字符的出现频率或位置。
- 当窗口内出现重复字符时,移动左指针缩小窗口,直到窗口不再有重复字符。
- 在滑动过程中,更新最长不重复子串的长度。
题解代码:
#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 的一个子数组,那么 b 由 a 中一段连续的元素(即 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^51 <= nums[i] <= 10^4
解题思路:
这道题可以通过滑动窗口技术来解决。我们需要维护一个没有重复元素的滑动窗口,并且在窗口中找到和最大的子数组。
滑动窗口步骤:
-
定义滑动窗口:使用两个指针
left和right来表示滑动窗口的左右边界。窗口内的元素应当是唯一的。 -
维护一个哈希表:通过哈希表或集合来记录当前窗口内的元素。当发现一个重复元素时,移动左指针缩小窗口,直到窗口中不再有重复元素。
-
计算最大得分:每次窗口移动时,计算当前窗口内的元素和,并更新最大得分。
题解代码:
#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: 尽可能使字符串相等
题目描述:
给你两个长度相同的字符串,s 和 t。每一个步骤中,你可以选择将字符串 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^50 <= maxCost <= 10^6s和t都只含小写英文字母
解题思路:
这道题的目标是在给定最大成本 maxCost 的情况下,找到字符串 s 和 t 在对应位置字符转换时,转换成本不超过 maxCost 的最长子字符串。
可以使用滑动窗口技术来解决此问题。
滑动窗口步骤:
-
定义滑动窗口:使用两个指针
left和right来表示窗口的左右边界。 -
计算转换成本:对于每对字符
s[i]和t[i],计算它们的转换成本|s[i] - t[i]|,并维护当前窗口内的转换成本总和currentCost。 -
调整窗口:当
currentCost超过maxCost时,移动左指针收缩窗口,直到总成本不超过maxCost。 -
更新最大长度:在每次窗口满足条件时,更新最长子字符串的长度。
题解代码:
#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是字符串s和t的长度。每个字符最多被处理两次(一次是加入窗口,一次是移出窗口),因此时间复杂度为 O(n)。
空间复杂度:
- O(1):除了几个用于记录指针和转换成本的变量外,算法只使用了常数级别的额外空间,因此空间复杂度为 O(1)。
1493: 删掉一个元素以后全为 1 的最长子数组
题目描述:
给你一个二进制数组 nums,你可以删掉一个元素。请你返回删掉一个元素后,数组中最长的全为 1 的子数组的长度。
示例 1:
输入:nums = [1,1,0,1]
输出:3
解释:删掉位置 2 的 0 后,[1,1,1] 是最长的全 1 子数组。
示例 2:
输入:nums = [0,1,1,1,0,1,1,0,1]
输出:5
解释:删掉位置 4 的 0 后,[1,1,1,1,1] 是最长的全 1 子数组。
示例 3:
输入:nums = [1,1,1]
输出:2
解释:你必须删除一个元素。
提示:
1 <= nums.length <= 10^5nums[i]要么是0,要么是1
解题思路:
可以使用滑动窗口技术来解决这道题,维护一个最多包含一个 0 的窗口,找到删掉一个 0 后,全为 1 的最长子数组。
-
滑动窗口思路:
- 使用两个指针
left和right表示滑动窗口的左右边界。 - 窗口内最多只能包含一个
0,每次移动右指针扩展窗口,如果窗口内的0超过 1 个,则移动左指针缩小窗口,直到窗口内的0不超过 1 个。
- 使用两个指针
-
计算最长子数组长度:
- 每次窗口满足条件时,记录当前窗口的长度。
- 需要注意,最终结果应该是窗口长度减去 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: 最小窗口子序列
题目描述:
给定字符串 S 和 T,在 S 中找出包含 T 所有字符的最短子序列。
如果 S 中没有这样的子序列,返回空字符串。如果有这样的子序列,返回最短的子序列。
示例 1:
输入:S = "abcdebdde", T = "bde"
输出:"bcde"
解释:"bcde" 是答案,因为它在 S 中出现,且包含 T 的所有字符。
最短子序列是 "bcde"。
示例 2:
输入:S = "jmeqksfrsdcmsiwvaovztaqenprpvnbstl", T = "k"
输出:"ksfrsdcmsiwv"
提示:
1 <= S.length, T.length <= 1000S和T只含有小写英文字母。
解题思路:
这道题的目标是在字符串 S 中找到包含 T 所有字符的最短子序列。我们可以通过双指针结合滑动窗口的方式来解决。
双指针滑动窗口步骤:
-
找到第一个匹配的子序列:
- 使用两个指针
i和j,分别指向字符串S和T。i用来遍历S,j用来遍历T。 - 首先移动
i,找到第一个包含T的子序列。当j到达T的末尾时,表示找到了一个匹配的子序列。
- 使用两个指针
-
找到最短的匹配子序列:
- 一旦找到匹配的子序列,逆向移动
i,尝试缩小窗口,找到最短的匹配子序列。 - 每次更新最短子序列时,记录子序列的起始和结束位置。
- 一旦找到匹配的子序列,逆向移动
-
重复上述步骤直到遍历完整个字符串
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)。
1137

被折叠的 条评论
为什么被折叠?



