力扣滑动窗口题目-76最小覆盖子串&&1234替换子串得到平衡字符串

76 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。

如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"

输出:"BANC"

解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a"

输出:"a"

解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa"

输出: ""

解释: t 中两个字符 'a' 均应包含在 s 的子串中,

因此没有符合条件的子字符串,返回空字符串。

java 复制代码
class Solution {
    public String minWindow(String s, String t) {
        int[] cnt = new int[1000];
        for (int i = 0; i < t.length(); i++) {
            cnt[t.charAt(i) - 'A']++;
        }
        HashSet<Character> set = new HashSet<>();
        for (char a : t.toCharArray()) {
            set.add(a);
        }
        int reFlag = 0;
        int min = Integer.MAX_VALUE;
        int l = 0;
        String minStr = s;
        int needCount = set.size();
        int hasCount = 0;
        for (int r = 0; r < s.length(); r++) {
            int flag = 0;
            char current = s.charAt(r);
            if (set.contains(current)) {
                // if(cnt[current-'A']>0){
                cnt[current - 'A']--;
                // }
                // 翻了个大错误!:cnt[current-'A']==0 如果这里>0才--,会出错,直接hasCount!如果是S=AAAB  t=AB 那么cnt[0]一开始是1。--。为0 直接hascount了!
                if (cnt[current - 'A'] == 0) {
                    hasCount++;
                }
            }
            while (hasCount == needCount && l <= r) {
                // 移动左指针
                String currentStr = s.substring(l, r + 1);
                // System.out.println(currentStr);
                if (currentStr.length() < min) {
                    reFlag = 1;
                    min = r - l + 1;
                    minStr = currentStr;
                }
                cnt[s.charAt(l) - 'A']++;
                if (set.contains(s.charAt(l)) && cnt[s.charAt(l) - 'A'] > 0) {
                    hasCount--;
                }
                l++;
            }

        }
        // if(t.length()>s.length()) return "";
        if (reFlag == 0)
            return "";
        return minStr;
    }
}

题解:

这道题很明显的滑动窗口题目:

使用set来存储t出现过的字符,方便后续的数组的计算。

使用数组来存储t出现的字符的次数。

使用needCount存储set.size()(如果达到了needCount表示当前子数组合法)

使用hasCount存储已经满足了几个字符

使用l表示慢指针

使用min和minstr来表示最小值和最短字符串

使用reflag表示最后到底是返回的全部字符串还是返回空字符串" 表示不满足题目"
首先使用快指针r循环遍历s字符串,如果碰到了字符串在set内,开始减数组出现的次数。如果是0表示这个字符已经满足出现的次数了,hascount++;
如果hasCount=needCount,表示字符串的所有字符都满足了出现字数的要求,开始移动左指针和判断最短字符串了! 得到min和minStr,开始移动l,并且如果移除的s.charAt[l]是在set里面,因为我们已经移除了,所以cnt必须要加了!如果加成0了,那么hasCount--了 !表示当前字符串不存在我们 这个字符了。

时间复杂度分析:

分析修改后的代码时间复杂度:

时间复杂度分析

您的算法是O(n),其中n是字符串s的长度。

详细分析:

外层循环:for(int r = 0; r < s.length(); r++)

执行n次(n = s.length())

内层while循环:

虽然看起来是嵌套循环,但每个字符最多被左指针和右指针各访问一次

左指针l只会向右移动,不会回退

因此,虽然有两个循环嵌套,但总操作次数是O(2n) = O(n)

其他操作:

初始化cnt数组:O(128) = O(1)

初始化set:O(m),其中m是t的长度

计算needCount:O(128) = O(1)

空间复杂度分析

cnt数组:固定大小128 = O(1)

set集合:O(m),其中m是t中不同字符的数量

其他变量:O(1)

总空间复杂度:O(m)

为什么是O(n)而不是O(n²)?

关键点:滑动窗口的左右指针都只向前移动,不会回退

右指针r:从0移动到n-1,共n步

左指针l:从0开始,最多移动到n-1,也是n步

虽然代码有嵌套循环,但每个字符最多被左右指针各访问一次

这类似于"两指针遍历数组"的模式,虽然代码结构是嵌套循环,但实际时间复杂度是线性的。

对比暴力解法:

如果使用暴力解法(检查所有子串),时间复杂度会是O(n³)或O(n²),而您的滑动窗口解法通过巧妙的指针移动将复杂度降到了O(n),这是相当高效的。

总结:算法的时间复杂度是O(n),空间复杂度是O(m),其中n是s的长度,m是t的长度

1234. 替换子串得到平衡字符串

提示

有一个只含有 'Q', 'W', 'E', 'R' 四种字符,且长度为 n 的字符串。

假如在该字符串中,这四个字符都恰好出现 n/4 次,那么它就是一个「平衡字符串」。

给你一个这样的字符串 s,请通过「替换一个子串」的方式,使原字符串 s 变成一个「平衡字符串」。

你可以用和「待替换子串」长度相同的 任何 其他字符串来完成替换。

请返回待替换子串的最小可能长度。

如果原字符串自身就是一个平衡字符串,则返回 0。

示例 1:

输入:s = "QWER"

输出:0

解释:s 已经是平衡的了。

示例 2:

输入:s = "QQWE"

输出:1

解释:我们需要把一个 'Q' 替换成 'R',这样得到的 "RQWE" (或 "QRWE") 是平衡的。

示例 3:

