秋招突击——8/22——算法整理——滑动窗口类型题目思维方式——查找最短包含子串、找到字符串中所有字母异位词、无重复最长子串

92 篇文章 10 订阅

引言

  • 今天面试字节,被老师指出来代码能力薄弱,确实如此。后续应当多加练习!
  • 今天的问题框架没有想好,然后直接上了优化策略,然后在那里缝缝补补!错过了机会!

正文

基本思路

问题简化==》控制不了双指针,先控制一个指针,实现单循环!

  • 先控制一个指针,实现单循环,将问题简化
  • 然后在增加一个指针,不断将问题进行优化,并且满足更多得条件

查找最短包含子串

在这里插入图片描述

考试实现代码

这个代码是我当时直接想的,我就说怎么越写越熟悉,合着我当时做这道题就是按照这个错误的代码写的,然后印象太深刻了,还是按照这个错误的代码写的,这个印象太深刻了!

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {


    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String str1 = in.nextLine();
        String str2 = in.nextLine();

        Map<Character, Integer> map = new HashMap<>();
        for (int i = 0; i < str2.length(); i++) {
            map.put(str2.charAt(i), map.getOrDefault(str2.charAt(i), 0) + 1);
        }

        int minLen = Integer.MAX_VALUE;
        int count = map.size();
        String res = "";

        int l = 0, r = 0;

        while (l < str1.length()) {
            // 遍历找到第一个包含l的值
            char charL = str1.charAt(l);
            while (!map.containsKey(charL)) {
                l++;
                charL = str1.charAt(l);
            }
            map.put(charL,map.get(charL) - 1);
            if(map.get(charL) == 0) count--;

            // 往后继续遍历l
            while (r < str1.length() && count != 0) {
                // 遍历找到第一个包含字母的值
                char charR = str1.charAt(r);
                while (!map.containsKey(charR)) {
                    r++;
                    charR = str1.charAt(r);
                }

                // 更新对应编码
                map.put(charR,map.get(charR) - 1);
                if(map.get(charR) == 0) count--;
                r ++;
            }

            // 比较最大值
            if (r < str1.length() && r - l + 1 < minLen){
                minLen = r - l + 1;
                res = str1.substring(l ,r + 1);
            }

            l ++;
            charL = str1.charAt(l);
            map.put(charL,map.get(charL) + 1);
            count ++;
        }

        System.out.println(res);


    }
}

总结

  • 上述代码中,同时写了左指针的优化方式,又写了右指针的优化方式,但是两个连在一块就开始缝缝补补了,这样只会让问题更加复杂,并不好写代码,并不好解决。
  • 跑步的时候想了很久,具体思维方式见下一节!
考试反思代码===》先确定一边的指针,然后再移动另外一个指针修改

终于写出来了

  • 先实现一个指针,然后根据第一个指针的条件,再决定第二个指针的运动过程!
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main {


    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String str1 = in.nextLine();
        String str2 = in.nextLine();

        Map<Character, Integer> mapTar = new HashMap<>();
        Map<Character, Integer> mapSour = new HashMap<>();

        for(int i = 0;i < str2.length(); i++){
            mapTar.put(str2.charAt(i),mapTar.getOrDefault(str2.charAt(i), 0 ) + 1);
        }

        // 逐个进行遍历
        int count = 0;
        int minLen = Integer.MAX_VALUE;
        String res = "";

        for (int l = 0,r = 0;l <= r && r < str1.length();r ++){
            char charR = str1.charAt(r);
            if(mapTar.containsKey(charR)){
                // 记录更新对应的字符
                mapSour.put(charR,mapSour.getOrDefault(charR,0) + 1) ;
                if(mapSour.get(charR) == mapTar.get(charR)){
                    count ++;
                }

                if(count == mapTar.size()) {

                    // 判定是否需要移动对应的l,保证对应l是相同的
                    char charL = str1.charAt(l );
                    while(l< r ) {
                        if (!mapTar.containsKey(charL)) {
                            // 当前的字符是无效的字符
                            l++;
                        } else{
                            //如果下一个字符还是对应的字符,就直接跳过,然后输出对应结果
                            if(mapSour.get(charL) - 1 < mapTar.get(charL)) break;
                            else{
                                l ++;
                                mapSour.put(charL,mapSour.get(charL) - 1);
                            }
                        }
                        charL = str1.charAt(l);
                    }

                    // 比较长度,确定最小的长度
                    int len = r - l + 1;
                    if(len < minLen){
                        res = str1.substring(l,r + 1);
                        minLen = len;
                    }

                    // 在往后移动对应的L即可,同时更新对应的字符
                    l ++;
                    mapSour.put(charL,mapSour.get(charL) - 1);
                    if(mapSour.get(charL) < mapTar.get(charL)) count --;
                }
            }
        }

        System.out.println(res);

    }
}

