大家好,我是知识汲取者,欢迎来到我的LeetCode热题100刷题专栏!
精选 100 道力扣(LeetCode)上最热门的题目,适合初识算法与数据结构的新手和想要在短时间内高效提升的人,熟练掌握这 100 道题,你就已经具备了在代码世界通行的基本能力。在此专栏中,我们将会涵盖各种类型的算法题目,包括但不限于数组、链表、树、字典树、图、排序、搜索、动态规划等等,并会提供详细的解题思路以及Java代码实现。如果你也想刷题,不断提升自己,就请加入我们吧!QQ群号:827302436。我们共同监督打卡,一起学习,一起进步。
LeetCode热题100专栏:LeetCode热题100
Gitee地址:知识汲取者 (aghp) - Gitee.com
题目来源:LeetCode 热题 100 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台
PS:作者水平有限,如有错误或描述不当的地方,恳请及时告诉作者,作者将不胜感激
原题链接:76.最小覆盖子串
解法一:滑动窗口算法
滑动窗口算法也写了好多遍了,这里就不再多做解释上,直接上代码
PS:上面的动图来自LeetCode官方题解,我感觉特别好,就没有自己制作动图了,直接把它的录制下来了
滑动窗口主要思路:
Step1:定义窗口。定义左右指针,一个是窗口左边界,一个是窗口右边界
Step2:滑动窗口。
经过1.和2.不断迭代,最终让左右指针构造的窗口从左往右滑动起来,然后就能取得我们需要的极值
总的来说,滑动窗口思想还剩很简单的,核心是对于窗口条件扩充和缩小条件的判断
import java.util.HashMap;
import java.util.Map;
/**
* @author ghp
* @title 最小覆盖子串
*/
class Solution {
public String minWindow(String s, String t) {
if (s.length() < t.length()) {
// s的长度小于t,s不可能覆盖t
return "";
}
// map用于判断窗口是否可以缩小
Map<Character, Integer> map = new HashMap<>();
for (int k = 0; k < t.length(); k++) {
char key = t.charAt(k);
if (map.containsKey(key)) {
map.put(t.charAt(k), map.get(key) + 1);
} else {
map.put(t.charAt(k), 1);
}
}
// 存储窗口中的元素
Map<Character, Integer> contains = new HashMap<>();
for (int k = 0; k < t.length(); k++) {
contains.put(t.charAt(k), 0);
}
// 左右指针
int i = 0;
int j = 0;
// 记录窗口的最小长度
int min = Integer.MAX_VALUE;
// 记录窗口最小长度时的左右边界
int start = -1;
// 从做左到右滑动窗口
while (j < s.length()) {
if (contains.containsKey(s.charAt(j))) {
Character key = s.charAt(j);
Integer value = contains.get(key);
contains.put(key, value + 1);
}
while (isCover(map, contains)) {
// 窗口中的元素已经能够覆盖t,缩小窗口
if (min > (j - i + 1)) {
// 如果当前窗口的长度为最小长度,则更新start和end
min = j - i + 1;
start = i;
}
if (contains.containsKey(s.charAt(i))) {
// 移除左边界的元素
Character key = s.charAt(i);
Integer value = contains.get(key);
contains.put(key, value - 1);
}
// 滑动窗口左边界
i++;
}
// 滑动窗口右边界
j++;
}
// 返回最小窗口长度的字符串(这里要判断start是否是-1,防止索引越界)
return start == -1 ? "" : s.substring(start, start + min);
}
private boolean isCover(Map<Character, Integer> map, Map<Character, Integer> contains) {
// 遍历map,判断当前窗口中的元素是否覆盖t
for (Character key : map.keySet()) {
if (map.get(key) > contains.getOrDefault(key, 0)) {
// 如果窗口中没有map对应等待元素 或者 窗口中的元素数量不够
return false;
}
}
return true;
}
}
复杂度分析:
代码优化:
/**
* @author ghp
* @title 最小覆盖子串
*/
class Solution {
public String minWindow(String s, String t) {
if (s.length() < t.length()) {
return "";
}
int[] letter = new int[128];
// 记录t中字符出现的次数
for (int i = 0; i < t.length(); i++) {
letter[t.charAt(i)]++;
}
int l = 0; // 窗口左边界
int r = 0; // 窗口左右边界
int min = Integer.MAX_VALUE; // 记录当前能够覆盖t的最小窗口的长度
int start = -1; // 记录当前能够覆盖t的最小窗口的长度时的左边界
int count = t.length();
while (r < s.length()) {
char ch = s.charAt(r);
if (letter[ch] > 0) {
// 当前字符被t包含,count--
count--;
}
// 把右边的字符加入窗口
letter[ch]--;
if (count == 0) {
// 窗口中的字母已经覆盖t,判断窗口是否需要缩小
while (l < r && letter[s.charAt(l)] < 0) {
// 左侧元素已经超过了需要的次数,移除左侧元素
letter[s.charAt(l)]++;
l++;
}
if (min > r - l + 1) {
// 当前窗口中字符的长度为最小的,更新start和min
min = r - l + 1;
start = l;
}
// 移除左侧元素使窗口不能够覆盖t,重新开始循环
letter[s.charAt(l)]++;
l++;
count++;
}
r++;
}
return start == -1 ? "" : s.substring(start, start + min);
}
}
经过提交测试,平均耗时只有大约2ms(前面的代码平均耗时170ms),空间占用42MB(前面的代码占用43MB),毫无疑问这次优化是十分值得的(●’◡’●)
复杂度分析:
其中 n n n 为数组中元素的个数
原题链接:78.子集
解法一:BFS
import java.util.*;
/**
* @author ghp
* @title 最小覆盖子串
*/
class Solution {
private Deque<Integer> path = new LinkedList<>();
private Map<Integer, Integer> map;
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
ans.add(Collections.emptyList());
boolean[] vis = new boolean[nums.length];
bfs(ans, path, vis, nums, 0);
return ans;
}
private void bfs(List<List<Integer>> ans, Deque<Integer> path, boolean[] vis, int[] nums, int step) {
if (path.size() > nums.length) {
return;
}
for (int i = step; i < nums.length; i++) {
if (!vis[i]) {
path.addLast(nums[i]);
vis[i] = true;
ans.add(new ArrayList<>(path));
bfs(ans, path, vis, nums, i);
vis[i] = false;
path.removeLast();
}
}
}
}
复杂度分析
时间复杂度: O ( n ! ) O(n!) O(n!)
空间复杂度: O ( n ! + n ) O(n!+n) O(n!+n)
n为nums数组的元素个数