代码随想录算法训练营第五十天| 739. 每日温度、496. 下一个更大元素 I 、503. 下一个更大元素 II 、42. 接雨水 、84. 柱状图中最大的矩形

[LeetCode] 739. 每日温度

[LeetCode] 739. 每日温度 文章解释

[LeetCode] 739. 每日温度 视频解释

题目:

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 1:

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]

示例 2:

输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]

示例 3:

输入: temperatures = [30,60,90]
输出: [1,1,0]

[LeetCode] 739. 每日温度 

自己看到题目的第一想法

    1. 第一反应是, 如果正序遍历的话, 遇到后一天温度比今天低, 这时候要知道过几天才能遇到更高的温度, 这时候就需要往后遍历或者记录住当前的天气. 如果往后遍历的话, 最坏的情况就是 O(n^2). 如果记录住当天没办法知道是否满足观测条件的天气, 则之后每个天气都要把所有记录住的天气遍历一遍, 最坏情况也是0(n^2).

    2. 可以倒序遍历吗, 如果当前天气比前一天高, 则记录住对应的索引关系. 这里没想通, 后来就放弃了.

看完代码随想录之后的想法

    就很神奇... 通过找当前元素右侧第一个大于自己的天气, 完成最终的计数. 只要满足就弹出并计算天数, 这样所有在栈中的温度, 从栈顶到栈底就都是单调递增了.

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int[] result = new int[temperatures.length];
        // 这里如果使用 Stack 的话效率会极低
        Deque<Integer> unmarkTemperatures = new LinkedList<>();
        for (int i = 0; i < temperatures.length; i++) {
            while (!unmarkTemperatures.isEmpty() && temperatures[i] > temperatures[unmarkTemperatures.peek()]) {
                result[unmarkTemperatures.peek()] = i - unmarkTemperatures.peek();
                unmarkTemperatures.pop();
            }
            unmarkTemperatures.push(i);
        }
        return result;
    }
}

自己实现过程中遇到哪些困难

    一开始想的复杂了, 通过 List 记录所有暂时无法计算的温度, 每次遇到新的温度时, 需要先遍历该 List 尝试计算, 最后这个方案也超时了.

    明白了单调栈之后就变得简单了, 但是当使用 Stack 作为数据结构的实现时, 效率也低的可怕, 改成 Deque<Integer> temps = new LinkedList<>() 之后效率就提上了.

[LeetCode] 496. 下一个更大元素 I

[LeetCode] 496. 下一个更大元素 I 文章解释

[LeetCode] 496. 下一个更大元素 I 视频解释

题目:

nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧第一个 比 x 大的元素。

给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。

对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j]下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1

返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素

示例 1:

输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
- 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。
- 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。

示例 2:

输入:nums1 = [2,4], nums2 = [1,2,3,4].
输出:[3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 2 ,用加粗斜体标识,nums2 = [1,2,3,4]。下一个更大元素是 3 。
- 4 ,用加粗斜体标识,nums2 = [1,2,3,4]。不存在下一个更大元素,所以答案是 -1 。

提示:

  • 1 <= nums1.length <= nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 104
  • nums1nums2中所有整数 互不相同
  • nums1 中的所有整数同样出现在 nums2

进阶:你可以设计一个时间复杂度为 O(nums1.length + nums2.length) 的解决方案吗?

[LeetCode] 496. 下一个更大元素 I 

自己看到题目的第一想法

    先计算 nums2 中每个元素的下一个更大元素, 然后双层 for 循环遍历一下 nums1 和 nums2, 填充对应的值. 这样效率很低吧?

看完代码随想录之后的想法

    可以把 nums1 先放到 Map 中, 这样在遍历 nums2 的过程中, 就可以直接填充 nums1 对应的值了.

class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        HashMap<Integer, Integer> nums1Indexes = new HashMap<>();
        for (int i = 0; i < nums1.length; i++) {
            nums1Indexes.put(nums1[i], i);
        }
        int[] result = new int[nums1.length];
        Arrays.fill(result, -1);
        LinkedList<Integer> nums2BiggersIndex = new LinkedList<>();
        for (int i = 0; i < nums2.length; i++) {
            while (!nums2BiggersIndex.isEmpty() && nums2[i] > nums2[nums2BiggersIndex.peek()]) {
                Integer nums1Index = nums1Indexes.get(nums2[nums2BiggersIndex.peek()]);
                if (nums1Index != null) {
                    result[nums1Index] = nums2[i];
                }
                nums2BiggersIndex.pop();
            }
            nums2BiggersIndex.push(i);
        }
        return result;
    }
}

