文章目录
- 写于2019年6月2日-6月3日
76. 最小覆盖子串
① 题目描述
- 给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。
- 示例:
输入: S = “ADOBECODEBANC”, T = “ABC”
输出: “BANC”
- 说明:
如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。
② 暴力法(Time Limit Exceeded
)
- 从下标0开始,验证长度至少为
T.len
的S的子串:遍历T中的字符看其在子串中是否存在,若存在需要删除子串中的该字符,避免出现情况1;在最后更新result时,需要将result.len与原始的子串长度作比较,而不是直接sub.length(),避免出现情况2.
情况1:
Input: “bbaa"和"aba”
Output: “bba”
Expected: “baa”
情况2:
Input: “abc"和"ab”
Output: “abc”
Expected: “ab”
- 代码如下,可惜
Time Limit Exceeded
:
public String minWindow(String s, String t) {
int tlen = t.length();
int slen = s.length();
String minString = "";
for (int i = 0; i < slen; i++) {
for (int j = i + tlen - 1; j < slen; j++) {
String sub = s.substring(i, j + 1);
boolean flag = true;
for (int k = 0; k < tlen; k++) {
int index = sub.indexOf(t.charAt(k));
if (index == -1) {
flag = false;
break;
} else {
sub = sub.substring(0, index) + sub.substring(index + 1);
}
}
if (flag) {
if (minString.equals("")) {
minString = s.substring(i, j + 1);
} else if (minString.length() > j - i + 1) {
// 不能与当前的sub.len作比较,应该与初始时的sub.len作比较
minString = s.substring(i, j + 1);
}
}
}
}
return minString;
}
③ 滑动窗口(双指针)
- 算法思想如下:
① 初始,left指针和right指针都指向S的第一个元素。
② 将 right 指针右移,扩张窗口,直到得到一个可行窗口,该窗口包含T的全部字目。
③ 得到可行的窗口后,将left指针逐个右移压缩窗口(去除窗口前端多余字母
),若得到的窗口依然可行,则更新最小窗口大小。
④ 若窗口不再可行,则跳转至 ②。 - 使用
Hashmap
记录T中字母的个数,然后使用count进行计数,如果出现T中的有效字符count + 1
;不断移动right指针,直到count与T.len
相等。 - left指针右移压缩窗口,当前字母个数加1,如果出现当前字母的个数大于0,表示该字母是有效的,要让count减1。
- 代码如下:
public String minWindow(String s, String t) {
HashMap<Character, Integer> map = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
// 构造t的Hashmap
char ch = t.charAt(i);
map.put(ch, map.getOrDefault(ch, 0) + 1);
}
int left = 0;
int right = 0;
int count = 0;
String result = "";
while (right < s.length()) {
// 如果right=s.len,说明到最后一个字母时,没有找到最小覆盖子串,不用再找了
char ch1 = s.charAt(right);
// 当前的字母次数减一,一定得是字母在Hashmap中,即是t中的字母
if (map.containsKey(ch1)) {
map.put(ch1, map.get(ch1) - 1);
//代表当前符合了一个字母
if (map.get(ch1) >= 0) {
count++;
}
}
while (count == t.length()) {
// 只要count仍然为t.len,就可以尝试不断右移left压缩窗口
if (result.equals("")) {
result = s.substring(left, right + 1);
} else if (result.length() > (right - left + 1)) {
result = s.substring(left, right + 1);
}
char ch2 = s.charAt(left);
// 因为要把当前字母移除,所以相应次数要加 1
if (map.containsKey(ch2)) {
map.put(ch2, map.get(ch2) + 1);
if (map.get(ch2) > 0) {
//此时的 map[key] 大于 0 了,表示缺少当前字母了,count--
count--;
}
}
left++;
}
right++;
}
return result;
}
78. 子集
① 题目描述
- 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
- 说明:解集不能包含重复的子集。
- 示例:
输入: nums = [1,2,3]
输出: [
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
② 回溯法
- 从构造一个集合的指定个数的数的组合获取灵感,固定start,选取相应数目的元素,构成一个组合。
- 代码如下:
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
result.add(new ArrayList<>());
if (nums.length == 0) {
return result;
}
for (int i = 1; i <= nums.length; i++) {
List<Integer> item = new ArrayList<>();
backtrace(nums, i, item, result, 0);
}
return result;
}
public void backtrace(int[] nums, int len, List<Integer> item, List<List<Integer>> result, int start) {
if (item.size() == len) {
result.add(new ArrayList<>(item));
return;
}
for (int i = start; i < nums.length; i++) {
item.add(nums[i]);
backtrace(nums, len, item, result, i + 1);
item.remove(item.size() - 1);
}
}
③ 位操作
- 通过观察发现,N个数字生成的组合可以与N bit从0~ 2 N − 1 2^N - 1 2N−1一致。
1 2 3
0 0 0 -> [ ]
0 0 1 -> [ 1]
0 1 0 -> [ 2 ]
0 1 1 -> [ 2 1]
1 0 0 -> [3 ]
1 0 1 -> [3 1]
1 1 0 -> [3 2 ]
1 1 1 -> [3 2 1]
- 代码如下:
public List<List<Integer>> subsets(int[] nums) {
int times = (int) Math.pow(2, nums.length);
List<List<Integer>> result = new ArrayList<>();
for (int i = 0; i < times; i++) {
int temp = i;
List<Integer> list = new ArrayList<>();
int count = 0;// 记录要添加的是哪一位
while (temp != 0) {
if ((temp & 1) == 1) {
// 判断待添加位是否为1
list.add(nums[count]);
}
temp = temp >> 1; // 右移一位,继续判断下一个待添加位
count++;// 指向下一个要添加的位置
}
result.add(list);
}
return result;
}
79. 单词搜索
① 题目描述
- 给定一个二维网格和一个单词,找出该单词是否存在于网格中。
- 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
- 示例:
board =
[
['A','B','C','E']
,
['S','F','C','S']
,
['A','D','E','E']
]
给定 word = “ABCCED”, 返回 true.
给定 word = “SEE”, 返回 true.
给定 word = “ABCB”, 返回 false.
② 回溯+DFS
- DFS需要使用visited记录元素是否已经访问过,如果元素被访问过,或者元素不在word中,就返回false。
- 代码如下:
public boolean exist(char[][] board, String word) {
boolean[][] visited = new boolean[board.length][board[0].length];
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (word.charAt(0) == board[i][j] && backtrace(i, j, 0, word, board, visited)) {
return true;
}
}
}
return false;
}
public boolean backtrace(int i, int j, int cur, String word, char[][] bord, boolean[][] visited) {
if (cur == word.length()) {
return true;
}
if (i < 0 || j < 0 || i >= bord.length || j >= bord[0].length
|| visited[i][j] || word.charAt(cur) != bord[i][j]) {
return false;
}
visited[i][j] = true;
if (backtrace(i - 1, j, cur + 1, word, bord, visited) || backtrace(i + 1, j, cur + 1, word, bord, visited)
|| backtrace(i, j - 1, cur + 1, word, bord, visited) || backtrace(i, j + 1, cur + 1, word, bord, visited)) {
return true;
}
visited[i][j] = false;
return false;
}
84. 柱状图中最大的矩形
① 题目描述
- 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
- 求在该柱状图中,能够勾勒出来的矩形的最大面积。
- 以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
- 图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
- 示例:
输入: [2,1,5,6,2,3]
输出: 10
② 暴力法
- 从下标i开始,获得i到j 的最小高度作为矩形的高,将
(j - i + 1)
作为矩形的长,计算出当前矩形的面积,并更新max_area; - 代码如下,虽然运行时间很长,但是竟然没有超时!
public int largestRectangleArea(int[] heights) {
int area = 0;
for (int i = 0; i < heights.length; i++) {
int min = heights[i];
for (int j = i; j < heights.length; j++) {
if (min > heights[j]) {
min = heights[j];
}
int temp_area = (j - i + 1) * min;
if (area < (j - i + 1) * min) {
area = temp_area;
}
}
}
return area;
}
③ 使用Stack
- 首先我们定义我们的stack,用来储存的是对应矩形的下标。我们希望,这个stack里储存的下标所对应的高度是递增的。如果出现不递增的情况,则代表出现了断层,这时候我们就可以在这个断层处更新我们的最大面积。
- 特殊情况: 有人可能会问,如果这些矩形的高度是一直递增的呢?那不就不存在下降的断层嘛?没错,所以我们在heights的
末尾处加了一个0
,就是为了让面积能在最后结束结算。因此循环时,i的上限为heights.len
。 - 代码如下:
public int largestRectangleArea(int[] heights) {
int area = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i <= heights.length; i++) {
int h = (i == heights.length) ? 0 : heights[i];
if (stack.isEmpty() || h >= heights[stack.peek()]) {
stack.push(i);
} else {
int temp = stack.pop();
area = Math.max(area, heights[temp] * (stack.isEmpty() ? i : i - 1 - stack.peek()));
i--;
}
}
return area;
}