钥匙和房间
- 题目。有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,…,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。
在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i],每个钥匙 rooms[i][j] 由 [0,1,…,N-1] 中的一个整数表示,其中 N = rooms.length。 钥匙 rooms[i][j] = v 可以打开编号为 v 的房间。
最初,除 0 号房间外的其余所有房间都被锁住。
你可以自由地在房间之间来回走动。
如果能进入每个房间返回 true,否则返回 false。
示例 1:
输入: [[1],[2],[3],[]]
输出: true
解释:
我们从 0 号房间开始,拿到钥匙 1。
之后我们去 1 号房间,拿到钥匙 2。
然后我们去 2 号房间,拿到钥匙 3。
最后我们去了 3 号房间。
由于我们能够进入每个房间,我们返回 true。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/keys-and-rooms
- 思路。有向图的遍历问题。可以将每个房间抽象成图中顶点,将每个房间能到达的房间抽象成边。如果从顶点0出发可以到达所有的顶点,则返回true,否则返回false。
- 代码。
class Solution {
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
// 图的遍历,当遍历完所有房间返回true,如果中途变量不下去了返回false
int r = rooms.size();
boolean[] visited = new boolean[r];
Queue<Integer> queue = new LinkedList<>();
queue.offer(0);
while (!queue.isEmpty()) {
int cur = queue.poll();
if (visited[cur]) {
continue;
}
visited[cur] = true;
List<Integer> edges = rooms.get(cur);
if (edges != null && edges.size() > 0) {
for (int neighbor : edges) {
queue.offer(neighbor);
}
}
}
for (int i = 0; i < visited.length; i++) {
if (!visited[i]) {
return false;
}
}
return true;
}
}
预测赢家
- 问题。给定一个表示分数的非负整数数组。 玩家 1 从数组任意一端拿取一个分数,随后玩家 2 继续从剩余数组任意一端拿取分数,然后玩家 1 拿,…… 。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。
给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。
示例 1:
输入:[1, 5, 2]
输出:False
解释:一开始,玩家1可以从1和2中进行选择。
如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。
所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 2 为 5 。
因此,玩家 1 永远不会成为赢家,返回 False 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/predict-the-winner - 思路1。先给个暴力递归的解法。假设我们有两个方法,一个是int pickFirst(int[] nums, int i, int j),代表从nums i到j先拿可以得到的最大分数;另一个方法是int pickLast(int[] nums, int i, int j),代表从nums i到j后拿可以得到的最大分数,所以返回pickFirst(nums, 0, nums.length - 1) >= pickLast(nums, 0, nums.length - 1) 即可。
- 代码1。
public boolean PredictTheWinner(int[] nums) {
return pickFirst(nums, 0, nums.length - 1) >= pickLast(nums, 0, nums.length - 1);
}
private int pickFirst(int[] nums, int i, int j) {
// 从i到j先选,i==j表示只剩一个了,则先选的人可以拿个这个数
if (i == j) {
return nums[i];
}
// 选i然后在i+1到j后选,或者选j然后在i到j-1后选,取二者较大的值
return Math.max(nums[i] + pickLast(nums, i + 1, j), nums[j] + pickLast(nums, i, j - 1));
}
private int pickLast(int[] nums, int i, int j) {
// 从i到j后选,i==j表示只剩一个了,则后选的人拿不到分数
if (i == j) {
return 0;
}
// 后选的人只能得到从i+1到j先选和从i到j-1先选较小的分数
return Math.min(pickFirst(nums, i + 1, j), pickFirst(nums, i, j - 1));
}
- 思路2。上面的暴力递归的解法存在重复计算的问题,可以改为动态规划。我们使用dp[i][j]表示可选nums[i…j]时,当前操作的选手(注意不一定是第一位选手)与另一位选手的最大分差。这里需要满足i<j并且dp[i][i]=nums[i]。我们在求dp[i][j]时, 如果选nums[i],则分差是nums[i]-dp[i+1][j];如果选nums[j],则分差是nums[j] - dp[i+1][j],取二者的最大值即可。
- 代码2。
public boolean PredictTheWinner(int[] nums) {
if (nums == null || nums.length == 0) {
return false;
}
// dp[i][j]表示可选nums[i...j]时,当前操作的选手(注意不一定是第一位选手)与另一位选手的最大分差
int[][] dp = new int[nums.length][nums.length];
// 只能在num[i...i]选,则最大分差就是nums[i],即dp[i][i]=nums[i]
for (int i = 0; i < nums.length; i++) {
dp[i][i] = nums[i];
}
// dp[i][j]=Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1])
for (int i = nums.length - 2; i >= 0; i--) {
for (int j = i + 1; j < nums.length; j++) {
dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]);
}
}
return dp[0][nums.length - 1] >= 0;
}
n 皇后问题
- 问题。n皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
输入:4
输出:[
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],
["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
解释: 4 皇后问题存在两个不同的解法。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-queens
- 思路。经典的回溯问题,也就是说我们通过dfs进行尝试,如果第i行的第j列可以放置一个皇后,那么我们尝试在第i+1行基于已有的结果继续尝试。第i行的第j列尝试完成后,我们尝试在第i行第j+1列放置一个皇后,然后重复之前的过程。
- 代码。
class Solution {
public List<List<String>> solveNQueens(int n) {
if (n <= 0) {
return new ArrayList<>();
}
List<List<String>> res = new ArrayList<>();
f(0, new ArrayList<>(), n, res);
return res;
}
private void f(int i, List<String> list, int n, List<List<String>> res) {
if (i == n && list.size() == n) {
res.add(new ArrayList<>(list));
return;
}
for (int j = 0; j < n; j++) {
if (canPlace(list, i, j, n)) {
String cur = getCurrentPlacement(j, n);
list.add(cur);
f(i + 1, list, n, res);
list.remove(list.size() - 1);
}
}
}
/**
* 已经放置的皇后保存在list中,看i j位置是否可以放置皇后
* */
private boolean canPlace(List<String> list, int i, int j, int n) {
// i j的正上方是否有皇后
int p = i - 1;
int q = 0;
while (p >= 0) {
if (list.get(p).charAt(j) == 'Q') {
return false;
}
p--;
}
// i j的左上方是否有皇后
p = i - 1;
q = j - 1;
while (p >= 0 && q >= 0) {
if (list.get(p).charAt(q) == 'Q') {
return false;
}
p--;
q--;
}
// i j的右上方是否有皇后
p = i - 1;
q = j + 1;
while (p >= 0 && p < n && q >= 0 && q < n) {
if (list.get(p).charAt(q) == 'Q') {
return false;
}
p--;
q++;
}
return true;
}
/**
* 第j列是皇后
* */
private String getCurrentPlacement(int j, int n) {
StringBuilder res = new StringBuilder();
for (int i = 0; i < n; i++) {
String cur = i == j ? "Q" : ".";
res.append(cur);
}
return res.toString();
}
/**
* queue记录已经摆放的皇后的位置,i表示当前要放第i行的皇后(此时第0到i-1行已经放完皇后了)
* */
private void dfs(char[][] queen, int i, int n, List<List<String>> res) {
if (i == n) {
res.add(fill(queen));
return;
}
for (int j = 0; j < n; j++) {
if (queen[i][j] == 'Q' || canNotPlace(queen, i, j, n)) {
continue;
}
queen[i][j] = 'Q';
dfs(queen, i + 1, n, res);
queen[i][j] = '.';
}
}
private List<String> fill(char[][] queen) {
List<String> list = new ArrayList<>();
for (char[] chars : queen) {
list.add(String.valueOf(chars));
}
return list;
}
private boolean canNotPlace(char[][] queen, int i, int j, int n) {
// 行
for (int p = 0; p < n; p++) {
if (queen[i][p] == 'Q') {
return true;
}
}
// 列
for (int p = 0; p < i; p++) {
if (queen[p][j] == 'Q') {
return true;
}
}
// 左上
int up = i - 1;
int left = j - 1;
while (up >= 0 && left >= 0) {
if (queen[up][left] == 'Q') {
return true;
}
up -= 1;
left -= 1;
}
// 右上
up = i - 1;
int right = j + 1;
while (up >= 0 && right < n) {
if (queen[up][right] == 'Q') {
return true;
}
up -= 1;
right += 1;
}
return false;
}
}
二叉树的所有路径
- 题目。给定一个二叉树,返回所有从根节点到叶子节点的路径。
说明: 叶子节点是指没有子节点的节点。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-tree-paths - 思路。二叉树的遍历。走到叶子节点则将获取的答案加入到结果集。
- 代码。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
TreeNode r;
public List<String> binaryTreePaths(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
r = root;
List<String> res = new ArrayList<>();
f(root, "", res);
return res;
}
private void f(TreeNode cur, String s, List<String> res) {
if (cur == null) {
return;
}
if (cur.left == null && cur.right == null) {
if (cur == r) {
res.add(cur.val + "");
} else {
res.add(s + "->" + cur.val);
}
return;
}
if (cur == r) {
f(cur.left, cur.val + "", res);
f(cur.right, cur.val + "", res);
} else {
f(cur.left, s + "->" + cur.val, res);
f(cur.right, s + "->" + cur.val, res);
}
}
}
第k个排列
- 题目。给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
“123”
“132”
“213”
“231”
“312”
“321”
给定 n 和 k,返回第 k 个排列。
说明:
给定 n 的范围是 [1, 9]。
给定 k 的范围是[1, n!]。
示例 1:
输入: n = 3, k = 3
输出: “213”
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutation-sequence
- 思路。直接写permutation会超时。需要加上剪枝。
- 代码。
class Solution {
private int n;
private int k;
private boolean[] used;
private int[] factorial;
public String getPermutation(int n, int k) {
this.n = n;
this.k = k;
used = new boolean[n + 1];
factorial = new int[n + 1];
factorial[0] = 1;
for (int i = 1; i <= n; i++) {
factorial[i] = factorial[i - 1] * i;
}
StringBuilder path = new StringBuilder();
f(0, path);
return path.toString();
}
private void f(int index, StringBuilder path) {
if (index == n) {
return;
}
// 计算还未确定的数字的全排列的个数,第 1 次进入的时候是 n - 1
int count = factorial[n - 1 - index];
for (int i = 1; i <=n ; i++) {
if (used[i]) {
continue;
}
if (count < k) {
k -= count;
continue;
}
path.append(i);
used[i] = true;
f(index + 1, path);
return;
}
}
// 超时的代码
// public String getPermutation(int n, int k) {
// List<String> res = new ArrayList<>();
// boolean[] used = new boolean[n];
// f(n, "", used, res);
// return res.get(k - 1);
// }
// private void f(int n, String cur, boolean[] used, List<String> res) {
// if (cur.length() == n) {
// res.add(cur);
// return;
// }
// for (int i = 0; i < n; i++) {
// if (used[i]) {
// continue;
// }
// used[i] = true;
// f(n, cur + (i + 1), used, res);
// used[i] = false;
// }
// }
}
二叉树的层次遍历 II
-
题目。给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
-
思路。二叉树的层次遍历。这里使用last表示当前层的最后一个节点,使用nextLast表示下一层的最后一个节点。
-
代码。
public List<List<Integer>> levelOrderBottom(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
LinkedList<List<Integer>> res = new LinkedList<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
TreeNode last = root;
TreeNode nextLast = null;
List<Integer> list = new ArrayList<>();
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
list.add(cur.val);
if (cur.left != null) {
queue.offer(cur.left);
nextLast = cur.left;
}
if (cur.right != null) {
queue.offer(cur.right);
nextLast = cur.right;
}
if (cur == last) {
res.addFirst(new ArrayList<>(list));
list = new ArrayList<>();
last = nextLast;
}
}
return res;
}