滑动窗口系列题
一、替换后的最长重复子串
给你一个字符串 s
和一个整数 k
。你可以选择字符串中的任一字符,并将其更改为任何其他大写英文字符。该操作最多可执行 k
次。
在执行上述操作后,返回 包含相同字母的最长子字符串的长度。
输入:s = "AABABBA", k = 1 输出:4 AABA BABB
思路:滑动窗口 滑动窗口的大小是根据条件得到的,不是题目中规定好的。
滑动窗口就是能够装更多的重复字母的窗口。:满足条件的时候滑动窗口就扩容,不满足条件的时候滑动窗口左边界移动
那么?这里的条件是什么:滑动窗口的长度<+重复的字母+可以替换的次数。也就是说如果窗口中需要替换的次数<=可以替换的次数,那么该滑动窗口就仍然有效,否则就要移动。
left:滑动窗口的左边界 right:滑动窗口的右边界 maxLen:某个滑动窗口中重复最多的字母。
1.首先,遍历到某一个字母的时候,把它存到数组中 arr[s.charAt(i)-'A']++; 然后更新maxLen
2.判断此时滑动窗口是扩容还是左边界改变。
2.1 if(right-left+1>maxLen+k) 此时就要左边界改变 arr[s.charAt(left)-'A']--;left++;
2.2 反之,更新一下滑动窗口的最大长度(也就是要求的结果 最长重复子串):res=Math.max(res,right-left+1);
总结:
其实就是在不断寻找最长滑动窗口长度的过程。滑动窗口的大小只能增大不能减小。
代码:
class Solution {
public int characterReplacement(String s, int k) {
int left=0,right=0;
int res=0;
int maxLen=-1;
char[] ch=new char[26];
while(right<s.length()){
char c=s.charAt(right);
ch[c-'A']++;
maxLen=Math.max(maxLen,ch[c-'A']);
if(right-left+1>maxLen+k){
ch[s.charAt(left)-'A']--;
left++;
}
res=Math.max(res,right-left+1);
right++;
}
return res;
}
}
二、最大连续1的个数(滑动窗口)
给定一个二进制数组 nums
和一个整数 k
,如果可以翻转最多 k
个 0
,则返回 数组中连续 1
的最大个数。
输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3 输出:10 解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1] 粗体数字从 0 翻转到 1,最长的子数组长度为 10。
模拟:
1. 0,0,1,1,0,0 此时maxLen+k<right-left+1 此时滑动窗口的左边界+1; left为0
2. 0,1,1,0,0,1 此时是滑动窗口长度增大的过程 直到增长到1,1,0,0,1,1,1,0,1,1 此时maxLen+k=10
left为2
3. 到1,0,0,1,1,1,0,1,1之后,就再也没有机会能使滑动窗口扩容了,此后滑动窗口的长度一直保持left-right+1;
思路:
当遇到1的时候,cnt++;maxLen=Math.max(cnt,maxLen);直到maxLen+k<right-left+1的时候,说明滑动窗口无法继续扩容了,此时将左边界右移一位,此时的right-left+1就是滑动窗口的大小(也就是maxLen+k) 通过cnt变大,使得maxLen取最大,从而使得滑动窗口maxLen+k变最大
代码:
class Solution {
public int longestOnes(int[] nums, int k) {
int cnt=0;
int left=0;
int right=0;
int maxLen=0;
int res=-1;
while(right<nums.length){
if(nums[right]==1)cnt++;
maxLen=Math.max(maxLen,cnt);
while(right-left+1>maxLen+k){
if(nums[left]==1)cnt--;
left++;
}
res=Math.max(res,right-left+1);
right++;
}
return res;
}
}
三、尽可能使字符串相等(滑动窗口)
题意:
给你两个字符串s,t;s[i]和t[i]的ASCII值的绝对值为转换的开销。在给定一个maxCost的前提下,求可以转换的最大长度。
输入:s = "abcd", t = "bcdf", maxCost = 3 输出:3 解释:s 中的 "abc" 可以变为 "bcd"。开销为 3,所以最大长度为 3。
思路:滑动窗口
利用滑动窗口,主要是判断什么时候扩容,什么时候左边界右移;
1.扩容,当totalCost<=maxCost的时候,right++。移动之后更新totalCost
2.右移: 当totalCost>maxCost的时候,左边界就要右移,left++;移动之后更新totalCost;
3.在移动的过程中记录最大的长度即,res=Math.max(res,right-left+1);
代码:
class Solution {
public int equalSubstring(String s, String t, int maxCost) {
int totalCost=0;
int left=0;
int right=0;
int res=0;
while(right<t.length()){
totalCost+=Math.abs(s.charAt(right)-t.charAt(right));
if(totalCost>maxCost){
totalCost-=Math.abs(s.charAt(left)-t.charAt(left));
left++;
}
res=Math.max(res,right-left+1);
right++;
}
return res;
}
}
四、最小覆盖子串(滑动窗口)
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
题意:
在s串中寻找涵盖t串的最小子串,并且返回最小子串;如果不存在就返回"";
思路:
要在s串中寻找涵盖t串的最小子串。
1.先将t串的字符和数量都统计起来(使用数组和map都可以),这里使用数组进行统计字符的种类,count统计字符的数量
2.统计完毕之后就开始在s串进行遍历了。如果遍历到的字符是字符中出现的字符,那么count--;
如果count==0的话,就说明找到了一个子串涵盖t串,此时右边界为right
3.找到右边界之后,对左边界进行压缩,if(--cnt[s.charAt(left)]>0),就说明letf所指向的该元素是在t串中出现的,因此左边界不能再移动了。此时更新子串的长度。right-left;
代码:先固定右边界 再固定左边界
class Solution {
public String minWindow(String s, String t) {
char[] ch1 = s.toCharArray();
char[] ch2 = t.toCharArray();
int[] cnt = new int[128];
int count = 0;
// 将t串中的字符都收集起来
for (char ch : ch2) {
cnt[ch - 'A']++;
count++;
}
// 遍历s串
int left = 0, start = -1, end = s.length();
for (int right = 0; right < s.length(); right++) {
// 如果遍历到的该字符 在t串中出现过的 就count--
if (--cnt[ch1[right] - 'A'] >= 0)
count--;
// 在s中找到子串
while (count == 0) {
// 如果当前left所指向的字符是t串中出现的字符 那么就不能向右更新了
if (++cnt[ch1[left] - 'A'] > 0) {
if (right - left < end - start) {
end = right;
start = left;
}
count++;
}
left++;// 当前left还能继续移动
}
}
return start == -1 ? "" : s.substring(start, end + 1);
}
}
快慢指针应用
五、环形链表II
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
思路:
1.首先判断环形链表是否有环,可以根据快慢指针是否相等判断。慢指针每次移动一个距离,快指针每次移动两个距离,如果有环的话,那么快指针可以由领先(在慢指针的前面)再到落后(在慢指针的后面)再到相等。因为快指针每次追一个距离,差距每次-1,差距为0的时候,就相遇了。
2.有环则一定有入口,根据公式2(x+y)=n*(y+z)+x+y,得到x=(n-1)(y+z)+z;可以得到
起点到入口的距离 == 相遇的点到起点的距离(顺时针)
代码:
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head==null||head.next==null)return null;
ListNode slow=head;
ListNode fast=head;
while(fast!=null&&fast.next!=null&&fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
//快慢指针相遇 说明有环
if(slow==fast){
ListNode index1=slow;
ListNode index2=head;
while(index1!=index2){
index1=index1.next;
index2=index2.next;
}
return index1;
}
}
return null;
}
}
六、重排链表(寻找中间值+翻转链表)
给定一个单链表 L
的头节点 head
,单链表 L
表示为:L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
题意:按照 1,n,2,n-1,3,n-2的规律排列
思路:找到中间值,变成两段序列(0->中间值前一个)、(中间值->最后)。将中间值以后的序列反转。反转之后两端序列依次出一个然后形成一段链表
1->2->3->4->5
1.找中间值 3。分成两段:1->2\ 3->4->5
2.中间值作为新的头结点反转 5->4->3
3.1->2\ 5->4->3 左边拿一个 右边拿一个 1->5->2->4->3
代码:
class Solution {
public void reorderList(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
//slow就是中间值 将中间值为新的起点 进行翻转
ListNode pre=null;
while(slow!=null){
ListNode temp=slow.next;
slow.next=pre;
pre=slow;
slow=temp;
}
//反转完了 此时pre就是新的头结点
while(pre.next!=null){
ListNode temp1=head.next;
ListNode temp2=pre.next;
head.next=pre;
pre.next=temp1;
head=temp1;
pre=temp2;
}
}
}