滑动窗口
import java.util.Arrays;
class Solution {
public String minWindow(String s, String t) {
if (s.length() < t.length()){
return "";
}
/**
* 滑动窗口
* 用哈希表数组记录t中每个字符在s中是否出现
* 右指针right一直右移,直到满足条件,然后记录下此时区间的边界,如果长度更小则更新l和r
*/
int[] tCount = new int[128];
int right = -1;
int l = 0;
int r = -1;
for (int i = 0; i < t.length(); i++) {
tCount[t.charAt(i)]++;
}
/**
* 左指针i从0开始,只有当区间包含所有t的字符后才会移动
*/
for (int i = 0; i < s.length() - t.length() + 1; i++) {
/**
* 只要区间没有完全包含t的元素,右指针right就不断移动
* 同时right不能越界
* 每次循环都要找出数组的最大值,时间复杂度为O(n^2)
*/
while (Arrays.stream(tCount).max().getAsInt() > 0 && right < s.length() - 1){
right++;
tCount[s.charAt(right)]--;
}
/**
* 只有当包含t的所有字符且区间长度更小且r < l时才会更新
*/
if (Arrays.stream(tCount).max().getAsInt() == 0) {
if (r < l) {
r = right;
l = i;
}
else if (r - l + 1 > right - i + 1){
r = right;
l = i;
}
}
/**
* 在左指针移动之前,要抛弃掉当前字符
*/
tCount[s.charAt(i)]++;
}
return s.substring(l, r + 1);
}
}
/**
* 时间复杂度 O(n^2)
* 空间复杂度 O(n)
*/
优化1——不用每次都找出数组最大值,同时简化判断条件
import java.util.Arrays;
class Solution {
public String minWindow(String s, String t) {
if (s.length() < t.length()){
return "";
}
/**
* 滑动窗口
* 用哈希表数组记录t中每个字符在s中是否出现
* 右指针right一直右移,直到满足条件,然后记录下此时区间的边界,如果长度更小则更新l和r
*/
int[] tCount = new int[128];
int num = t.length();
int right = -1;
int l = 0;
int r = -1;
for (int i = 0; i < t.length(); i++) {
tCount[t.charAt(i)]++;
}
/**
* 左指针i从0开始,只有当区间包含所有t的字符后才会移动
*/
for (int i = 0; i < s.length() - t.length() + 1; i++) {
/**
* 只要区间没有完全包含t的元素,右指针right就不断移动
* 同时right不能越界
* 使用一个变量num同步记录t中字符出现的次数,当次数大于0时才减1,这样就不用每次都比较tCount的最大值了
*/
while (num > 0 && right < s.length() - 1){
right++;
if (tCount[s.charAt(right)] > 0) {
num--;
}
tCount[s.charAt(right)]--;
}
/**
* 只有当包含t的所有字符且区间长度更小且r < l时才会更新
*/
if (num == 0 && (r < l || r - l + 1 > right - i + 1)) {
r = right;
l = i;
}
/**
* 在左指针移动之前,要抛弃掉当前字符,同时维护num
* 如果这个字符的次数是负数,说明其不在t中,num就不用更新
*/
if (tCount[s.charAt(i)] == 0) {
num++;
}
tCount[s.charAt(i)]++;
}
return s.substring(l, r + 1);
}
}
/**
* 时间复杂度 O(n)
* 空间复杂度 O(n)
*/
优化2——简化边界判断条件
import java.util.Arrays;
class Solution {
public String minWindow(String s, String t) {
/**
* 哈希表存储t中所有字符出现的次数
*/
int[] arr = new int[128];
for (int i = 0; i < t.length(); i++) {
arr[t.charAt(i)]++;
}
/**
* 统计总次数,以此判断是否子串包含了所有t的字符
*/
int sum = Arrays.stream(arr).sum();
/**
* s中的字符个数可能会大于t,当出现多个相同元素时,记录真实的个数信息
*/
int[] real = Arrays.copyOf(arr, arr.length);
int left = 0;
int right = 0;
String result = s + t;
while (right <= s.length() - 1){
/**
* 当s中存在t中的字符时,该字符次数减1,总次数减1
*/
if (arr[s.charAt(right)] > 0){
arr[s.charAt(right)]--;
real[s.charAt(right)]--;
sum--;
right++;
}
else {
/**
* 当该字符不在t中或者s中个数大于t中的个数时,单独记录下真实的次数信息
*/
real[s.charAt(right)]--;
right++;
}
/**
* 当num == 0说明t中所有字符均在子串中
*/
if (sum == 0){
/**
* 为了尽可能取到最短的子串,尝试将left右移
* 如果real[s.charAt(left)] < 0,说明这个字符不在t中,或者是多余的,就可以略过
*/
while (real[s.charAt(left)] < 0){
real[s.charAt(left)]++;
left++;
}
/**
* 保留最短的子串
*/
String res = s.substring(left, right);
if (res.length() < result.length()){
result = res;
}
/**
* 处理完当前区间后,left右移一位继续判断,同时当前的left位置的字符次数和总字符次数要加1
*/
arr[s.charAt(left)]++;
real[s.charAt(left)]++;
left++;
sum++;
}
}
/**
* 如果不存在这样的子串,那么result不会发生变化
*/
return result.equals(s + t) ? "" : result;
}
}
/**
* 时间复杂度 O(n)
* 空间复杂度 O(n)
*/
优化3——不使用额外数组
class Solution {
public String minWindow(String s, String t) {
int[] hash = new int[128];
for (int i = 0; i < t.length(); i++) {
hash[t.charAt(i)]++;
}
int count = t.length();
int slow = 0;
int fast = 0;
String min = s + " ";
while (fast < s.length()) {
char word = s.charAt(fast);
if (hash[word] > 0) {
count--;
}
hash[word]--;
if (count == 0) {
while (hash[s.charAt(slow)] < 0) {
hash[s.charAt(slow)]++;
slow++;
}
if (min.length() > fast - slow + 1) {
min = s.substring(slow, fast + 1);
}
hash[s.charAt(slow)]++;
slow++;
count++;
}
fast++;
}
return min.length() == s.length() + 1 ? "" : min;
}
}
/**
* 时间复杂度 O(n)
* 空间复杂度 O(n)
*/
https://leetcode-cn.com/problems/minimum-window-substring/