Day5–滑动窗口与双指针–3. 无重复字符的最长子串,3090. 每个字符最多出现两次的最长子字符串,1493. 删掉一个元素以后全为 1 的最长子数组
今天要训练的题目类型是:【不定长滑动窗口】,题单来自@灵艾山茶府。
滑动窗口相当于在维护一个队列。右指针的移动可以视作入队,左指针的移动可以视作出队。
不定长滑动窗口主要分为三类:求最长子数组,求最短子数组,求子数组个数。
今天的题目类型是:求最长子数组。
3. 无重复字符的最长子串
思路:
- 利用map记录窗口内元素个数。要一直保持窗口内元素数量都是1
- for循环遍历右指针
- 右指针元素进窗
- 如果右指针元素数量大于1,证明窗口内有重复元素,且该元素就是右指针所处位置的元素
- 左指针元素出窗,直到右指针元素数量变为1,此时窗口内无重复元素
- 更新最大值
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] ch = s.toCharArray();
int n = ch.length;
// ASCII码有128个
int[] map = new int[128];
int len = 0;
int maxLen = 0;
// 窗口左指针
int left = 0;
// 开始滑动窗口,for循环遍历右指针
for (int i = 0; i < n; i++) {
char c = ch[i];
// 右指针(i)入窗,如果数量为1,即为新元素,len++
map[c]++;
// 当右指针处的元素数量大于1,证明窗口内有重复元素
// 循环直到右指针元素数量为1,这样能保证窗口内元素一直是互不相同的
while (map[c] > 1) {
// 左指针元素出窗
map[ch[left]]--;
// 收缩左指针
left++;
}
// 更新长度
maxLen = Math.max(maxLen, i - left + 1);
}
return maxLen;
}
}
思路:
简洁版
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] ch = s.toCharArray();
int n = ch.length;
int[] map = new int[128];
int maxLen = 0;
int left = 0;
for (int i = 0; i < n; i++) {
char c = ch[i];
map[c]++;
while (map[c] > 1) {
map[ch[left]]--;
left++;
}
maxLen = Math.max(maxLen, i - left + 1);
}
return maxLen;
}
}
思路:
自己第一次写的版本,有点累赘。
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] ch = s.toCharArray();
int n = ch.length;
// ASCII码有128个
int[] map = new int[128];
int len = 0;
int maxLen = 0;
// 窗口左指针
int left = 0;
// 开始滑动窗口,for循环遍历右指针
for (int i = 0; i < n; i++) {
// 右指针(i)入窗,如果数量为1,即为新元素,len++
if (++map[ch[i]] == 1) {
len++;
if (len > maxLen) {
maxLen = len;
}
} else {
// 如果是旧元素,不算缩小窗口,left++
// 直到右指针的元素数量为1,这样能保证窗口内元素一直是互不相同的
while (map[ch[i]] > 1) {
map[ch[left++]]--;
}
}
// 更新目前窗口的长度(右-左+1)
len = i - left + 1;
}
return maxLen;
}
}
3090. 每个字符最多出现两次的最长子字符串
和上题一模一样,上题是最多出现一次,这题是最多出现两次。(但是这题反而难度是简单题,搞笑了)
思路:
同上,仅需把while (map[c] > 1)
改成2.
class Solution {
public int maximumLengthSubstring(String s) {
char[] ch = s.toCharArray();
int n = ch.length;
int[] map = new int[128];
int maxLen = 0;
int left = 0;
for (int i = 0; i < n; i++) {
char c = ch[i];
map[c]++;
while (map[c] > 2) {
map[ch[left]]--;
left++;
}
maxLen = Math.max(maxLen, i - left + 1);
}
return maxLen;
}
}
这里可以开长度为26的数组,然后map[c-‘a’],语义更清晰了,但是写得更慢了。
1493. 删掉一个元素以后全为 1 的最长子数组
思路【我】:
- 题目含义:窗口里面最多只能有一个0
- 不定长滑动窗口三步曲:入–出–更新
- 入:右指针进窗
- 出:当zero不满足窗口要求,左指针收缩
- 更新
- 结果:题目要求的是,删除0之后最长为1的子数组长度。因为我们默许窗口内最多有一个0,所以要减一
class Solution {
public int longestSubarray(int[] nums) {
// 窗口里面最多只能有一个0
int n = nums.length;
int maxLen = 0;
int zero = 0;
int left = 0;
for (int i = 0; i < n; i++) {
// 右指针进窗
if (nums[i] == 0) {
zero++;
}
// 当zero不满足窗口要求,左指针收缩
while (zero > 1) {
// 当且仅当左指针元素为0,才zero--
if (nums[left] == 0) {
zero--;
}
left++;
}
// 更新
maxLen = Math.max(maxLen, i - left + 1);
}
// 题目要求的是,删除0之后最长为1的子数组长度。因为我们默许窗口内最多有一个0,所以要减一
return maxLen - 1;
}
}
思路【我】:
纯净版:
class Solution {
public int longestSubarray(int[] nums) {
int n = nums.length;
int maxLen = 0;
int zero = 0;
int left = 0;
for (int i = 0; i < n; i++) {
if (nums[i] == 0) {
zero++;
}
while (zero > 1) {
if (nums[left] == 0) {
zero--;
}
left++;
}
maxLen = Math.max(maxLen, i - left + 1);
}
return maxLen - 1;
}
}