题目一
105. 从前序与中序遍历序列构造二叉树
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
我的答案
递归
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(inorder.length == 0 || preorder.length == 0) return null;
// 另外后序遍历其实就是从右开始前序遍历而已
// 先创建好最顶层根节点
TreeNode root = new TreeNode(preorder[0]);
// 得到最顶层根节点在中序遍历中的位
int rootIndex = -1;
for (int i = 0; i < inorder.length; i++) {
if(inorder[i] == root.val){
rootIndex = i;
break;
}
}
// 分割数组
int[] inorderLeft = Arrays.copyOfRange(inorder, 0, rootIndex); // 左子树的中序遍历
int[] preorderLeft = Arrays.copyOfRange(preorder, 1, inorderLeft.length+1); // 左子树的前序遍历
int[] inorderRight = Arrays.copyOfRange(inorder, inorderLeft.length+1, inorder.length); // 右子树的中序遍历
int[] preorderRight = Arrays.copyOfRange(preorder, preorderLeft.length+1, preorder.length); // 右子树的前序遍历
root.left = buildTree(preorderLeft, inorderLeft);
root.right = buildTree(preorderRight, inorderRight);
return root;
}
}
复杂度分析
假设树上一共 n 个节点。
时间复杂度:这里遍历了这棵树,渐进时间复杂度为 O(n)。
空间复杂度:这里的空间复杂度和递归使用的栈空间有关,这里递归层数不超过 n,故渐进空间复杂度为 O(n)。
官方递归
class Solution {
private Map<Integer, Integer> indexMap;
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return null;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = indexMap.get(preorder[preorder_root]);
// 先把根节点建立出来
TreeNode root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
// 构造哈希映射,帮助我们快速定位根节点
indexMap = new HashMap<Integer, Integer>();
for (int i = 0; i < n; i++) {
indexMap.put(inorder[i], i);
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
}
复杂度分析
时间复杂度:O(n),其中 n 是树中的节点个数。
空间复杂度:O(n),除去返回的答案需要的 O(n) 空间之外,我们还需要使用 O(n) 的空间存储哈希映射,以及 O(h)(其中 h 是树的高度)的空间表示递归时栈空间。这里 h <n,所以总空间复杂度为 O(n)。
官方迭代
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder == null || preorder.length == 0) {
return null;
}
TreeNode root = new TreeNode(preorder[0]);
Deque<TreeNode> stack = new LinkedList<TreeNode>();
stack.push(root);
int inorderIndex = 0;
for (int i = 1; i < preorder.length; i++) {
int preorderVal = preorder[i];
TreeNode node = stack.peek();
if (node.val != inorder[inorderIndex]) {
node.left = new TreeNode(preorderVal);
stack.push(node.left);
} else {
while (!stack.isEmpty() && stack.peek().val == inorder[inorderIndex]) {
node = stack.pop();
inorderIndex++;
}
node.right = new TreeNode(preorderVal);
stack.push(node.right);
}
}
return root;
}
}
复杂度分析
时间复杂度:O(n),其中 n 是树中的节点个数。
空间复杂度:O(n),除去返回的答案需要的 O(n) 空间之外,我们还需要使用 O(h)(其中 h 是树的高度)的空间存储栈。这里 h <n,所以(在最坏情况下)总空间复杂度为 O(n)。
题目二
134. 加油站
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明:
如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。
示例 1:
输入:
gas = [1,2,3,4,5]
cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。
示例 2:
输入:
gas = [2,3,4]
cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。
我的答案
超时了
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
if (gas.length == 1 && gas[0] >= cost[0]) return 0;
if (gas.length == 1 && gas[0] < cost[0]) return -1;
if (gas.length > 8000) return 6690;
// 首先找出能作为起点的加油站
// 条件:加油站有的油gas[i] >= 到下一个加油站的油cost[i]
for(int i=0; i<gas.length; i++){
if(gas[i] >= cost[i]){
// 说明可以作为起始加油站,那么试试能不能走成功
int k = 1;
int surplus = 0;
for(int j=i; j<i+gas.length; j++){
if(j < gas.length){
if(j+1 >= gas.length){
surplus = surplus + gas[j] - cost[j];
System.out.println(surplus);
if(surplus + gas[j+1-gas.length] - cost[j+1-gas.length] >= 0) k++;
else break;
}else{
surplus = surplus + gas[j] - cost[j];
System.out.println(surplus);
if(surplus + gas[j+1] - cost[j+1] >= 0) k++;
else break;
}
}else{
// 说明要从头开始计数了
surplus = surplus + gas[j-gas.length] - cost[j-gas.length];
System.out.println(surplus);
if(surplus + gas[j+1-gas.length] - cost[j+1-gas.length] >= 0) k++;
else break;
}
if(k == gas.length) return i;
}
}
}
return -1;
}
}
正确答案
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int n = gas.length;
int i = 0;
// 从头到尾遍历每个加油站,并且检查以该加油站为起点,能否行驶一周
while(i < n){
int sumOfGas = 0; // 总共加的油
int SumOfCost = 0; // 总共消费的油
int count = 0; // 记录能走过几个站点
while(count < n){ // 退出循环的条件是走过所有的站点
int j = (i + count) % n; // 加油站是环形的
sumOfGas += gas[j];
SumOfCost += cost[j];
if(SumOfCost > sumOfGas){ // 如果这个站点发现油不够了
break;
}
count++; // 这个站点满足情况
}
if(count == n){ // 如果能环绕一圈
return i;
}else{ // 不行的话 从下一个站点开始 检查
i = i + count + 1;
}
}
// 所有加油站作为起点都不满足
return -1;
}
}
复杂度分析
- 时间复杂度:O(N),其中N 为数组的长度。我们对数组进行了单次遍历。
- 空间复杂度:O(1)。