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

97 篇文章 11 订阅

引言

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

正文

基本思路

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

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

查找最短包含子串

leetcode原题
在这里插入图片描述

考试实现代码

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

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;
    }
}

在这里插入图片描述

01得分——8/30补充

题目

  • 对于某个 由0和1组成的串,他的得分是每段连续的1的长度的平方的总和。给定一个01串,你可以进行最多k次操作,每次操作可以选择一个0将其变成1。请问操作过后该串的最大得分是多少。
  • 输入描述
    • 第一行有两个数字n,k,分别标识字符串的长度,以及可以操作的次数
    • 第二行仅有0和1构成的长度为n的字符串
  • 输出描述
    • 一个数字,代表最大得分
  • 样例
    • 6 1
    • 100101
    • 输出:10
      • 1 00 111 :就是1 + 3* 3 = 10
个人实现
  • 这个问题我知道是用滑动窗口,但是一直轴在一个地方,就是想到是一次要移动很多次r,但是又不要每一次都进行判定,是否移动l,怎么使用for来实现双指针,然后就实现了下面的代码,完全是通过补丁实现的
  • 只能说这个代码很丑,真的很丑,那个困住我的点,还是跟我之前问题一样,执着于某一个指针,然后不断进行遍历,导致忽略了另外一个指针!是先解决某一个,而不是执着于某一个!
import java.util.*;

public class Main {


    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            int n = sc.nextInt();
            int k = sc.nextInt();
            String str = sc.next();
//            System.out.println(str);
            // 滑动窗口实现不同的操作
            int maxV = 0;
            int maxL = 0;
            int maxR = 0;

            int l = 0, r = 0;
            while (l < n) {
                // 先确定l的移动,如果
                while (k > 0 && r < n) {
                    if (r < n && str.charAt(r) == '0') {
                        k--;
                    }
                    r++;
                }
                while (r < n && str.charAt(r) == '1') r++;

                // 移动完成之后计算一下当前的结果
                int temp = (int) Math.pow(r - l, 2);
                if (temp > maxV) {
                    maxV = temp;
                    maxL = l;
                    maxR = r;
                }

                // 更新对应的l
                while (l < n && str.charAt(l) == '1') {
                    l++;
                }
                k++;
                l++;
            }

            // 计算左边的值
            int valL = 0;
            int valR = 0;
            for (int i = 0; i < maxL; i++) {
                int count = 0;
                while (i < maxL && str.charAt(i) == '1') {
                    count++;
                    i++;
                }
                valL += Math.pow(count, 2);
            }

            // 计算右边的值
            for (int i = maxR; i < n; i++) {
                int count = 0;
                while (i < n && str.charAt(i) == '1') {
                    count++;
                    i++;
                }
                valR += Math.pow(count, 2);
            }
            System.out.println(maxV + valL + valR);
        }
    }
}
纠正实现
  • 先解决一个指针:右指针r

    • 如果遇到1,直接跳过
    • 如果遇到0,k–
      • 如果k变成了零,怎么办?
  • 再解决第二个指针:左指针l

    • 如果k变成了零,移动左指针
      • 如果左指针是1,默认继续移动
      • 如果左指针是0,然后就k ++
import javax.crypto.Cipher;
import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            int n = sc.nextInt();
            int k = sc.nextInt();
            String str = sc.next();
            char[] charArray = str.toCharArray();
            int res = 0;
            for(int l = 0, r= 0;r < n;r ++){
                // 先解决r的移动,遇到默认直接移动,遇到零
                if (charArray[r] == '0'){
                    k --;
                    charArray[r] = '2';
                }

                // 判定是否移动l
                if (k == 0){
                    // 已经移动完k了,需要移动l,保证k大于零
                    res = Math.max(res,calSocre(charArray));

                    // 移动l,直到移动到一个零
                    while (k == 0 && l <= r){
                        if(charArray[l] == '2') {
                            charArray[l] = '0';
                            k ++;
                        }
                        l ++;
                    }
                }
            }
            System.out.println(res);
        }
    }

    public static int calSocre(char[] charArray){
        // 需要计算以下长度
        int score = 0;
        for(int i = 0;i < charArray.length;i ++){
            int count = 0;
            while(i < charArray.length && charArray[i] != '0'){
                count ++;
                i ++;
            }
            score += count * count;

        }
        return score;
    }
}

总结

  • 这个写起来就流畅很多,不用担心漏了什么地方,因为他的框架就是逻辑自洽的!还有一个问题,你每次千万不要将这两个指针纠结到一块想,分开去想,另外一个指针是基于第一个指针做出的决策,一旦你把这两个东西缠到一块,就写的漏洞百出,完全没有必要!

总结

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

8/30

  • 今天做了一个题目,明明是滑动窗口,但是老毛病又犯了,然后没写好,今天把这三道题完全再刷一遍,整理一下思路!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值