自己实现过程中遇到哪些困难

    无.

[LeetCode] 503. 下一个更大元素 II

[LeetCode] 503. 下一个更大元素 II 文章解释

[LeetCode] 503. 下一个更大元素 II 视频解释

题目:

给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素

数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。

示例 1:

输入: nums = [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数; 
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

示例 2:

输入: nums = [1,2,3,4,3]
输出: [2,3,4,-1,4]

提示:

  • 1 <= nums.length <= 10^4
  • -10^9 <= nums[i] <= 10^9

[LeetCode] 503. 下一个更大元素 II 

自己看到题目的第一想法

    用一个栈记录住所有递减的数字, 如果当前待入栈的数字和栈顶的数字一致, 或者栈中的元素达到列表长度加1的长度时, 则停止.

    运行时长爆表了~~~

class Solution {
    public int[] nextGreaterElements(int[] nums) {
        Stack<Integer> numsIndexes = new Stack<>();
        int[] result = new int[nums.length];
        Arrays.fill(result, -1);
        if (nums.length <= 1) {
            return result;
        }
        numsIndexes.push(0);
        int index = 1;
        while (!numsIndexes.isEmpty() && numsIndexes.size() != (nums.length + 1)) {
            while (!numsIndexes.isEmpty() && nums[index] > nums[numsIndexes.peek()]) {
                result[numsIndexes.pop()] = nums[index];
            }
            if (!numsIndexes.isEmpty() && numsIndexes.peek() == index) {
                numsIndexes.pop();
            } else {
                numsIndexes.push(index);
                index = (index + 1) % nums.length;
            }
        }
        return result;
    }
}

看完代码随想录之后的想法

    原来是跑一圈, 现在跑两圈就可以了, 似乎很不错啊~

    因为只遍历一遍的话, 都是后面的数比前面大才处理, 成环之后如果前面的数字比后面的数字大, 也需要处理, 因此只要跑两圈, 就可以定可以处理到 nums[nums.length - 2] 和 nums[nums.length - 1] 的情况, 可以遍历完所有的元素.

class Solution {
    public int[] nextGreaterElements(int[] nums) {
        Stack<Integer> numsIndexes = new Stack<>();
        int[] result = new int[nums.length];
        Arrays.fill(result, -1);
        Deque<Integer> unmark = new LinkedList<>();
        for (int i = 0; i < nums.length * 2; i++) {
            int realIndex = i % nums.length;
            while (!unmark.isEmpty() && nums[realIndex] > nums[unmark.peek()]) {
                result[unmark.peek()] = nums[realIndex];
                unmark.pop();
            }
            unmark.push(realIndex);
        }
        return result;
    }
}

自己实现过程中遇到哪些困难

    无, 就是自己想复杂了, 随想录的算法清爽了非常多!!!

[LeetCode] 42. 接雨水

[LeetCode] 42. 接雨水 文章解释

[LeetCode] 42. 接雨水 视频解释

题目:

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

提示:

  • n == height.length
  • 1 <= n <= 2 * 10^4
  • 0 <= height[i] <= 10^5

[LeetCode] 42. 接雨水 

自己看到题目的第一想法

    这题我做过, 这题我会!!! 特别是放在单调栈这里, 更清晰了. 主要就是栈中所有元素从栈底到栈顶都是单调递减的, 这样如果当前的墙的高度大于栈顶元素, 说明出现凹槽了, 需要计算高度... 之前看了很久才看明白的, 这题我会!!!

    Two thousand years later...

    写是写出来了, 过程是真的不顺利... 特别是如果当前墙的高度和栈顶的墙高度一致该怎么办呢, 一开始我对于高度为0的墙没有入栈, 因此导致相同高度的也要考虑不能入栈, 导致实现非常混乱.

    加了一堆判断条件才做出来.

看完代码随想录之后的想法

    1. 只要当前墙的高度小于栈顶元素都入栈, 因为确实还没有形成凹槽.

    2. 如果墙的高度和栈顶元素相同, 则需要注意一下, 因为形成盛雨水容器需要三个墙, 第一是当前的墙, 一个是栈顶的墙, 该墙形成了盛雨水容器的底部, 因此需要弹出, 当雨水容器的底部弹出后, 下一个就是雨水容器的左侧. 如果墙的高度和栈顶元素相同时, 如果弹出栈顶元素后再入栈, 则下次计算雨水容器的时候, 可以少计算一次容积, 否则得计算两次.

// 单调栈版本
class Solution {
    public int trap(int[] totalWallHeight) {
        int sum = 0;
        Deque<Integer> wallHeights = new LinkedList<>();
        for (int i = 0; i < totalWallHeight.length; i++) {
            while (!wallHeights.isEmpty() && totalWallHeight[i] > totalWallHeight[wallHeights.peek()]) {
                int bottom = totalWallHeight[wallHeights.pop()];
                if (!wallHeights.isEmpty()) {
                    int leftTop = totalWallHeight[wallHeights.peek()];
                    int width = i - wallHeights.peek() - 1;
                    int height = Math.min(leftTop, totalWallHeight[i]) - bottom;
                    sum += width * height;
                }
            }
            if (!wallHeights.isEmpty() && totalWallHeight[i] == totalWallHeight[wallHeights.peek()]) {
                wallHeights.pop();
            }
            wallHeights.push(i);
        }
        return sum;
    }
}
// 动态规划版本
class Solution {
    public int trap(int[] totalWallHeight) {
        // dp[i][0] 表示 totalWallHeight[i] 开始往左(包括自己), 最高的墙的高度
        // dp[i][1] 表示 totalWallHeight[i] 开始往右(包括自己), 最高的墙的高度
        int[][] dp = new int[totalWallHeight.length][2];
        // 递推公式: 
        //     dp[i][0] = Math.max(dp[i - 1][0], totalWallHeight[i]);
        //     dp[i][1] = Math.max(dp[i + 1][1], totalWallHeight[i]);
        // 初始化:
        dp[0][0] = totalWallHeight[0];
        dp[totalWallHeight.length - 1][1] = totalWallHeight[totalWallHeight.length - 1];
        // 遍历顺序 
        //    dp[i][0] 从左往右
        //    dp[i][1] 从右往左
        for (int i = 1; i < totalWallHeight.length; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], totalWallHeight[i]);
        }
        for (int i = totalWallHeight.length - 2; i >= 0; i--) {
            dp[i][1] = Math.max(dp[i + 1][1], totalWallHeight[i]);
        }

        // 计算左右墙低的值减去当前墙的高度, 就是雨水的容积
        int sum = 0;
        for (int i = 0; i < totalWallHeight.length; i++) {
            sum += Math.min(dp[i][0], dp[i][1]) - totalWallHeight[i];
        }
        return sum;
    }
}

