文章目录
103. 二叉树的锯齿形层序遍历
题目链接
标签
树 广度优先搜索 二叉树
思路
本题考查了 二叉树的层序遍历,只需要在遍历 奇数层 时将节点的值按 从左到右 的顺序放到集合中,在遍历 偶数层 时将节点的值按 从右到左 的顺序放到集合中。
要实现这一点并不难,只需要使用 双端队列,根据层数的不同有如下不同的操作:
- 在遍历 奇数层 时,将节点的值加入到 队列尾部,形成 从左到右 的顺序。
- 在遍历 偶数层 时,将节点的值加入到 队列头部,形成 从右到左 的顺序。
至于层序遍历,如果不懂,可以看 637. 二叉树的层平均值。
代码
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
List<List<Integer>> res = new ArrayList<>(); // 储存结果的集合
boolean isOdd = true; // 是否是奇数层,第一层为奇数层
LinkedList<TreeNode> queue = new LinkedList<>(); // 保存每层节点的队列
queue.offer(root); // 先将根节点放入队列,即遍历第一层
while (!queue.isEmpty()) { // 直到队列为空,才退出循环
LinkedList<Integer> deque = new LinkedList<>(); // 存储本层节点的值的集合
int size = queue.size(); // 获取本层节点的数量
for (int i = 0; i < size; i++) { // 遍历本层所有节点
TreeNode curr = queue.poll(); // 取出 当前节点
TreeNode left = curr.left; // 获取当前节点的 左子节点
if (left != null) { // 如果 左子节点 不为 null
queue.offer(left); // 则将其放到队列中,等待下一层遍历
}
TreeNode right = curr.right; // 获取当前节点的 右子节点
if (right != null) { // 如果 右子节点 不为 null
queue.offer(right); // 则将其放到队列中,等待下一层遍历
}
if (isOdd) { // 如果是奇数层
deque.offerLast(curr.val); // 则将新元素添加到队列尾部
} else { // 否则是偶数层
deque.offerFirst(curr.val); // 将新元素添加到队列头部
}
}
res.add(deque); // 计算平均值
isOdd = !isOdd; // 遍历完本层后,从 奇(偶)数层 变为 偶(奇)数层
}
return res;
}
}
137. 只出现一次的数字 II
题目链接
标签
位运算 数组
思路
如果不要求一定要达到
O
(
1
)
O(1)
O(1) 的常数级空间复杂度,则可以使用 Set
来解决本题。
本题需要使用 相同的三个数的 二进制数的 每一位 加起来都是 3
的倍数 的规律,例如:
对于 num = 11,它的二进制数为 1011,三个它相加的结果如下:
1011
+ 1011
+ 1011
-------
3033
可以发现,3033 的每一位都能被 3 整除。
如果给其中混进一个不同的数 10,它的二进制数为 1010,则相加的结果如下:
1011
+ 1011
+ 1011
+ 1010
-------
4043
发现如果对 4043 的每一位都对 3 取余,则会得到 1010 这个数,即为 不同的数 的二进制数。
综上所述,对于 int(32 位)
的整数,可以对每一位进行如下操作:
- 先获取
nums
中所有数在这一位的和sum
。 - 当
sum
不为3
的倍数时,将结果的这一位设置为1
。
代码
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for (int i = 0; i < 32; i++) {
int sum = 0; // 第 i + 1 位的和
for (int x : nums) {
sum += (x >> i) & 1; // & 1 用于清除 x 的其他位的影响,只取第 i + 1位的值
}
if (sum % 3 != 0) { // 当 sum % 3 不等于 0 时,才进行设置
res |= 1 << i; // 将第 i + 1 位设置为 1
}
}
return res;
}
}
909. 蛇梯棋
题目链接
标签
广度优先搜索 数组 矩阵
思路
本题的处理较为复杂,涉及 将蛇梯棋的 id
转化为对应的行数(纵坐标)和列数(横坐标) 和 广度优先搜索。另外,题目描述也是一绝。
将蛇梯棋的 id 转化为对应的 行数 和 列数
按照题目描述,当 n = 4
时,蛇梯棋的棋盘如下图所示:
上图已经举出一个例子,以下列出解决方法:
- 将
id
减一。 - 获取正常的 行数
r
和 列数c
。 - 如果 行数
r
是奇数,则需要颠倒 列数c
,即有c = n - 1 - c
。 - 颠倒 行数
r
,即有r = n - 1 - r
。 - 返回最终的行数和列数。
广度优先搜索
广度优先搜索时需要使用 队列,针对本题,有如下的步骤:
- 创建一个
boolean[]
数组,共有n * n + 1
个元素,可使用的下标区间为[0, n * n]
,在本题中使用的下标的区间为[1, n * n]
。这个数组保存了每个id
对应的方格是否被遍历过。 - 创建一个队列,这个队列存放 对组,对组中有两个元素:第一个元素表示 当前方格的
id
,第二个元素为 从id = 1
的方格 走到 当前方格 的步数。此处的步数指的是 掷出骰子的次数。 - 队列中的第一个对组是
new int[]{1, 0}
,表示当前方格的id
为1
,走到当前方格的步数为0
。 - 进行循环,直到队列为空。具体的循环操作如下:
- 取出队列保存的对组,获取 当前方格的
id
和 当前步数。 - 从
1
到6
模拟投掷骰子的点数,对于每个点数,有以下操作:- 将这个点数加到 当前方格的
id
上,这个值表示下一个方格的id
。 - 如果 下一个方格的
id
大于n * n
,则没有必要再枚举更大的点数了,直接终结模拟。否则才能进行接下来的操作: - 将 下一个方格的
id
转换为 方格的行数和列数。 - 如果 下一个方格 是 蛇 或 梯子,则直接到达其所指向的方格的
id
。讲一讲int[][] board
这个棋盘中方格的值:如果方格的值不为-1
,则它的值表示另一个方格的id
,可以直接从这个方格跳转到另一个方格。 - 如果下一个方格就是终点,则返回 当前步数 + 1,这里的
1
是从当前方格到下一个方格的步数。 - 如果没有遍历过下一个方格,则标识已遍历过了,并将 下一个方格的
id
和 走到下一个方格的步数 作为 对组 放到队列中,等待遍历。此外,如果遍历过下一个方格,代表下一个方格的搜索结果已经知道了,无需再进行搜索。
- 将这个点数加到 当前方格的
- 取出队列保存的对组,获取 当前方格的
- 如果没有在循环内部返回,则表示不可能到
id
为n * n
的方格,返回-1
。
代码
class Solution {
public int snakesAndLadders(int[][] board) {
final int N = board.length; // 棋盘的 总行数 和 总列数
final int MAX_ID = N * N; // 棋盘中方格的最大id
boolean[] vis = new boolean[MAX_ID + 1]; // 判断某个方格是否被遍历过
// 存储 对组:第一个元素为 当前方格的id,第二个元素为 从 id为1的方格 走到 当前方格 的步数
LinkedList<int[]> queue = new LinkedList<>();
queue.offer(new int[]{1, 0}); // 从 id 为 1 的地方开始遍历,初始步数为 0
while (!queue.isEmpty()) {
int[] pair = queue.poll(); // 取出 pair
int currId = pair[0], currStep = pair[1]; // 取出 当前方格的id 和 当前步数
for (int i = 1; i <= 6; i++) { // 从 1~6 中模拟掷出的点数
int nextId = currId + i; // 获取下一个方格的id
if (nextId > MAX_ID) { // 如果 id 大于 MAX_ID
break; // 则之后的 nextId 都大于 MAX_ID,退出循环
}
int[] rc = idToRC(nextId, N); // 将 下一个方格的id 转换为 行数 和 列数
int r = rc[0], c = rc[1]; // 取出 下一个方格 的 行数 和 列数
if (board[r][c] > 0) { // 如果存在 蛇 或 梯子
nextId = board[r][c]; // 则直接到达其所指向的方格id
}
if (nextId == MAX_ID) { // 如果能到达终点
return currStep + 1; // 则返回 当前步数 + 到id为 n平方 的方格的最后一步
}
if (!vis[nextId]) { // 如果没有遍历过 nextId 的方格
vis[nextId] = true; // 则标识已遍历过了
// 并将 下一个方格的id 和 走到下一个方格的步数 作为 对组 放到队列中
queue.offer(new int[]{nextId, currStep + 1});
} // 如果遍历过 nextId 的方格,则无需将其对组放入队列中,因为已经知晓它的结果了
}
}
return -1; // 不可能到id为 n平方 的方格,返回 -1
}
// 将 某个方格的id 转换为 它的行数和列数
private int[] idToRC(int id, int n) {
id--; // 由于方格的id的取值范围为 [1, MAX_ID],所以先让它的取值范围变成 [0, MAX_ID - 1]
int r = id / n, c = id % n; // 获取正常的 行数 和 列数
if (r % 2 == 1) { // 如果 正常的行数 为奇数,则需要 颠倒列数
c = n - 1 - c;
}
return new int[]{n - 1 - r, c}; // 颠倒行数并返回结果
}
}