11, 76. 最小覆盖子串
https://leetcode-cn.com/problems/minimum-window-substring/
思路1:暴力解法
* 这里思路还是蛮简单当:就是通过类似笛卡尔积的方式进行遍历
* // 对于 ADOBECODEBANC,通过如下方式遍历:
* // ADO ADOB ADOBE ... ADOBECODEBANC
* // DOB DOBE ... DOBECODEBANC
* // OBE ... ADOBECODEBANC
* // ...
* // ANC
* // 遍历到的数据,通过和target字符串进行比对看下是否满足要求
*
* 时间复杂度: O(n*n*n)
* 比对的时候, 通过把字符串的对应字母及出现个数分别记录下来,
* 然后判断target中的字母是否在被比对的字符串中都有,而且个数大于等于target中的字母个数
思路2: 思路1优化:窗口话缩放
* 思路2:思路1的时间复杂度太高了
* 这里使用类似窗口的操作:先往右扩展窗口,找到对应的数据后,然后再从左缩短窗口
* 双指针,左边指向窗口开始,右边指向窗口结束,当找到合适窗口当时候,尝试把开始位置右移动,看看是否能更短满足条件。
* 时间复杂度: O(n*n)
*
* 这里其实和思路1是不一样的,因为思路1在发现子串之后,通过break跳出内部循环,就相当于是start++了,但是end还是从头开始遍历的;
思路3: 优化思路2中的比对过程
* 思路3:思路2在找到子串,比对子串和目标串的时候, 每次都遍历的方式效率略低
* 这里优化比对过程
* 通过一个currentSubMap来实时的记录当前的子串的内容,而不是每次都遍历处理
* 时间复杂度: O(n)
思路4: 优化是否子串的过程
* 思路4:思路3在比对的时候,每次都需要遍历target,这个过程也可以优化
*
* 假设target有三个字母,那么每个字母满足要求,就增加一个贡献值
* 假设某个字母不再满足,就减少一个贡献值。
* 因为我们本身有target及每个字母的map
* 和currentSubMap,所以通过比贡献值就能判断是否还能满足要求
*
* // 这里窗口结束位置后移了,看下贡献值是否应该增加
* if (currentSubMap.get(s.charAt(i)) <= tMap.get(s.charAt(i))) {
* score++;
* }
*
* // 这里开始位置后移了,看一下贡献值是否应该减少
* if (currentSubMap.get(s.charAt(start)) < tMap.get(s.charAt(start))) {
* score--;
* }
package com.shangguigu.dachang.algrithm.A04_SlidingWindow;
import java.util.HashMap;
/**
* @author : 不二
* @date : 2022/4/9-下午8:48
* @desc : 76. 最小覆盖子串
* https://leetcode-cn.com/problems/minimum-window-substring/
**/
public class A40_minWindow {
public static void main(String[] args) {
// String s = "ADOBECODEBANC", t = "ABC";
String s = "ADOBECODEBANC", t = "ABC";
// String s = "a", t = "aa";
// String s = "aa", t = "aa";
// String s1 = minWindow(s, t);
// String s1 = minWindow_v2(s, t);
// String s1 = minWindow_v3(s, t);
String s1 = minWindow_v4(s, t);
System.out.println("结果是:" + s1);
}
/**
* 思路4:思路3在比对的时候,每次都需要遍历target,这个过程也可以优化
*
* 假设target有三个字母,那么每个字母满足要求,就增加一个贡献值
* 假设某个字母不再满足,就减少一个贡献值。
* 因为我们本身有target及每个字母的map
* 和currentSubMap,所以通过比贡献值就能判断是否还能满足要求
*
* // 这里窗口结束位置后移了,看下贡献值是否应该增加
* if (currentSubMap.get(s.charAt(i)) <= tMap.get(s.charAt(i))) {
* score++;
* }
*
* // 这里开始位置后移了,看一下贡献值是否应该减少
* if (currentSubMap.get(s.charAt(start)) < tMap.get(s.charAt(start))) {
* score--;
* }
*
*
* @param s
* @param t
* @return
*/
public static String minWindow_v4(String s, String t){
if (t.length() > s.length()) {
return "";
}
String theSubStr = "";
// 先把目标给存储下来方便后续遍历的时候使用
HashMap<Character, Integer> tMap = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
tMap.put(t.charAt(i), tMap.getOrDefault(t.charAt(i), 0) + 1);
}
HashMap<Character, Integer> currentSubMap = new HashMap<>();
// 每当一个字母满足要求的时候,贡献值加1
// 当某个字母不再满足要求的时候,贡献值减去1
// 当score == t.length()的时候,说明符合要求了
int score = 0;
int start = 0, end = t.length()-1;
// 先把对应的数据给添加到map中,后续end++ 或者start++的时候,直接从map中进行操作
for (int i = 0; i <= end; i++) {
// 如果结果串中没有就可以忽略了
if (tMap.containsKey(s.charAt(i))) {
currentSubMap.put(s.charAt(i), currentSubMap.getOrDefault(s.charAt(i), 0) + 1);
// 说明个数小于了,贡献值加1
if (currentSubMap.get(s.charAt(i)) <= tMap.get(s.charAt(i))) {
score++;
}
}
}
// 如果start和end的差距已经小于了t的长度的话,那么就没有再接着走的必要了
while (start <= end - t.length() + 1 && end < s.length()) {
// 先看下 start-end之间的字符串是否满足要求, 我们这里按照包含end来算
String subStr = s.substring(start, end + 1);
// 不再使用遍历方法,而是使用贡献值的方式进行判断是否是子串
/*boolean findIt = true;
// 遍历出来的结果跟tmap一一对比
for (Character character : tMap.keySet()) {
if (tMap.get(character) > currentSubMap.getOrDefault(character, 0)) {
findIt = false;
}
}*/
// ADOBECODEBANC
if (score == t.length()) {
if (subStr.length() < theSubStr.length() || theSubStr.equals("")) {
theSubStr = subStr;
}
// 找到对应的子串之后,窗口就没有必要再end++了,因为是要找最短的,所以把当前窗口start++
// 这里要先删除,然后再++
if (tMap.containsKey(s.charAt(start))) {
currentSubMap.put(s.charAt(start), currentSubMap.getOrDefault(s.charAt(start), 0) - 1);
// 这里开始位置后移了,看一下贡献值是否应该减少
if (currentSubMap.get(s.charAt(start)) < tMap.get(s.charAt(start))) {
score--;
}
}
start++;
} else {
// 这里要先++然后在添加
end++;
if (end <= s.length() - 1 && tMap.containsKey(s.charAt(end))) {
currentSubMap.put(s.charAt(end), currentSubMap.getOrDefault(s.charAt(end), 0) + 1);
// 说明个数小于了,贡献值加1
if (currentSubMap.get(s.charAt(end)) <= tMap.get(s.charAt(end))) {
score++;
}
}
}
// System.out.println("theSubStr:" + theSubStr);
}
return theSubStr;
}
/**
* 思路3:思路2在找到子串,比对子串和目标串的时候, 每次都遍历的方式效率略低
* 这里优化比对过程
* 通过一个currentSubMap来实时的记录当前的子串的内容,而不是每次都遍历处理
* 时间复杂度: O(n)
*/
public static String minWindow_v3(String s, String t){
if (t.length() > s.length()) {
return "";
}
String theSubStr = "";
// 先把目标给存储下来方便后续遍历的时候使用
HashMap<Character, Integer> tMap = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
tMap.put(t.charAt(i), tMap.getOrDefault(t.charAt(i), 0) + 1);
}
HashMap<Character, Integer> currentSubMap = new HashMap<>();
int start = 0, end = t.length()-1;
// 先把对应的数据给添加到map中,后续end++ 或者start++的时候,直接从map中进行操作
for (int i = 0; i <= end; i++) {
// 如果结果串中没有就可以忽略了
if (tMap.containsKey(s.charAt(i))) {
currentSubMap.put(s.charAt(i), currentSubMap.getOrDefault(s.charAt(i), 0) + 1);
}
}
// 如果start和end的差距已经小于了t的长度的话,那么就没有再接着走的必要了
while (start <= end - t.length() + 1 && end < s.length()) {
// 先看下 start-end之间的字符串是否满足要求, 我们这里按照包含end来算
String subStr = s.substring(start, end + 1);
boolean findIt = true;
// 遍历出来的结果跟tmap一一对比
for (Character character : tMap.keySet()) {
if (tMap.get(character) > currentSubMap.getOrDefault(character, 0)) {
findIt = false;
}
}
// ADOBECODEBANC
if (findIt) {
if (subStr.length() < theSubStr.length() || theSubStr.equals("")) {
theSubStr = subStr;
}
// 找到对应的子串之后,窗口就没有必要再end++了,因为是要找最短的,所以把当前窗口start++
// 这里要先删除,然后再++
if (tMap.containsKey(s.charAt(start))) {
currentSubMap.put(s.charAt(start), currentSubMap.getOrDefault(s.charAt(start), 0) - 1);
}
start++;
} else {
// 这里要先++然后在添加
end++;
if (end <= s.length() - 1 && tMap.containsKey(s.charAt(end))) {
currentSubMap.put(s.charAt(end), currentSubMap.getOrDefault(s.charAt(end), 0) + 1);
}
}
// System.out.println("theSubStr:" + theSubStr);
}
return theSubStr;
}
/**
* 思路2:思路1的时间复杂度太高了
* 这里使用类似窗口的操作:先往右扩展窗口,找到对应的数据后,然后再从左缩短窗口
* 双指针,左边指向窗口开始,右边指向窗口结束,当找到合适窗口当时候,尝试把开始位置右移动,看看是否能更短满足条件。
* 时间复杂度: O(n*n)
*
* 这里其实和思路1是不一样的,因为思路1在发现子串之后,通过break跳出内部循环,就相当于是start++了,但是end还是从头开始遍历的;
*/
public static String minWindow_v2(String s, String t) {
if (t.length() > s.length()) {
return "";
}
String theSubStr = "";
// 先把目标给存储下来方便后续遍历的时候使用
HashMap<Character, Integer> tMap = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
tMap.put(t.charAt(i), tMap.getOrDefault(t.charAt(i), 0) + 1);
}
int start = 0, end = t.length()-1;
// 如果start和end的差距已经小于了t的长度的话,那么就没有再接着走的必要了
while (start <= end - t.length() + 1 && end < s.length()) {
// 先看下 start-end之间的字符串是否满足要求, 我们这里按照包含end来算
String subStr = s.substring(start, end + 1);
// System.out.println("subStr: " + subStr);
// 先比较subStr和t是否满足要求
HashMap<Character, Integer> subMap = new HashMap<>();
for (int i = 0; i < subStr.length(); i++) {
subMap.put(subStr.charAt(i), subMap.getOrDefault(subStr.charAt(i), 0) + 1);
}
boolean findIt = true;
// 遍历出来的结果跟tmap一一对比
for (Character character : tMap.keySet()) {
if (tMap.get(character) > subMap.getOrDefault(character, 0)) {
findIt = false;
}
}
// ADOBECODEBANC
if (findIt) {
// System.out.println("找到对应的子串:" + subStr);
if (subStr.length() < theSubStr.length() || theSubStr.equals("")) {
theSubStr = subStr;
}
// 找到对应的子串之后,窗口就没有必要再end++了,因为是要找最短的,所以把当前窗口start++
start++;
} else {
end++;
}
// System.out.println("theSubStr:" + theSubStr);
}
return theSubStr;
}
/**
* 思路1:暴力解法
* 这里思路还是蛮简单当:就是通过类似笛卡尔积的方式进行遍历
* // 对于 ADOBECODEBANC,通过如下方式遍历:
* // ADO ADOB ADOBE ... ADOBECODEBANC
* // DOB DOBE ... DOBECODEBANC
* // OBE ... ADOBECODEBANC
* // ...
* // ANC
* // 遍历到的数据,通过和target字符串进行比对看下是否满足要求
*
* 时间复杂度: O(n*n*n)
* 比对的时候, 通过把字符串的对应字母及出现个数分别记录下来,
* 然后判断target中的字母是否在被比对的字符串中都有,而且个数大于等于target中的字母个数
*
*/
public static String minWindow(String s, String t) {
if (t.length() > s.length()) {
return "";
}
// 先把目标给存储下来方便后续遍历的时候使用
HashMap<Character, Integer> tMap = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
tMap.put(t.charAt(i), tMap.getOrDefault(t.charAt(i), 0) + 1);
}
// 随机开始进行笛卡尔遍历:
// ADO ADOB ADOBE ...
// DOB DOBE ...
// OBE ...
// ...
// 这种方法不行,没法遍历出来最小长度的, 没法直到是不是最小长度的🤔
// 卧槽,定义一个变量, 在下面遍历不中途退出,遍历全部一遍,就知道哪个最短了,这也太垃圾了把
String minString = "";
for (int i = 0; i < s.length() - t.length(); i++) {
for (int j = i + t.length() - 1; j < s.length(); j++) {
String candidate = s.substring(i, j+1);
System.out.println(candidate);
// 遍历出来的字符串存储成map
HashMap<Character, Integer> canMap = new HashMap<>();
for (int k = 0; k < candidate.length(); k++) {
canMap.put(candidate.charAt(k), canMap.getOrDefault(candidate.charAt(k), 0) + 1);
}
boolean findIt = true;
// 遍历出来的结果跟tmap一一对比
for (Character character : tMap.keySet()) {
if (tMap.get(character) > canMap.getOrDefault(character, 0)) {
findIt = false;
}
}
if (findIt == true && ( minString == "" || candidate.length() < minString.length())) {
minString = candidate;
// 当前的j的遍历就可以结束了
break;
}
}
}
return minString;
}
}