LeetCode 118, 52, 212


118. 杨辉三角

题目链接

118. 杨辉三角

标签

数组 动态规划

思路

alt text
本题可以使用 动态规划 的思想来解决,先将杨辉三角看成如上图形,则有:

  • 状态转移方程 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(r1,c1)+f(r1,c),其中 r , c r, c r,c 分别代表待计算数字的 行数列数,即第 r r r 行第 c c c 列第数字是 它的上一行左边的数字(第 r − 1 r - 1 r1 行第 c − 1 c - 1 c1 列第数字) 与 它的上一行正对的数字(第 r − 1 r - 1 r1 行第 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

题目链接

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

题目链接

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; // 保存该字符串
        }
    }
}
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值