139. 单词拆分
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"] 输出: true 解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"] 输出: true 解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。 注意,你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] 输出: false
解法:动态规划+哈希
dp[0] = true
:空字符串默认可拆分。对于边界条件,定义 dp[0]=true 表示空串且合法。
假设输入:
s = "leetcode"
wordDict = ["leet", "code"]
dp数组填充过程:
初始化:dp = [T, F, F, F, F, F, F, F, F]
(长度9)
i=1-3:没有匹配的子串
i=4:
j=0:检查"leet"在字典中且dp[0]=true → dp[4]=true
i=8:
j=4:检查"code"在字典中且dp[4]=true → dp[8]=true
最终dp[8]=true,返回true
public class Solution {
public boolean wordBreak(String s, List wordDict) {
Set wordDictSet = new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
815. 公交路线
给你一个数组 routes
,表示一系列公交线路,其中每个 routes[i]
表示一条公交线路,第 i
辆公交车将会在上面循环行驶。
routes[0] = [1, 5, 7]
表示第 0
辆公交车会一直按序列 1 -> 5 -> 7 -> 1 -> 5 -> 7 -> 1 -> ...
这样的车站路线行驶。现在从 source
车站出发(初始时不在公交车上),要前往 target
车站。 期间仅可乘坐公交车。
求出 最少乘坐的公交车数量 。如果不可能到达终点车站,返回 -1
。
示例 1:
输入:routes = [[1,2,7],[3,6,7]], source = 1, target = 6 输出:2 解释:最优策略是先乘坐第一辆公交车到达车站 7 , 然后换乘第二辆公交车到车站 6 。
示例 2:
输入:routes = [[7,12],[4,5,15],[6],[15,19],[9,12,13]], source = 15, target = 12 输出:-1
class Solution {
int s, t;
int[][] rs;
public int numBusesToDestination(int[][] _rs, int _s, int _t) {
rs = _rs; s = _s; t = _t;
if (s == t) return 0;
int ans = bfs();
return ans;
}
int bfs() {
// 记录某个车站可以进入的路线
Map> map = new HashMap<>();
// 队列存的是经过的路线
Deque d = new ArrayDeque<>();
// 哈希表记录的进入该路线所使用的距离
Map m = new HashMap<>();
int n = rs.length;
for (int i = 0; i < n; i++) {
for (int station : rs[i]) {
// 将从起点可以进入的路线加入队列
if (station == s) {
d.addLast(i);
m.put(i, 1);
}
Set set = map.getOrDefault(station, new HashSet<>());
set.add(i);
map.put(station, set);
}
}
while (!d.isEmpty()) {
// 取出当前所在的路线,与进入该路线所花费的距离
int poll = d.pollFirst();
int step = m.get(poll);
// 遍历该路线所包含的车站
for (int station : rs[poll]) {
// 如果包含终点,返回进入该路线花费的距离即可
if (station == t) return step;
// 将由该线路的车站发起的路线,加入队列
Set lines = map.get(station);
if (lines == null) continue;
for (int nr : lines) {
if (!m.containsKey(nr)) {
m.put(nr, step + 1);
d.add(nr);
}
}
}
}
return -1;
}
}
130. 被围绕的区域
给你一个 m x n
的矩阵 board
,由若干字符 'X'
和 'O'
组成,捕获 所有 被围绕的区域:
'O'
的单元格来形成一个区域。'X'
单元格 连接这个区域,并且区域中没有任何单元格位于 board
边缘,则该区域被 'X'
单元格围绕。通过 原地 将输入矩阵中的所有 'O'
替换为 'X'
来 捕获被围绕的区域。你不需要返回任何值。
示例 1:
输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
解释:
在上图中,底部的区域没有被捕获,因为它在 board 的边缘并且不能被围绕。
示例 2:
输入:board = [["X"]]
输出:[["X"]]
解法一:DFS递归
class Solution {
public void solve(char[][] board) {
if (board == null || board.length == 0) return;
int m = board.length;
int n = board[0].length;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 从边缘o开始搜索
boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1;
if (isEdge && board[i][j] == 'O') {
dfs(board, i, j);
}
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == 'O') {
board[i][j] = 'X';
}
if (board[i][j] == '#') {
board[i][j] = 'O';
}
}
}
}
public void dfs(char[][] board, int i, int j) {
if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] == 'X' || board[i][j] == '#') {
// board[i][j] == '#' 说明已经搜索过了.
return;
}
board[i][j] = '#';
dfs(board, i - 1, j); // 上
dfs(board, i + 1, j); // 下
dfs(board, i, j - 1); // 左
dfs(board, i, j + 1); // 右
}
}
解法二:BFS(并查集)
public void solve(char[][] board) {
if (board == null || board.length == 0)
return;
int rows = board.length;
int cols = board[0].length;
// 用一个虚拟节点, 边界上的O 的父节点都是这个虚拟节点
UnionFind uf = new UnionFind(rows * cols + 1);
int dummyNode = rows * cols;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (board[i][j] == 'O') {
// 遇到O进行并查集操作合并
if (i == 0 || i == rows - 1 || j == 0 || j == cols - 1) {
// 边界上的O,把它和dummyNode 合并成一个连通区域.
uf.union(node(i, j), dummyNode);
} else {
// 和上下左右合并成一个连通区域.
if (i > 0 && board[i - 1][j] == 'O')
uf.union(node(i, j), node(i - 1, j));
if (i < rows - 1 && board[i + 1][j] == 'O')
uf.union(node(i, j), node(i + 1, j));
if (j > 0 && board[i][j - 1] == 'O')
uf.union(node(i, j), node(i, j - 1));
if (j < cols - 1 && board[i][j + 1] == 'O')
uf.union(node(i, j), node(i, j + 1));
}
}
}
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (uf.isConnected(node(i, j), dummyNode)) {
// 和dummyNode 在一个连通区域的,那么就是O;
board[i][j] = 'O';
} else {
board[i][j] = 'X';
}
}
}
}
int node(int i, int j) {
return i * cols + j;
}
}