输入:s = "QQQW"

输出:2

解释:我们可以把前面的 "QQ" 替换成 "ER"。

示例 4:

输入:s = "QQQQ"

输出:3

解释:我们可以替换后 3 个 'Q',使 s = "QWER"。

提示:

1 <= s.length <= 10^5

s.length 是 4 的倍数

s 中只含有 'Q', 'W', 'E', 'R' 四种字符

题目分析

题目的要求是找到一个「待替换子串」,通过替换这个子串,使得原字符串s变成一个「平衡字符串」。

也就是说原字符串可以看作两个部分:「待替换子串」 + 「不替换的内容」

那么我们就要求 「不替换的内容」中四个字符的出现次数必须小于等于n/4,这部分出现次数少于n/4的字符我们可以用「替换子串」去补充。

因此我们的目标就是,找到最短的「待替换子串」,使得「不替换的内容」中四个字符的出现次数小于等于n/4。

那么我们如何去搜索最短的「待替换子串」,或者说我们对于当前得到的一个子串如何去进行进一步的搜索。我们记一个可以使得「不替换的内容」中四个字符的出现次数小于等于n/4的「待替换子串」为合法子串。

如果当前得到的子串是一个合法子串,那么我们考虑去缩短这个子串,看看有没有可能找到更短的合法子串;

如果当前得到的子串不是合法子串,那么我们必须去延伸这个子串。【子串不合法,就说明「不替换的内容」中存在至少一个字符的出现次数大于n/4,我们就要通过加长子串,从而缩短「不替换的内容」,期望多的字符被加入子串,使得「不替换的内容」可能满足】

这样的搜索方式我们考虑使用一个滑动窗口[left, right]来维护搜索到的子串, 我们从left=right=0即第一个字母构成的子串开始搜索。

如果当前子串是一个合法子串,left右移缩短字符串;如果left已经到达字符串尾部,说明没有缩小的空间,那么就结束搜索。

如果当前子串不是一个合法子串,right右移延伸字符串;如果right已经到达字符串尾部,说明没有延伸的空间,那么就结束搜索。

因为我们在搜索过程中要时刻判断「不替换的内容」中四个字符的出现次数是否小于等于n/4,因此我们必须先遍历一遍整个字符串,得到字符的总共出现次数【初始化断「不替换的内容」中四个字符的出现次数,表示初始子串为空】,然后在滑动窗口的移动过程中,动态的增加或减少对应字符出现的次数,来维护「不替换的内容」中四个字符的出现次数。

下图显示了滑动窗口的一个搜索过程:

java 复制代码
class Solution {
    public int balancedString(String s) {
        HashMap<Character, Integer> map = new HashMap<>();
        int l = 0;
        int min = Integer.MAX_VALUE;
        if (s.length() % 4 != 0)
            return 0;
        int cnt = s.length() / 4;
        for (int r = 0; r < s.length(); r++) {
            char a = s.charAt(r);
            map.put(a, map.getOrDefault(a, 0) + 1);
        }
        if(check(map,cnt)) return 0;
        for (int r = 0; r < s.length(); r++) {
              // 不合法,移动右指针
                map.put(s.charAt(r),map.getOrDefault(s.charAt(r),0)-1);
            
            while (l<=r&&check(map, cnt)) {
                min=Math.min(min,r-l+1);
                map.put(s.charAt(l),map.getOrDefault(s.charAt(l),0)+1);
                l++;
            }
              
        }
        return min;
    }

    public boolean check(HashMap<Character, Integer> map, int cnt) {
        if (map.getOrDefault('W', 0) > cnt || map.getOrDefault('E', 0) > cnt || map.getOrDefault('Q', 0) > cnt
                || map.getOrDefault('R', 0) > cnt)
            return false;
        else
            return true;
    }
}
/**
我们需要找到一个最短的窗口 [l, r],把它替换后,使得剩余的部分都 ≤ n/4。

步骤:

统计每个字符的出现次数。

如果所有字符出现次数 ≤ n/4,直接返回 0。

否则使用 滑动窗口:

移动右指针 r,不断减少 map[s[r]]。

当当前剩余字符的数量都 ≤ n/4,说明 [l, r] 这段替换即可。

更新最小窗口长度,并尝试右移左指针 l。
 */
相关推荐
小欣加油2 小时前
leetcode 860 柠檬水找零
c++·算法·leetcode·职场和发展·贪心算法
还是码字踏实2 小时前
基础数据结构之数组的矩阵遍历:螺旋矩阵(LeetCode 54 中等题)
数据结构·leetcode·矩阵·螺旋矩阵
粉色挖掘机3 小时前
矩阵在密码学的应用——希尔密码详解
线性代数·算法·机器学习·密码学
七七七七073 小时前
【计算机网络】UDP协议深度解析:从报文结构到可靠性设计
服务器·网络·网络协议·计算机网络·算法·udp
TitosZhang3 小时前
排序算法稳定性判断
数据结构·算法·排序算法
一种乐趣4 小时前
PHP推荐权重算法以及分页
算法·php·推荐算法
ccLianLian4 小时前
计算机视觉·TagCLIP
人工智能·算法
千弥霜4 小时前
codeforces1997(div.3)E F
算法
周杰伦_Jay5 小时前
【Python后端API开发对比】FastAPI、主流框架Flask、Django REST Framework(DRF)及高性能框架Tornado
数据结构·人工智能·python·django·flask·fastapi·tornado