LeetCode 530, 201, 433


530. 二叉搜索树的最小绝对差

题目链接

530. 二叉搜索树的最小绝对差

标签

树 深度优先搜索 广度优先搜索 二叉搜索树 二叉树

思路

对于 二叉搜索树,在 中序遍历 的结果中找 相邻的元素,只有 相邻的元素之差 才可能 成为 最小差值,其他元素之间的差值只会比相邻元素之差大。例如对于以下二叉搜索树:

    4
   / \
  2   6
 / \
1  3

中序遍历结果为 [1, 2, 3, 4, 6],最小差值在 2 - 1, 3 - 2, 4 - 3, 6 - 4 中取得,发现最小差值为 1

中序遍历的核心思想为:先遍历左子树,再处理本节点,最后遍历右子树。按照中序遍历的结果,左子树的最后一个节点就是当前节点的“上一个”节点。也就是说,这两个节点在中序遍历的结果中是相邻的。这时就可以计算它们的差值,进而更新最小差值。

不过,有一个特殊情况:根节点一直往左子节点遍历的最终节点(也可以称为中序遍历的第一个节点是没有前一个节点的,所以该节点无法计算差值。

代码

class Solution {
    private int prev; // 前一个节点的值
    private int res; // 结果
    public int getMinimumDifference(TreeNode root) {
        res = Integer.MAX_VALUE;
        prev = -1; // 给 prev 赋初始值 -1,表示中序遍历的第一个节点没有前一个节点
        dfs(root);
        return res;
    }
    private void dfs(TreeNode curr) { // 中序遍历
        if (curr == null) {
            return;
        }

		// 先遍历左子树
        dfs(curr.left); // 递归遍历左子树,遍历完之后,prev 就是中序遍历结果中 curr 的上一个节点的值

		// 再处理本节点
        if (prev != -1) { // 当 prev == -1 时,表示中序遍历的第一个节点,无需进行求差值的操作
            res = Math.min(res, curr.val - prev);
        }
        prev = curr.val;

		// 最后遍历右子树
        dfs(curr.right); // 递归遍历右子树
    }
}

201. 数字范围按位与

题目链接

201. 数字范围按位与

标签

位运算

思路

对于本题,可以使用一个规律来解决:寻找 左、右边界的二进制数 的公共前缀

比如说要找 [20, 24] 之间的所有数的按位与的结果,则可以通过 20, 24 这两个数的二进制数来获取:

如果使用普通的解法:
20 = 0001 0100
21 = 0001 0101
21 = 0001 0110
21 = 0001 0111
24 = 0001 1000
		^
发现只有一位全为 1,其余位的按位与结果都是 0,最终的结果为 0001 0000,即 16

如果使用规律:
左、右边界的初始值
20 = 0001 0100
24 = 0001 1000
右移一位(两边界不相等):
0000 1010
0000 1100
右移一位(两边界不相等):
0000 0101
0000 0110
右移一位(两边界不相等):
0000 0010
0000 0011
右移一位(两边界相等):
0000 0001
0000 0001

发现公共前缀为 0001,期间向右移动了 4 位,所以也应该让 0001 向左移动 4 位,即得到 0001 0000,也就是 16

所以这个规律奏效

代码

class Solution {
    public int rangeBitwiseAnd(int left, int right) {
        int shift = 0; // 当 left == right 时,移动的位数
        while (left < right) { // 当 left == right 时退出循环
            left >>= 1; // left 向右移一位
            right >>= 1; // right 向右移一位
            shift++; // 移动的位数加一
        }
        return left << shift; // 让 left 或 right 左移 shift 位
    }
}

433. 最小基因变化

题目链接

433. 最小基因变化

标签

广度优先搜索 哈希表 字符串

思路

本题需要使用 广度优先搜索,使用广度优先搜索就需要 队列。本题的核心思想是:从 起始基因 开始,一个一个地 变化字符,从而形成 新基因;然后再从这些 新基因 开始,一个一个地 变化字符,从而形成 更新的基因;直到无法继续形成新基因形成的新基因与最终基因相同

由这个核心思想就引出了本题要讲解的如下几点:

  • 新基因的限制条件:新基因不能无法无天,想怎么生成就怎么生成,而是只能生成 基因库 中现有的基因。如果不是现有的基因,那么就跳过它。
  • 如何确定无法继续形成新基因:可以通过一个 Set 集合记录已遍历过的基因,如果新基因都是已遍历过的基因,那么就无法形成新基因了。
  • 如何确定形成新基因的步数:每次完成 从 一批基因 形成 另一批新的基因 的操作时,步数就加一,直到形成的新基因与最终基因相同。

至于一些细枝末节的处理,请看代码中的注释。

代码

class Solution {
    public int minMutation(String startGene, String endGene, String[] bank) {
        if (startGene.equals(endGene)) { // 如果 起始基因 与 最终基因 一样
            return 0; // 则无需变化,返回 0
        }

        Set<String> lib = new HashSet<>(bank.length); // 存储 基因 的库
        for (String gene : bank) { // 将 所有基因 放入 库 中
            lib.add(gene);
        }
        if (!lib.contains(endGene)) { // 如果 基因库 中不存在 最终基因
            return -1; // 则无法完成此基因变化,返回 -1
        }

        Set<String> vis = new HashSet<>(); // 存储 已遍历过 的基因
        int step = 1; // 从 起始基因 到 最终基因 需要转化的步数
        LinkedList<String> queue = new LinkedList<>(); // 存储 待遍历 的基因

        queue.offer(startGene); // 先将起始基因放入队列,等待遍历
        vis.add(startGene); // 并将其放入已遍历过的基因集合
        while (!queue.isEmpty()) { // 直到 没有待遍历的基因 才退出循环
            int size = queue.size(); // 获取本次遍历的基因数量
            for (int cnt = 0; cnt < size; cnt++) { // 循环 size 次,遍历 size 个基因
                String curr = queue.poll(); // 取出待遍历的基因,也称作 当前基因
                char[] currGene = curr.toCharArray();

                // 枚举 从当前基因 发生一次变化 形成的 可能的 新基因
                for (int i = 0; i < geneLen; i++) {
                    char temp = currGene[i]; // 先保存原有的基因

                    for (char key : keys) { // 取出可能的字符进行替换
                        if (key == currGene[i]) { // 如果 当前基因的第 i 个字符 与 待替换字符 一样
                            continue; // 则无法产生新的基因,跳过接下来的替换
                        }

                        // 准备构建新的基因
                        currGene[i] = key; // 将当前基因的第 i 个字符替换为 待替换字符

                        String newGene = new String(currGene); // 形成新的基因
                        if (vis.contains(newGene) // 如果新基因已经遍历过了
                                || !lib.contains(newGene)) { // 或者新基因不在基因库中
                            continue; // 则跳过这个新基因
                        }

                        if (newGene.equals(endGene)) { // 如果 新基因 与 最终基因 一致
                            return step; // 则可以返回转化的步数
                        }

                        queue.offer(newGene); // 将新基因放入队列,等待遍历
                        vis.add(newGene); // 并将其放入已遍历过的基因集合
                    }
                    
                    currGene[i] = temp; // 将原有的基因还原回来
                }
            }
            step++; // 进行了一次转化,步数加一
        }

        return -1; // 无法完成此基因变化,返回 -1
    }
    private static final char[] keys = {'A', 'C', 'G', 'T'}; // 基因序列中可能的字符
    private static final int geneLen = 8; // 基因序列的长度为 8
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值