文章目录
引言
- 今天面试字节,被老师指出来代码能力薄弱,确实如此。后续应当多加练习!
- 今天的问题框架没有想好,然后直接上了优化策略,然后在那里缝缝补补!错过了机会!
正文
基本思路
问题简化==》控制不了双指针,先控制一个指针,实现单循环!
- 先控制一个指针,实现单循环,将问题简化
- 然后在增加一个指针,不断将问题进行优化,并且满足更多得条件
查找最短包含子串
考试实现代码
这个代码是我当时直接想的,我就说怎么越写越熟悉,合着我当时做这道题就是按照这个错误的代码写的,然后印象太深刻了,还是按照这个错误的代码写的,这个印象太深刻了!
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;
}
}
总结
- 历时大半天,从昨晚面试完字节自闭,到现在完整地整理完这类题目,我对这类问题有了更加清晰的认知,再来一个新类型的题目,就算一时间想不起来任何更好地方法,但是最朴素的方法还是能够想到的!
- 很多代码问题,都是这样的!有的时候整体揉在一块,你想不清楚怎么做,但是你可以把这个问题简化,先简化一个条件或者某一个状态去考虑,等这个简单的考虑清楚了,就可以考虑复杂的了。这类思想在很多地方都是适用的。本章讲的双指针问题,便是如此,先是想清楚一个指针是什么样,然后在想另外一个指针基于这个指针应该怎么运作!然后动态规划也是差不多,先清楚最后一个状态是啥,有什么推导出来的,就可以推出来状态转移方程。
- 当然这些东西都是大而全的东西,实际上还是要多做题,多看题!