75. 颜色分类,76. 最小覆盖子串,77. 组合,每题做详细思路梳理,配套Python&Java双语代码, 2024.03.21 可通过leetcode所有测试用例。
目录
75. 颜色分类
给定一个包含红色、白色和蓝色、共
n
个元素的数组nums
,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数
0
、1
和2
分别表示红色、白色和蓝色。必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0] 输出:[0,0,1,1,2,2]示例 2:
输入:nums = [2,0,1] 输出:[0,1,2]提示:
n == nums.length
1 <= n <= 300
nums[i]
为0
、1
或2
解题思路
其核心思想是使用三个指针来分别跟踪 0、1 和 2。我们可以将数组中的元素分为三部分:红色区(0),白色区(1),以及蓝色区(2)。以下是具体的步骤:
-
初始化三个指针:
low
、mid
和high
。low
指针跟踪红色区的最后一个元素的下一个位置,mid
指针用于遍历整个数组,high
指针跟踪蓝色区的第一个元素的前一个位置。初始时,low
和mid
都设置为数组的起始位置,high
设置为数组的末尾位置。 -
遍历数组:当
mid
指针小于等于high
指针时,进行遍历:- 如果
nums[mid]
等于 0(红色),则将其与nums[low]
交换,并将low
和mid
指针都向前移动一位。 - 如果
nums[mid]
等于 1(白色),则不需要交换,只需将mid
指针向前移动一位。 - 如果
nums[mid]
等于 2(蓝色),则将其与nums[high]
交换,并将high
指针向后移动一位。此时不移动mid
指针,因为交换后的元素还未被检查。
- 如果
-
重复步骤 2,直到
mid
指针超过high
指针。
完整代码
Python
class Solution:
def sortColors(self, nums: List[int]) -> None:
low, mid, high = 0, 0, len(nums) - 1
while mid <= high:
if nums[mid] == 0:
nums[low], nums[mid] = nums[mid], nums[low]
low += 1
mid += 1
elif nums[mid] == 1:
mid += 1
else:
nums[mid], nums[high] = nums[high], nums[mid]
high -= 1
Java
class Solution {
public void sortColors(int[] nums) {
int low = 0, mid = 0, high = nums.length - 1;
while (mid <= high) {
switch (nums[mid]) {
case 0:
// 交换 low 和 mid 的值
int temp = nums[low];
nums[low] = nums[mid];
nums[mid] = temp;
low++;
mid++;
break;
case 1:
mid++;
break;
case 2:
// 交换 mid 和 high 的值
temp = nums[mid];
nums[mid] = nums[high];
nums[high] = temp;
high--;
break;
}
}
}
}
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 的子串中, 因此没有符合条件的子字符串,返回空字符串。
解题思路
我们可以使用滑动窗口的方法。基本思想是使用两个指针 left
和 right
来表示 s 中的一个窗口,并在这个窗口中寻找包含 t 所有字符的最小子串。这个过程分为几个步骤:
-
初始化:创建两个哈希表,一个用于存储字符串 t 中字符的频率(称为
need
),另一个用于存储当前窗口中字符的频率(称为window
)。还需要两个指针left
和right
表示当前窗口的左右边界,初始化为 0。定义valid
变量来记录窗口中满足need
条件的字符数量。 -
扩大窗口:移动右指针
right
扩大窗口,直到窗口中包含了 t 的所有字符。 -
更新结果:当窗口包含了 t 的所有字符后,尝试移动左指针
left
缩小窗口,同时更新最小子串的长度和起始位置。如果当前窗口的长度小于之前找到的最小子串长度,更新最小子串长度和起始位置。 -
缩小窗口:继续移动左指针
left
缩小窗口,直到窗口不再满足 t 的所有字符。此时再次扩大窗口,重复步骤 2 和 3。 -
返回结果:根据记录的最小子串起始位置和长度返回最终结果。
完整代码
Python
class Solution:
def minWindow(self, s: str, t: str) -> str:
from collections import Counter
need, window = Counter(t), {}
left = right = 0
valid = 0
start, length = 0, float('inf')
while right < len(s):
# c 是将移入窗口的字符
c = s[right]
# 右移窗口
right += 1
# 进行窗口内数据的一系列更新
if c in need:
window[c] = window.get(c, 0) + 1
if window[c] == need[c]:
valid += 1
# 判断左侧窗口是否要收缩
while valid == len(need):
# 更新最小覆盖子串
if right - left < length:
start = left
length = right - left
# d 是将移出窗口的字符
d = s[left]
# 左移窗口
left += 1
# 进行窗口内数据的一系列更新
if d in need:
if window[d] == need[d]:
valid -= 1
window[d] -= 1
return "" if length == float('inf') else s[start:start+length]
Java
class Solution {
public String minWindow(String s, String t) {
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for (char c : t.toCharArray()) need.put(c, need.getOrDefault(c, 0) + 1);
int left = 0, right = 0;
int valid = 0;
int start = 0, length = Integer.MAX_VALUE;
while (right < s.length()) {
char c = s.charAt(right);
right++;
if (need.containsKey(c)) {
window.put(c, window.getOrDefault(c, 0) + 1);
if (window.get(c).equals(need.get(c))) valid++;
}
while (valid == need.size()) {
if (right - left < length) {
start = left;
length = right - left;
}
char d = s.charAt(left);
left++;
if (need.containsKey(d)) {
if (window.get(d).equals(need.get(d))) valid--;
window.put(d, window.get(d) - 1);
}
}
}
return length == Integer.MAX_VALUE ? "" : s.substring(start, start + length);
}
}
77. 组合
给定两个整数
n
和k
,返回范围[1, n]
中所有可能的k
个数的组合。你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]示例 2:
输入:n = 1, k = 1 输出:[[1]]
解题思路
我们可以使用回溯法,这是一种通过探索所有可能的候选解来找出所有解的解决方案的算法。当搜索到一个可能解时,回溯法会先记录下这个解,然后撤销到上一步状态,尝试另一种选择,继续探索直到所有的选择都尝试过了。以下是具体的步骤:
-
定义回溯函数:创建一个回溯函数
backtrack
,它接受当前的组合combination
和下一个要添加到组合中的数字start
。 -
递归的终止条件:当
combination
的长度等于k
时,将其添加到结果列表中,然后返回。 -
遍历所有可能的选择:从
start
到n
遍历所有可能的数字。对于每一个数字,将其添加到当前组合中,然后递归地调用backtrack
函数进入下一层。 -
回溯:递归调用返回后,撤销上一步的操作,即从
combination
中移除上一步添加的数字,尝试下一个可能的数字。 -
调用回溯函数并返回结果:初始化一个空列表来存储所有可能的组合,然后从数字
1
开始调用回溯函数。
完整代码
Python
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
result = []
combination = []
def backtrack(start):
if len(combination) == k:
result.append(combination[:])
return
for i in range(start, n + 1):
combination.append(i)
backtrack(i + 1)
combination.pop()
backtrack(1)
return result
Java
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> combination = new ArrayList<>();
void backtrack(int start) {
if (combination.size() == k) {
result.add(new ArrayList<>(combination));
return;
}
for (int i = start; i <= n; i++) {
combination.add(i);
backtrack(i + 1);
combination.remove(combination.size() - 1);
}
}
backtrack(1);
return result;
}
}