目录
中等
给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
示例 1:
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
示例 2:
输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。
提示:
-
1 <= s.length, p.length <= 3 * 104
-
s
和p
仅包含小写字母
//滑动窗口 + 哈希表
//时间复杂度:O (n),其中 n 是字符串 s 的长度。每个字符最多被访问两次(右指针和左指针各一次)。
//空间复杂度:O (k),其中 k 是字符串 t 中不同字符的个数,主要用于存储哈希表。
class Solution {
public:
vector<int> findAnagrams(string s, string t) {
unordered_map<char, int> need, window;
// 统计目标字符串t中各字符的频率。
// 作为参考标准,用于判断窗口是否包含 t 的所有字符。
for (char c : t) need[c]++;
// 滑动窗口的左右边界
int left = 0, right = 0;
// 记录窗口中满足need条件的字符个数
int valid = 0;
// 存储结果的数组
vector<int> res;
while (right < s.size()) {
char c = s[right];
// 右边界右移,扩大窗口
right++;
// 进行窗口内数据的一系列更新
if (need.count(c)) {
window[c]++;
// 当窗口中字符c的数量与need中相同时
if (window[c] == need[c])
valid++;
}
// 判断左侧窗口是否要收缩
while (right - left >= t.size()) {
// 当窗口中有效字符数等于need的大小时,说明找到一个异位词
if (valid == need.size())
// 记录起始索引
res.push_back(left);
char d = s[left];
// 左边界右移,缩小窗口
left++;
// 若移除的字符在need中,更新window哈希表
if (need.count(d)) {
// 若移除前字符d的数量刚好满足条件
if (window[d] == need[d])
// 有效字符数减1
valid--;
// 窗口中字符d的数量减1
window[d]--;
}
}
}
return res;
}
};
-
哈希表统计字符频率:用
need
哈希表统计目标字符串t
中每个字符的出现次数,作为参考标准。 -
动态维护窗口:通过双指针
left
和right
维护一个固定大小的滑动窗口(窗口大小等于t
的长度),遍历字符串s
。-
窗口扩张阶段:
-
右指针
right
不断右移,将字符加入窗口。 -
若当前字符
c
在need
中,更新window[c]
,并检查是否达到need[c]
的数量。若相等,valid
加 1。
-
-
窗口收缩阶段:
-
当窗口大小达到
t.size()
时,开始收缩左侧窗口。 -
若当前窗口满足
valid == need.size()
,说明窗口是t
的异位词,记录起始索引left
。 -
收缩窗口时,移除左侧字符
d
,若d
在need
中,更新window[d]
,并检查是否影响valid
。
-
-
动态维护
valid
:-
仅当
window[c]
从小于need[c]
变为等于时,valid
加 1。 -
仅当
window[d]
从等于need[d]
变为小于时,valid
减 1。
-
-
-
有效性检查:使用计数器
valid
记录当前窗口中满足window[c] == need[c]
的字符数量。当valid
等于need.size()
时,说明窗口中的字符恰好是t
的一个异位词。
图论
200. 岛屿数量 中等
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
提示:
-
m == grid.length
-
n == grid[i].length
-
1 <= m, n <= 300
-
grid[i][j]
的值为'0'
或'1'
//DFS
//时间复杂度:O (n×m),其中 n 和 m 分别是网格的行数和列数。每个单元格最多被访问一次。
//空间复杂度:O (n×m)(最坏情况),递归调用栈的深度可能达到整个网格的大小(当所有单元格都是'1'时)。
class Solution {
public:
// DFS函数:将当前岛屿的所有'1'标记为'2'(已访问)
void dfs(vector<vector<char>>& grid, int i, int j) {
if (!judgement(grid, i, j)) return; // 越界检查
if (grid[i][j] == '1') {
grid[i][j] = '2'; // 标记为已访问
dfs(grid, i-1, j); // 上
dfs(grid, i+1, j); // 下
dfs(grid, i, j-1); // 左
dfs(grid, i, j+1); // 右
}
}
// 判断坐标(i,j)是否在网格内,防止越界
bool judgement(vector<vector<char>>& grid, int i, int j) {
int n = grid.size();
int m = grid[0].size();
if (i >= n || j >= m) return false; // 超出右/下边界
else if (i < 0 || j < 0) return false; // 超出左/上边界
else return true;
}
// 主函数:计算岛屿数量
int numIslands(vector<vector<char>>& grid) {
int ans = 0;
int n = grid.size();//行数
int m = grid[0].size();//列数
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (grid[i][j] == '1') {
ans++; // 发现新岛屿
dfs(grid, i, j); // 标记该岛屿的所有'1'
}
}
}
return ans;
}
};
核心思路
-
问题转化:将二维网格视为一个图,每个
'1'
表示陆地(节点),相邻的'1'
之间有边相连。岛屿是由相邻的'1'
组成的连通块。 -
DFS 遍历:遍历每个网格单元格,当遇到
'1'
时,将其标记为已访问(修改为'2'
),并递归访问其上下左右四个方向的相邻单元格,将属于同一岛屿的所有'1'
都标记为已访问。 -
计数:每启动一次 DFS,就意味着发现一个新岛屿,计数器
ans
加 1。