118. 杨辉三角
题目链接
标签
数组 动态规划
思路
本题可以使用 动态规划 的思想来解决,先将杨辉三角看成如上图形,则有:
- 状态转移方程: f ( r , c ) = f ( r − 1 , c − 1 ) + f ( r − 1 , c ) f(r, c) = f(r - 1, c - 1) + f(r - 1, c) f(r,c)=f(r−1,c−1)+f(r−1,c),其中 r , c r, c r,c 分别代表待计算数字的 行数 和 列数,即第 r r r 行第 c c c 列第数字是 它的上一行左边的数字(第 r − 1 r - 1 r−1 行第 c − 1 c - 1 c−1 列第数字) 与 它的上一行正对的数字(第 r − 1 r - 1 r−1 行第 c c c 列第数字) 之和。
- 初始状态:可以发现,每行的左、右两边的数字都是
1
,无法被计算出来,所以每行的左、右两边的数字就是初始状态,值为1
。
代码
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> res = new ArrayList<>(numRows);
for (int r = 0; r < numRows; r++) { // 第 r 行,r 从 0 开始
Integer[] row = new Integer[r + 1]; // 第 r 行有 r + 1 个数字
row[0] = row[r] = 1; // 对初始状态赋值
for (int c = 1; c < r; c++) { // 对 剩余数字 使用 状态转移方程 进行赋值
row[c] = res.get(r - 1).get(c - 1) + res.get(r - 1).get(c);
}
res.add(Arrays.asList(row)); // 将这一行的数字作为 List 放入结果集合中
}
return res;
}
}
52. N 皇后 II
题目链接
标签
回溯
思路
本题和 51. N 皇后 题很像,唯一的区别就是本题求的是 解决方案数,而 51 题求的是 具体的解决方案,显然本题的处理更简单一点。
提醒一下要点,其余细节就不作赘述了:
- 对每一行,枚举每一列来放置皇后,此时要保证 每列、每左斜线、每右斜线 上只有一个皇后。
- 本题使用了基础的 回溯 思路,模版如下:
- 判断是否遍历完成,如果遍历完成,则记录结果,并退出遍历。
- 枚举可能的所有值,进行如下操作:
- 判断这个值是否可用,即 没有出边界 且 没有遍历过,如果不可用,则跳过它。
- 标记当前值遍历过,如果需要,则将当前值放入栈中。
- 遍历下一个值。
- 取消遍历过的标记,如果需要,则将当前值从栈中弹出。
代码
class Solution {
public int totalNQueens(int n) {
// 初始化本对象的属性
this.n = n;
this.col = new boolean[n];
this.lSlash = new boolean[2 * n - 1];
this.rSlash = new boolean[2 * n - 1];
this.table = new char[n][n];
for (char[] row : table) { // 将棋盘的每行都填充为 '.'
Arrays.fill(row, '.');
}
dfs(0);
return res;
}
private int res; // 存储不同的解决方案数
private char[][] table; // 棋盘
private int n; // 棋盘的 行数 和 列数
private boolean[] col; // 判断 某一列 是否有皇后
private boolean[] lSlash; // 判断 某一左斜线(左上 到 右下) 是否有皇后
private boolean[] rSlash; // 判断 某一右斜线(右上 到 左下) 是否有皇后
// 在索引为 i 的行上,放置第 i+1 个皇后
private void dfs(int i) {
if (i == n) { // 如果已经放置够了 n 个皇后
res++; // 则让结果数加一
return; // 并直接返回
}
for (int j = 0; j < n; j++) { // 枚举所有的 列
int ls = n - (i - j) - 1; // 当前行列对应的 左斜线的编号
int rs = i + j; // 当前行列对应的 右斜线的编号
// 如果 这列、左斜线、右斜线 之一被占,则取消本次放置
if (col[j] || lSlash[ls] || rSlash[rs]) {
continue;
}
table[i][j] = 'Q'; // 将皇后放到棋盘的当前位置上
col[j] = lSlash[ls] = rSlash[rs] = true; // 标记皇后在这一列、这一左斜线、这一右斜线上
dfs(i + 1); // 放置下一个皇后
col[j] = lSlash[ls] = rSlash[rs] = false; // 取消标记
table[i][j] = '.'; // 将当前皇后从棋盘上移除
}
}
}
212. 单词搜索 II
题目链接
标签
字典树 数组 字符串 回溯 矩阵
思路
本题的困难实际上是把两个中等题堆在一起了,这两个中等题如下所示:
- 79. 单词搜索:深度优先搜索 + 使用方向数组 + 通过改变
board
中的字符避免使用boolean[][] vis
二维数组。 - 208. 实现 Trie (前缀树):构建前缀树(本题中的前缀树和 208 题有些区别,没有使用 节点
Node
作为内部类,而是直接将Trie
看作节点,此外,没有实现search, startsWith
方法,因为没有必要)。
这道题介绍了 前缀树 的一个使用场景:以多个字符串作为模板,在字符串中搜索与模板相同的子字符串。所以不能像 79 题一样,只对一个字符串进行搜索,而是要 建立前缀树,沿着前缀树在 board
中搜索指定的字符。
在看完 79 题和 208 题之后即可看代码了,由于有注释,所以并不是很难。
代码
class Solution {
public List<String> findWords(char[][] board, String[] words) {
// 初始化成员变量
this.board = board;
this.ROW = board.length;
this.COL = board[0].length;
// 将所有的单词添加到前缀树中
Trie root = new Trie(); // 前缀树的根节点
for (String word : words) {
root.insert(word);
}
for (int r = 0; r < ROW; r++) {
for (int c = 0; c < COL; c++) {
Trie next = root.children[board[r][c] - 'a']; // 获取 (r, c) 指向的方格的节点
if (next != null) { // 如果 (r, c) 指向的方格的节点不为 null
dfs(next, r, c); // 则遍历 (r, c) 指向的字符
}
}
}
return new ArrayList<>(res);
}
private char[][] board; // 二维网格
private Set<String> res = new HashSet<>(); // 存储结果的集合,使用 Set 去重
private int ROW; // 总行数
private int COL; // 总列数
// 方向数组,分别为 向右、向下、向左、向上
private static final int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
private void dfs(Trie curr, int r, int c) {
if (curr.word != null) { // 如果 本节点 保存了单词
res.add(curr.word); // 则储存它
}
char ch = board[r][c]; // 记录本次遍历的字符
board[r][c] = '\0'; // 将本次遍历的字符修改为 '\0',防止重复遍历
for (int[] dir : dirs) { // 枚举四个方向,进行搜索
int kr = r + dir[0], kc = c + dir[1]; // 获取待搜索的方格 行数 和 列数
if (!(kr >= 0 && kr < ROW && kc >= 0 && kc < COL) // 如果 (kr, kc) 不在网格中
|| board[kr][kc] == '\0') { // 或者标记 (kr, kc) 指向的方格已被遍历
continue; // 则跳过 (kr, kc) 指向的字符
}
Trie next = curr.children[board[kr][kc] - 'a']; // 获取 (kr, kc) 指向的字符对应的节点
if (next != null) { // 如果 (kr, kc) 指向的字符对应的节点不为 null
dfs(next, kr, kc); // 则遍历 (kr, kc) 指向的字符
}
}
board[r][c] = ch; // 将本次遍历的字符恢复成原先的状态
}
static class Trie { // 前缀树的节点
String word; // 如果本节点是单词的结尾字符,则储存整个单词
Trie [] children = new Trie[26]; // 存储子节点
public void insert(String word) {
char[] str = word.toCharArray();
Trie curr = this;
for (char ch : str){
int idx = ch - 'a'; // 获取在指针数组中的索引
if (curr.children[idx] == null) { // 如果这个位置没有字符节点
curr.children[idx] = new Trie(); // 则新建一个字符节点
}
curr = curr.children[idx]; // 往下一个字符节点移动
}
curr.word = word; // 保存该字符串
}
}
}