LeetCode 103, 137, 909


103. 二叉树的锯齿形层序遍历

题目链接

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

题目链接

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 位) 的整数,可以对每一位进行如下操作:

  1. 先获取 nums 中所有数在这一位的和 sum
  2. 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. 蛇梯棋

题目链接

909. 蛇梯棋

标签

广度优先搜索 数组 矩阵

思路

本题的处理较为复杂,涉及 将蛇梯棋的 id 转化为对应的行数(纵坐标)和列数(横坐标)广度优先搜索。另外,题目描述也是一绝。

将蛇梯棋的 id 转化为对应的 行数 和 列数

按照题目描述,当 n = 4 时,蛇梯棋的棋盘如下图所示:
alt text
上图已经举出一个例子,以下列出解决方法:

  1. id 减一。
  2. 获取正常的 行数r 和 列数c
  3. 如果 行数r 是奇数,则需要颠倒 列数c,即有 c = n - 1 - c
  4. 颠倒 行数r,即有 r = n - 1 - r
  5. 返回最终的行数和列数。

广度优先搜索

广度优先搜索时需要使用 队列,针对本题,有如下的步骤:

  1. 创建一个 boolean[] 数组,共有 n * n + 1 个元素,可使用的下标区间为 [0, n * n],在本题中使用的下标的区间为 [1, n * n]。这个数组保存了每个 id 对应的方格是否被遍历过。
  2. 创建一个队列,这个队列存放 对组,对组中有两个元素:第一个元素表示 当前方格的 id,第二个元素为 id = 1 的方格 走到 当前方格 的步数。此处的步数指的是 掷出骰子的次数
  3. 队列中的第一个对组是 new int[]{1, 0},表示当前方格的 id1,走到当前方格的步数为 0
  4. 进行循环,直到队列为空。具体的循环操作如下:
    1. 取出队列保存的对组,获取 当前方格的 id 和 当前步数。
    2. 16 模拟投掷骰子的点数,对于每个点数,有以下操作:
      1. 将这个点数加到 当前方格的 id 上,这个值表示下一个方格的 id
      2. 如果 下一个方格的 id 大于 n * n,则没有必要再枚举更大的点数了,直接终结模拟。否则才能进行接下来的操作:
      3. 将 下一个方格的 id 转换为 方格的行数和列数。
      4. 如果 下一个方格 是 蛇 或 梯子,则直接到达其所指向的方格的 id。讲一讲 int[][] board 这个棋盘中方格的值:如果方格的值不为 -1,则它的值表示另一个方格的 id,可以直接从这个方格跳转到另一个方格。
      5. 如果下一个方格就是终点,则返回 当前步数 + 1,这里的 1 是从当前方格到下一个方格的步数。
      6. 如果没有遍历过下一个方格,则标识已遍历过了,并将 下一个方格的 id 和 走到下一个方格的步数 作为 对组 放到队列中,等待遍历。此外,如果遍历过下一个方格,代表下一个方格的搜索结果已经知道了,无需再进行搜索。
  5. 如果没有在循环内部返回,则表示不可能到 idn * 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}; // 颠倒行数并返回结果
    }
}
  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值