每日算法12_2020-11-18

来源:题库 - 力扣 (LeetCode) (leetcode-cn.com)。侵删。

题目一

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)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值