阿翰 剑指offer 之 Day 20 分治算法 中等

本文介绍了分治算法在重建二叉树和计算数值整数次方的问题上的实现,包括两种不同的重建二叉树的递归方法以及快速幂和二分递归计算数值次方的策略。同时,还探讨了如何验证二叉搜索树的后序遍历序列的正确性。这些算法展示了分治思想在解决计算机科学问题中的高效性和实用性。
摘要由CSDN通过智能技术生成

目录

分治算法

1 重建二叉树

1.2 优化写法

2 数值的整数次方

1. 快速幂

​ 2. 二分递归

3 二叉搜索树的后序遍历

1. 递归分治

2. 辅助单调栈


分治算法

1 重建二叉树

剑指 Offer 07. 重建二叉树https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/

  1. 通过前序序列第一个元素确定根节点(例如 3)
  2. 通过根节点把中序序列分成两个序列,一个是左子树序列([9]),一个是右子树序列([15,20,7)
  3. 通过左右子树的中序序列可以求出前序遍历的左右子树序列(左:[9],右:[20,15,7])
  4. 左右子树的前序序列第一个元素分别是根节点的左右儿子
  5. 通过递归重复以上步骤
class Solution {
    private Map<Integer, Integer> indexForInOrders = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        for (int i = 0; i < inorder.length; i++){
            indexForInOrders.put(inorder[i], i);
        }
        return reConstructBinaryTree(preorder, 0, preorder.length - 1, 0);
    }

    private TreeNode reConstructBinaryTree(int[] preorder, int preL, int preR, int inL) {
        if(preL > preR) return null;
        TreeNode root = new TreeNode(preorder[preL]);
        int inIndex = indexForInOrders.get(root.val);
        int leftTreeSize = inIndex - inL;

        root.left = reConstructBinaryTree(preorder, preL+1, preL+leftTreeSize, inL);
        root.right = reConstructBinaryTree(preorder, preL+leftTreeSize+1, preR, inL+leftTreeSize+1);
        return root;
    }
}

1.2 优化写法

class Solution { 
    HashMap<Integer, Integer> dic = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        for(int i = 0; i < inorder.length; i++)
            dic.put(inorder[i], i);
        return recur(preorder, 0, 0, inorder.length - 1);
    }
    TreeNode recur(int[] preorder, int root, int left, int right) {
        if(left > right) return null;                          // 递归终止
        TreeNode node = new TreeNode(preorder[root]);          // 建立根节点
        int i = dic.get(preorder[root]);                       // 划分根节点、左子树、右子树
        node.left = recur(preorder, root + 1, left, i - 1);              // 开启左子树递归
        node.right = recur(preorder, root + i - left + 1, i + 1, right); // 开启右子树递归
        return node;                                           // 回溯返回根节点
    }
} 

2 数值的整数次方

剑指 Offer 16. 数值的整数次方https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/

1. 快速幂

求 x^n 最简单的方法是通过循环将 n 个 x 乘起来,依次求 x^1, x^2, ..., x^{n-1},时间复杂度为 O(n) 。
快速幂法 可将时间复杂度降低至O(log2​n) ,以下从 “二分法” 和 “二进制” 两个角度解析快速幂法。

快速幂解析(二进制角度):

利用十进制数字 n 的二进制表示,可对快速幂进行数学化解释。

 快速幂解析(二分法角度):

快速幂实际上是二分思想的一种应用。

 

 

代码:

public double myPow(double x, int n) {
        if(x == 0)  return 0;
        long b = n;
        double res = 1.0;
//        负数幂指数 转化
        if(b < 0){
            x = 1/x;
            b = -b;
        }
        while(b > 0){
            if((b & 1) == 1)    res *= x;
            x *= x;
            b >>= 1;
        }
        return res;
    }

Java 代码中 int32 变量 n∈[−2147483648,2147483647] ,因此当 n = −2147483648 时执行 n = -n 会因越界而赋值出错。解决方法是先将 n 存入 long 变量 b ,后面用 b 操作即可。
来源:力扣(LeetCode) 作者:jyd

 2. 二分递归

解题思路

 假设求一个数的32次方,如果已知它的16次方,只要在16次方的基础上再平方一次;而16次方是8次方的平方 ;依此类推,求32次方只需要做5次乘法,先求平方,在平方的基础上求4次方,在4次方的基础上求8次方,在8次方的基础上求16次方,最后在16次方的基础上求32次方。

需要考虑一些特殊案例:x的0次方为1、x的1次方为x、x的-1次方为1/x
性能优化:用右移运算代替/2、用位与运算代表求余运算来判断一个数是奇数还是偶数。

class Solution {
    public double myPow(double x, int n){
        if (n == 0) return 1;
        if (n == 1) return x;
        if (n == -1) return 1 / x;
        // 用右移代替除以2
        double result = myPow(x, n >> 1);
        result *= result;
        // 用位与运算代替求余运算符来判断一个数是不是奇数还是偶数
        if ((n & 1) == 1) {
            result *= x;
        }
        return result;
    }
}

来源:力扣(LeetCode)作者:angela-x

3 二叉搜索树的后序遍历

剑指 Offer 33. 二叉搜索树的后序遍历序列icon-default.png?t=M4ADhttps://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/

  • 后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。
  • 二叉搜索树定义: 左子树中所有节点的值 << 根节点的值;右子树中所有节点的值 >> 根节点的值;其左、右子树也分别为二叉搜索树。 

1. 递归分治

根据二叉搜索树的定义,可以通过递归,判断所有子树的 正确性 (即其后序遍历是否满足二叉搜索树的定义) ,若所有子树都正确,则此序列为二叉搜索树的后序遍历。

class Solution {
    public boolean verifyPostorder(int[] postorder) { 
        return recur(postorder, 0 , postorder.length - 1);
    }
    public boolean recur(int[] postorder, int i, int j){
        if(i >= j) return true;
        int p = i;
        while(postorder[p] < postorder[j]) p++;
        int m = p;
        while(postorder[p] > postorder[j]) p++; 
        return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);
    }
}

2. 辅助单调栈

后序遍历倒序: [ 根节点 | 右子树 | 左子树 ] 。类似 先序遍历的镜像 ,即先序遍历为 “根、左、右” 的顺序,而后序遍历的倒序为 “根、右、左” 顺序。 

逆序遍历,找到里面的单调递增的部分,通过递增来判断是否是二叉搜索树。

使用 单调栈 来装 逆序的节点,需要先判断是否大于root 如果大于,直接return false;

然后有2个状态需要理解和处理的:

  • 当前节点比栈顶节点 小 。说明一边的递增逻辑已经遍历完,我们需要向左移动,切换遍历节点,继续进行递增逻辑。不过在这之前,需要清除上一次递增逻辑留下来的节点,需要把 大于 当前节点的值 出栈。 然后再压栈。
  • 当前节点比栈顶节点 大 。说明还是在 根->右 的逻辑范围(递增),只需要继续 压栈。

可以理解为一开始的根节点是MAX_VALUE
来源:力扣(LeetCode)作者:fengziL  

public boolean verifyPostorder(int[] postorder) {
        Deque<Integer> stack = new LinkedList<>();
        int root = Integer.MAX_VALUE;
        for (int i = postorder.length - 1; i >= 0 ; i--) {
            System.out.println(root+" "+postorder[i]+" " + stack);
            if(postorder[i] > root) return false;
            while(!stack.isEmpty() && stack.peek() > postorder[i])  root = stack.pop();//递减 才 全出栈 保留root
            stack.push(postorder[i]);//递增 压栈
        }
        return true;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值