自己实现过程中遇到哪些困难

    真是“一言难尽”, 从信誓旦旦说我会, 结果遇到高度相等的情况时, 脑子转不过来了. 现在想想还蛮简单的, 为什么一开始的时候会那么恐惧呢?

[LeetCode] 84. 柱状图中最大的矩形

[LeetCode] 84. 柱状图中最大的矩形 文章解释

[LeetCode] 84. 柱状图中最大的矩形 视频解释

题目:

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

示例 1:

输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

示例 2:

输入: heights = [2,4]
输出: 4

提示:

  • 1 <= heights.length <=10^5
  • 0 <= heights[i] <= 10^4

[LeetCode] 84. 柱状图中最大的矩形

自己看到题目的第一想法

    计算最大面积的思路是什么呢?

看完代码随想录之后的想法

    想了有一会儿, 想明白了! 当前的矩形如果想和左右两边的矩形勾勒新的矩形, 首先左右两边的矩形得大于等于自己, 不然顶边往左右两边延伸的时候, 就会遇到空白区域. 因此要计算当前矩形能勾勒的最大矩形面积, 就要找到当前矩形左右两边第一个高度比自己小的矩形.

class Solution {
    public int largestRectangleArea(int[] heights) {
        int[] newHeights = new int[heights.length + 2];
        System.arraycopy(heights, 0, newHeights, 1, heights.length);
        int[] rightEdges = new int[newHeights.length];
        int[] leftEdges = new int[newHeights.length];// left edges 可以在查询 rightEdges 时候动态的计算出来
        Deque<Integer> recHeights = new LinkedList<>();
        for (int i = 0; i < newHeights.length; i++) {
            while (!recHeights.isEmpty() && newHeights[i] < newHeights[recHeights.peek()]) {
                rightEdges[recHeights.peek()] = i;
                recHeights.pop();
            }
            recHeights.push(i);
        }
        recHeights.clear();
        for (int i = newHeights.length - 1; i >= 0; i--) {
            while (!recHeights.isEmpty() && newHeights[i] < newHeights[recHeights.peek()]) {
                leftEdges[recHeights.peek()] = i;
                recHeights.pop();
            }
            recHeights.push(i);
        }

        int result = 0;
        for (int i = 0; i < newHeights.length; i++) {
            int leftIndex = leftEdges[i];
            int rightIndex = rightEdges[i];
            int width = rightIndex - leftIndex - 1;
            result = Math.max(result, width * newHeights[i]);
        }
        return result;
    }
}
class Solution {
    public int largestRectangleArea(int[] heights) {
        int result = 0;

        int[] newHeights = new int[heights.length + 2];
        System.arraycopy(heights, 0, newHeights, 1, heights.length);

        Deque<Integer> recHeights = new LinkedList<>();
        for (int i = 0; i < newHeights.length; i++) {
            while (!recHeights.isEmpty() && newHeights[i] < newHeights[recHeights.peek()]) {
                int height = newHeights[recHeights.pop()];
                int width = i - recHeights.peek() - 1;
                result = Math.max(result, width * height);
            }
            recHeights.push(i);
        }
        return result;
    }
}
// 效率最优, 我一直想实现但是没有成功的版本索引记录法~
// I like it.
class Solution {
    public int largestRectangleArea(int[] heights) {
        int result = 0;
        int[] leftEdges = new int[heights.length];
        int[] rightEdges = new int[heights.length];
        rightEdges[heights.length - 1] = heights.length;
        for (int i = 0; i < heights.length; i++) {
            int leftEdge = i - 1;
            while (leftEdge >= 0 && heights[i] <= heights[leftEdge]) {
                leftEdge = leftEdges[leftEdge];
            }
            leftEdges[i] = leftEdge;
        }
        for (int i = heights.length - 1; i >= 0; i--) {
            int rightEdge = i + 1;
            while (rightEdge < heights.length && heights[i] <= heights[rightEdge]) {
                rightEdge = rightEdges[rightEdge];
            }
            rightEdges[i] = rightEdge;
        }
        for (int i = 0; i < heights.length; i++) {
            int width = rightEdges[i] - leftEdges[i] - 1;
            result = Math.max(result, width * heights[i]);
        }
        return result;
    }
}

自己实现过程中遇到哪些困难

    单调栈的时候, 自己先计算了左边缘, 再计算了右边缘. 实际上在计算右边缘的过程中, 因为当前栈顶的元素不停的弹出, 左边缘实际上是弹出后下一个栈顶元素对应的值, 因此是可以复用的. 这点自己完全没有想到.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值