在这里插入图片描述

找到字符串中所有字母异位词

复习实现

思路分析

  • 不能代入一个思维定式,觉得每一次遍历右边都是对的,这里应该根据情况进行确定
  • 首先这里判定的子串有以下几个特征
    • 仅仅包含目标字符串内容
    • 长度是固定的
  • 基于以上两个特征可以想着从左边指针进行遍历,因为长度一定,右指针的位置就是固定的了!
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;


public class Main {


    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String s = in.nextLine();
        String p = in.nextLine();
        List<Integer> res = new ArrayList<>();


        // 记录每一个字符出现的频率
        Map<Character, Integer> mapS = new HashMap<>();
        Map<Character, Integer> mapP = new HashMap<>();
        for (char x: p.toCharArray()) mapP.put(x,mapP.getOrDefault(x,0) + 1);

        int count = 0;
        for(int r = 0,l = 0;l <= s.length() - p.length();l ++){
            char charL = s.charAt(l);
            if(mapP.containsKey(charL)) {
                // 处理一下一开始的情况
                if (l >= r) {
                    mapS.clear();
                    count = 0;
                    r= l + 1;

                    // 包含对应字符,只有第一次运行的时候才会这样
                    mapS.put(charL,mapS.getOrDefault(charL,0) + 1);
                    if(mapP.get(charL) == mapS.get(charL)) count ++;
                }


                // 在合法长度内部
                while (r - l + 1 <= p.length()){
                    // 匹配的是不在目标范围的字符,直接跳过
                    char charR = s.charAt(r);
                    if (mapP.containsKey(charR)){
                        // 这里是匹配到满足条件的字符串
                        mapS.put(charR,mapS.getOrDefault(charR,0) + 1);
                        if(mapP.get(charR) == mapS.get(charR)) count ++;
                        r ++;
                    }else{
                        // 这里匹配到不满足条件的字符串
                        l = r;
                        count = 0;
                        break;
                    }
                }

                // 满足条件再进行判定
                if(count == mapP.size()) {
                    res.add(l);
                }

                // 下一步i ++ 需要进行定点清除,并判定是否需要维护count
                if(mapP.containsKey(s.charAt(l))){
                    mapS.put(charL,mapS.getOrDefault(charL,0) - 1);
                    // 原来比他大的减掉他,变成相等的了,不用边,如果原来是跟他相等的,就要改变
                    if (mapP.get(charL) == mapS.get(charL) + 1) count --;
//                    else count --;
                }

            }
        }
        System.out.println(res.toString());
    }
}

总结

  • 这个是我今天有做了一遍这个题目,然后按照昨天晚上想的思路做的,我先根据情况分析了应该先确定左指针的操作,进而根据左指针的相关操作来决定右指针在不同情况下的操作,但是这个编程方式更像是打补丁,差不多用了四十分钟,中间遇到了很多问题,只能一个一个调试出来,还是不行,主要是很多细节没有想清楚!

