算法学习day26(滑动窗口)

滑动窗口系列题

一、替换后的最长重复子串

给你一个字符串 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;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值