目录
分治算法
1 重建二叉树
剑指 Offer 07. 重建二叉树https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/
- 通过前序序列第一个元素确定根节点(例如 3)
- 通过根节点把中序序列分成两个序列,一个是左子树序列([9]),一个是右子树序列([15,20,7)
- 通过左右子树的中序序列可以求出前序遍历的左右子树序列(左:[9],右:[20,15,7])
- 左右子树的前序序列第一个元素分别是根节点的左右儿子
- 通过递归重复以上步骤
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(log2n) ,以下从 “二分法” 和 “二进制” 两个角度解析快速幂法。
快速幂解析(二进制角度):
利用十进制数字 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;
}
}
3 二叉搜索树的后序遍历
- 后序遍历定义: [ 左子树 | 右子树 | 根节点 ] ,即遍历顺序为 “左、右、根” 。
- 二叉搜索树定义: 左子树中所有节点的值 << 根节点的值;右子树中所有节点的值 >> 根节点的值;其左、右子树也分别为二叉搜索树。
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;
}