在这里插入图片描述

  • 看了以前的一些做题记录,发现以前真厉害,想的真清楚,一下子就做出来了,我靠!真厉害!现在怎么想不明白了!是我变笨了吗?
参考实现
  • 移动的右指针,然后再根据不同的情况移动左指针,具体思路如下
    • 如果右指针的字符串不在目标字典中,直接跳过,移动l和r到下一个位置,并且清空原来的字典
    • 如果右指针的字符串在目标字典中,就移动左指针,让特定的字符串满足目标,因为这里数量是一一对应的
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;


public class Main {


    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String s = in.nextLine();
        String p = in.nextLine();
        List<Integer> res = new ArrayList<>();


        // 记录每一个字符出现的频率
        Map<Character, Integer> mapS = new HashMap<>();
        Map<Character, Integer> mapP = new HashMap<>();
        for (char x: p.toCharArray()) mapP.put(x,mapP.getOrDefault(x,0) + 1);

        for (int r = 0,l = 0;r < s.length();r ++){
            char charR = s.charAt(r);
            if (mapP.containsKey(charR)){
                // 移动l,控制r的次数是相同
                mapS.put(charR,mapS.getOrDefault(charR,0) + 1);
                while(l < r && mapS.get(charR) != mapP.get(charR)){
                    char charL = s.charAt(l ++);
                    mapS.put(charL,mapS.get(charL) - 1);
                    if (mapS.get(charL) == 0) mapS.remove(charL);
                }

                // 判定是否完全相等
                int len = r - l + 1;
                if (len == p.length() && mapS.size() == mapP.size()){
                    res.add(l);
                }
            }else{
                // 不包含对应的字符,直接跳过,并清空字典
                l = r + 1;
                mapS.clear();
            }
        }
        
        System.out.println(res.toString());
    }
}

总结

  • 写的真的是简洁,这个思路真的是简洁,确定了是滑动窗口,也就是说右指针和左指针是同向运动的,就先走右指针,然后根据不同的情况来制定左指针的运动过程,左指针就是用来纠正右指针的。
  • 没记住这个基本的代码结构,还是不行的!然后出了记住这个基本的代码结构,还应该能够有一个固定的思维顺序和方式。

无重复最长子串

题目链接

个人实现
  • 这次实现是建立在之前已经忘得一干二净的基础上的,总体来说实现的还不错,至少对滑动窗口的思维逻辑还有代码框架,有了更加熟悉的认知!
class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> map = new HashMap<>();
        int res = 0;
        for (int l = 0, r = 0; r < s.length(); r++) {
            char charR = s.charAt(r);
            map.put(charR, map.getOrDefault(charR, 0) + 1);
            // 需要移动l来纠正
            while (map.get(charR) > 1) {
                char charL = s.charAt(l++);
                map.put(charL, map.get(charL) - 1);
            }

            // 计算长度
            res = Math.max(r - l + 1, res);
        }
        return res;
    }
}

在这里插入图片描述

总结

  • 历时大半天,从昨晚面试完字节自闭,到现在完整地整理完这类题目,我对这类问题有了更加清晰的认知,再来一个新类型的题目,就算一时间想不起来任何更好地方法,但是最朴素的方法还是能够想到的!
  • 很多代码问题,都是这样的!有的时候整体揉在一块,你想不清楚怎么做,但是你可以把这个问题简化,先简化一个条件或者某一个状态去考虑,等这个简单的考虑清楚了,就可以考虑复杂的了。这类思想在很多地方都是适用的。本章讲的双指针问题,便是如此,先是想清楚一个指针是什么样,然后在想另外一个指针基于这个指针应该怎么运作!然后动态规划也是差不多,先清楚最后一个状态是啥,有什么推导出来的,就可以推出来状态转移方程。
  • 当然这些东西都是大而全的东西,实际上还是要多做题,多看题!
  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值