系列文章目录
力扣热题 100:哈希专题三道题详细解析(JAVA)
力扣热题 100:双指针专题四道题详细解析(JAVA)
力扣热题 100:滑动窗口专题两道题详细解析(JAVA)
力扣热题 100:子串专题三道题详细解析(JAVA)
力扣热题 100:普通数组专题五道题详细解析(JAVA)
力扣热题 100:矩阵专题四道题详细解析(JAVA)
力扣热题 100:链表专题经典题解析(前7道)
力扣热题 100:链表专题经典题解析(后7道)
力扣热题 100:二叉树专题经典题解析(前8道)
力扣热题 100:二叉树专题进阶题解析(后7道)
力扣热题 100:图论专题经典题解析
力扣热题 100:回溯专题经典题解析
力扣热题 100:二分查找专题经典题解析
力扣热题 100:栈专题经典题解析
力扣热题 100:堆专题经典题解析
力扣热题 100:贪心算法专题经典题解析
力扣热题 100:动态规划专题经典题解析
力扣热题 100:多维动态规划专题经典题解析
力扣热题 100:技巧专题经典题解析
文章目录
在力扣(LeetCode)平台上,热题 100 是许多开发者提升算法能力的必刷清单。今天,我们就来详细解析热题 100 中与子串相关的三道题,帮助大家更好地理解解题思路和技巧。
一、和为 K 的子数组
1. 题目描述
给定一个整数数组 nums
和一个整数 k
,找到和为 k
的子数组的个数。
2. 示例
示例 1:
输入:nums = [1, 1, 1], k = 2
输出:2
解释:和为 2 的子数组有 [1, 1]
和 [1, 1]
。
3. 解题思路
这道题主要考察前缀和和哈希表的应用。我们可以使用前缀和来计算子数组的和,使用哈希表来存储前缀和出现的次数。具体步骤如下:
- 初始化前缀和
sum
为 0,哈希表map
用于存储前缀和出现的次数,初始时map[0] = 1
。 - 遍历数组,对于每个元素
nums[i]
,更新前缀和sum
。 - 检查
sum - k
是否在哈希表中存在,如果存在,则说明存在和为k
的子数组,更新结果计数。 - 更新哈希表中
sum
的出现次数。
4. 代码实现(Java)
import java.util.HashMap;
public class Solution {
public int subarraySum(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
int sum = 0, count = 0;
for (int num : nums) {
sum += num;
if (map.containsKey(sum - k)) {
count += map.get(sum - k);
}
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
return count;
}
}
5. 复杂度分析
- 时间复杂度 :O(n),其中 n 是数组的长度。我们只需要遍历数组一次,每次哈希表的查找和插入操作都是 O(1)。
- 空间复杂度 :O(n),需要使用哈希表存储前缀和及其出现次数。
二、滑动窗口最大值
1. 题目描述
给定一个数组 nums
和一个滑动窗口的大小 k
,找到每个滑动窗口中的最大值。
2. 示例
示例 1:
输入:nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
输出:[3, 3, 5, 5, 6, 7]
解释:滑动窗口中的最大值分别为 3、3、5、5、6、7。
3. 解题思路
这道题主要考察双端队列的应用。我们可以使用双端队列来维护滑动窗口中的最大值。具体步骤如下:
- 初始化双端队列
deque
,用于存储数组元素的索引。 - 遍历数组,对于每个元素
nums[i]
:- 移除双端队列中索引小于
i - k + 1
的元素,因为这些元素已经不在滑动窗口中。 - 移除双端队列中值小于
nums[i]
的元素,因为这些元素不可能是滑动窗口中的最大值。 - 将当前元素的索引
i
添加到双端队列的末尾。 - 如果滑动窗口已经形成(即
i >= k - 1
),记录当前滑动窗口的最大值nums[deque[0]]
。
- 移除双端队列中索引小于
4. 代码实现(Java)
import java.util.ArrayDeque;
import java.util.Deque;
public class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> deque = new ArrayDeque<>();
int[] result = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
while (!deque.isEmpty() && deque.peek() < i - k + 1) {
deque.poll();
}
while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
deque.pollLast();
}
deque.offer(i);
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peek()];
}
}
return result;
}
}
5. 复杂度分析
- 时间复杂度 :O(n),其中 n 是数组的长度。我们只需要遍历数组一次,每次双端队列的操作都是 O(1)。
- 空间复杂度 :O(k),需要使用双端队列存储滑动窗口中的元素索引。
三、最小覆盖子串
1. 题目描述
给定两个字符串 s
和 t
,找到 s
中包含 t
中所有字符的最小子串。
2. 示例
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小子串是 "BANC"
,包含 t
中的所有字符。
3. 解题思路
这道题主要考察滑动窗口的应用。我们可以使用滑动窗口来维护一个包含 t
中所有字符的子串。具体步骤如下:
- 初始化两个指针
left
和right
,分别表示滑动窗口的左右边界。 - 使用两个数组
window
和target
来存储当前窗口内的字符计数和目标字符串的字符计数。 - 遍历字符串
s
,对于每个字符s[right]
:- 更新当前窗口内的字符计数
window
。 - 如果当前窗口包含
t
中的所有字符,尝试移动left
指针来缩小窗口大小。 - 记录最小窗口的起始索引和长度。
- 更新当前窗口内的字符计数
- 重复步骤 3,直到
right
遍历完整个字符串。
4. 代码实现(Java)
public class Solution {
public String minWindow(String s, String t) {
int[] window = new int[128];
int[] target = new int[128];
for (char c : t.toCharArray()) {
target[c]++;
}
int left = 0, right = 0, formed = 0, minLen = Integer.MAX_VALUE, minStart = 0;
int required = t.length();
while (right < s.length()) {
char c = s.charAt(right);
window[c]++;
if (target[c] > 0 && window[c] == target[c]) {
formed++;
}
while (left <= right && formed == required) {
c = s.charAt(left);
if (right - left + 1 < minLen) {
minLen = right - left + 1;
minStart = left;
}
window[c]--;
if (target[c] > 0 && window[c] < target[c]) {
formed--;
}
left++;
}
right++;
}
return minLen == Integer.MAX_VALUE ? "" : s.substring(minStart, minStart + minLen);
}
}
5. 复杂度分析
- 时间复杂度 :O(n),其中 n 是字符串
s
的长度。我们只需要遍历字符串一次,每次移动指针的时间复杂度都是 O(1)。 - 空间复杂度 :O(1),我们只使用了固定大小的数组来存储字符计数。
以上就是力扣热题 100 中与子串相关的三道题的详细解析,希望对大家有所帮助。在实际刷题过程中,建议大家多动手实践,理解解题思路的本质,这样才能更好地应对各种算法问题。