月度归档: 2021年7月

LeetCode刷题【901】股票价格跨度

编写一个 StockSpanner 类,它收集某些股票的每日报价,并返回该股票当日价格的跨度。

今天股票价格的跨度被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。

例如,如果未来7天股票的价格是 [100, 80, 60, 70, 60, 75, 85],那么股票跨度将是 [1, 1, 1, 2, 1, 4, 6]

 

示例:

输入:["StockSpanner","next","next","next","next","next","next","next"], [[],[100],[80],[60],[70],[60],[75],[85]]
输出:[null,1,1,1,2,1,4,6]
解释:
首先,初始化 S = StockSpanner(),然后:
S.next(100) 被调用并返回 1,
S.next(80) 被调用并返回 1,
S.next(60) 被调用并返回 1,
S.next(70) 被调用并返回 2,
S.next(60) 被调用并返回 1,
S.next(75) 被调用并返回 4,
S.next(85) 被调用并返回 6。

注意 (例如) S.next(75) 返回 4,因为截至今天的最后 4 个价格
(包括今天的价格 75) 小于或等于今天的价格。

 

提示:

  1. 调用 StockSpanner.next(int price) 时,将有 1 <= price <= 10^5
  2. 每个测试用例最多可以调用  10000StockSpanner.next
  3. 在所有测试用例中,最多调用 150000 次 StockSpanner.next
  4. 此问题的总时间限制减少了 50%。
Related Topics
  • 设计
  • 数据流
  • 单调栈
  • \n
  • 👍 127
  • 👎 0
  • 题解

    看题目。好像很简单。一直往前叠加计数么。

    
    import java.util.ArrayList;
    import java.util.LinkedList;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class StockSpanner {
    
        private ArrayList<Integer> list;
    
        public StockSpanner() {
            list = new ArrayList<>();
        }
        
        public int next(int price) {
            int count = 1;
            for (int i = list.size()-1; i >=0 ; i--) {
                if (list.get(i)>price){
                    break;
                }
                count++;
            }
            list.add(price);
            return count;
        }
    }
    
    /**
     * Your StockSpanner object will be instantiated and called as such:
     * StockSpanner obj = new StockSpanner();
     * int param_1 = obj.next(price);
     */
    //leetcode submit region end(Prohibit modification and deletion)

    提交,看结果。

    解答成功:
    执行耗时:572 ms,击败了12.05% 的Java用户
    内存消耗:47.6 MB,击败了9.54% 的Java用户

    emmmmmmmmm,恶魔妈妈木木木木木木木。。。。。应该离超时不远了。

    但是其实想一想,回过头来看下代码。每次next的时候,其实是执行的操作是,往结尾插入一个新数据,并往前找到第一个比自己大的值,结果已经很明显了, 另外再维护一份单调性递减的栈数据,十分清晰的空间换时间。

    
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.Stack;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class StockSpanner {
    
        private ArrayList<Integer> list;
    
        private Stack<Integer> stack;
        public StockSpanner() {
            list = new ArrayList<>();
            stack = new Stack<>();
        }
        
        public int next(int price) {
            int count = 1;
            list.add(price);
            int index = list.size()-1;
            if (stack.isEmpty()){
                stack.push(index);
            }else{
                //等值的时候的情况也要考虑
                while (!stack.isEmpty() && price >= list.get(stack.peek())){
                    stack.pop();
                    if (stack.isEmpty()){
                        count = index + 1;
                    }else{
                        count = index - stack.peek();
                    }
                }
                stack.push(index);
            }
            return count;
        }
    }
    
    /**
     * Your StockSpanner object will be instantiated and called as such:
     * StockSpanner obj = new StockSpanner();
     * int param_1 = obj.next(price);
     */
    //leetcode submit region end(Prohibit modification and deletion)

    结果

    解答成功:
    执行耗时:29 ms,击败了89.06% 的Java用户
    内存消耗:45.9 MB,击败了99.10% 的Java用户

    2022年10月21日更新下

    这么来说

    [100, 80, 60, 70, 60, 75, 85]

    面对这样的数据的时候

    1. 拿到了一个100,往前看没有数据,此时应当返回1
    2. 第二次拿到了80,往前看只能看到一个数字100,比80大,此时应当返回1
    3. 第三次,拿到了数字60,继续往前看,直接就遇到了80,还是返回1
    4. 第四次,拿到了数字70,往前可以看到一个数字60,此时结果值应当加1,再往前,遇到了数字80了,比70大,返回结果2
    5. 再次遇到60,结果依旧返回1
    6. 遇到75,往前看到60,结果次数加1,再往前看到70,依旧比75小,但是之前我们知道之前遇到数字70的时候得到过结果2,那么就表示在数字70之前,有比70小的数字,和这个70组成连续区间的长度为2,那么结果再加上2。之后再继续往前找,直到找到比数字75大的数字,那么最终结果返回4
    7. 最后拿到数字85,往前找到了前一个数字75,根据上一步的结果,我们知道了75之前有一段长度为4的连续区间是小于等于75的,那么也必然是小于85的,那么给结果直接加4,并可以跳过这个数字4对应的从60开始到75的这段区间。再继续往前,遇到了80,比85小,依旧可以继续加1,直到遇到了, 数字100之后结束,返回结果6

    完成分析之后

    第一个问题,在经过了上面的分析过程之后,我们可以很清除的明白了这里的运算逻辑,而单调栈的特性非常完美的契合了这个场景。以最快的速度,找到当前数字之前比这个数字大的数字。

    第二个问题,在上面的计算过程中,我们需要记得之前的数字对应的他前面的单调递增区间的长度,又因为每天的价格是可以重复的,那么此时就不然选择Map结构来记录某天价格和区间长度的对应的值
    此时有两个选择:

    1. 我们在解决第一个问题时候用到的单调栈的存储结构中,同时记录他的跨度值,即对应长度
    2. 另外用一个能跟单调栈同步弹出/压入操作的数据结构映射对应之前记录的跨度值,最简单的方式就是另外建一个栈同步操作

    从代码书写简便的角度,我选择了第二种,你也可以尝试下用第一种方法来实现下

    class StockSpanner {
    
        private Stack<Integer> priceStack;
        private Stack<Integer> spanStack;
    
        public StockSpanner() {
            priceStack = new Stack<>();
            spanStack = new Stack<>();
        }
        
        public int next(int price) {
            int span = 1;
            while (!priceStack.isEmpty() && priceStack.peek() <= price){
                priceStack.pop();
                span += spanStack.pop();
            }
            priceStack.add(price);
            spanStack.add(span);
            return span;
        }
    }

    Java的Stack运行效率比较慢,单当前这个场景下如果追求速度,可以考虑下自建一个简单快速点的栈数据结构,这里只讨论本题的思路,不在这个上面继续做扩展了

    https://leetcode.cn/problems/online-stock-span/solution/by-cheungq-6-bd6u/

    LeetCode刷题【503】下一个更大元素 II

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

    示例 1:

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

    注意: 输入数组的长度不会超过 10000。

    Related Topics
  • 数组
  • 单调栈
  • \n
  • 👍 456
  • 👎 0
  • 题解

    找某个值附近的前面或者后面的较大或者较小值,最直接的单调栈解法,遍历数组,把当前值存入栈,如果当前值比栈顶值大,则挨个弹出栈顶的值,直到栈顶的值比当前值小,这些挨个被弹出的值可以理解为是当前值前面按照递减顺序排列的值,所以当前值是栈内弹出的这些值的后面的第一个比当前值大的。

    然后这里有个变种,题中说了这是一个环状结构,常规作法,破环成链,把链复制一份连接到结尾,变成一个双倍的长度的结构

    
    import java.util.Arrays;
    import java.util.Stack;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int[] nextGreaterElements(int[] nums) {
            int[] res = new int[nums.length];
            Arrays.fill(res,-1);
            int[] doubleArr = new int[nums.length*2];
            for (int i = 0; i < nums.length; i++) {
                doubleArr[i] = nums[i];
                doubleArr[i+nums.length] = nums[i];
            }
            Stack<Integer> stack = new Stack<>();
            for (int i = 0; i < doubleArr.length; i++) {
                if (stack.isEmpty()){
                    stack.push(i);
                }else{
                    while (!stack.isEmpty() && doubleArr[i] > doubleArr[stack.peek()]){
                        res[stack.pop()%nums.length] = doubleArr[i];
                    }
                    stack.push(i);
                }
            }
    
            return res;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    上面的是单调栈,常规来说,普通的思路用暴力解法也可使,看力扣上有人提交的暴力解法也能提交通过。

    挨个遍历当前数组,每到一位,再往后尝试探测找到第一个比当前值大的值,结束后再继续下一位循环。而需要破环成链的作法和上面一样,构建成原来长度双倍的数组,往后探测的时候只要探测到后面一段的相同位置就可以停止了。

    其实还有一个细节也许可以考虑,因为数组的连续性,如果是连续递减区间的话,比如【5,4,3,2,1,3,9】,从5到1是一个连续的递减区间,这样的在从5遍历到1的时候,这个连续区间应该是都可以统一调过的,是不是可以维护这样一个连续递减区间的信息,在遍历数组的每一个值的时候,可以根据这个区间的信息来减少探测次数

    LeetCode刷题【496】 下一个更大元素 I

    给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。

    请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。

    nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1

     

    示例 1:

    输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
    输出: [-1,3,-1]
    解释:
        对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
        对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
        对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

    示例 2:

    输入: nums1 = [2,4], nums2 = [1,2,3,4].
    输出: [3,-1]
    解释:
        对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
        对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
    

     

    提示:

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

     

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

    Related Topics
  • 数组
  • 哈希表
  • 单调栈
  • \n
  • 👍 454
  • 👎 0
  • 题解

    找集合中元素附近的最大最小值,用单调栈的特性来解决。本来想的以为num1是num2的其中一个连续段,还想着先要找到起始位置,仔细审题之后发现自己想多了

    
    
    import java.util.HashMap;
    import java.util.Stack;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int[] nextGreaterElement(int[] nums1, int[] nums2) {
            int[] res = new int[nums1.length];
            HashMap<Integer,Integer> map = new HashMap<>();
            Stack<Integer> stack = new Stack<Integer>();
            for (int numOf2 : nums2) {
                if (stack.isEmpty()){
                    stack.push(numOf2);
                }else{
                    while (!stack.isEmpty() && stack.peek() < numOf2){
                        map.put(stack.pop(),numOf2);
                    }
                    stack.push(numOf2);
                }
            }
            for (int i = 0; i < nums1.length; i++) {
                res[i] = map.getOrDefault(nums1[i], -1);
            }
            return res;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    LeetCode刷题【155】最小栈

    设计一个支持 pushpoptop 操作,并能在常数时间内检索到最小元素的栈。

    • push(x) —— 将元素 x 推入栈中。
    • pop() —— 删除栈顶的元素。
    • top() —— 获取栈顶元素。
    • getMin() —— 检索栈中的最小元素。

     

    示例:

    输入:
    ["MinStack","push","push","push","getMin","pop","top","getMin"]
    [[],[-2],[0],[-3],[],[],[],[]]
    
    输出:
    [null,null,null,null,-3,null,0,-2]
    
    解释:
    MinStack minStack = new MinStack();
    minStack.push(-2);
    minStack.push(0);
    minStack.push(-3);
    minStack.getMin();   --> 返回 -3.
    minStack.pop();
    minStack.top();      --> 返回 0.
    minStack.getMin();   --> 返回 -2.
    

     

    提示:

    • poptopgetMin 操作总是在 非空栈 上调用。
    Related Topics
  • 设计
  • \n
  • 👍 962
  • 👎 0
  • 题解

    
    import java.util.Stack;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class MinStack {
    
        private Stack<Integer> stack;
        private Stack<Integer> minStack = new Stack<>();
    
        /** initialize your data structure here. */
        public MinStack() {
            stack = new Stack<>();
            minStack = new Stack<>();
        }
        
        public void push(int val) {
            stack.push(val);
            if (minStack.isEmpty() || minStack.peek()>= val){
                minStack.push(val);
            }
        }
        
        public void pop() {
            if (stack.peek().equals(minStack.peek())){
                minStack.pop();
            }
            stack.pop();
        }
        
        public int top() {
            return stack.peek();
        }
        
        public int getMin() {
            return minStack.peek();
        }
    }
    
    /**
     * Your MinStack object will be instantiated and called as such:
     * MinStack obj = new MinStack();
     * obj.push(val);
     * obj.pop();
     * int param_3 = obj.top();
     * int param_4 = obj.getMin();
     */
    //leetcode submit region end(Prohibit modification and deletion)
    

    LeetCode刷题【907】子数组的最小值之和

    给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。

    由于答案可能很大,因此 返回答案模 10^9 + 7

     

    示例 1:

    输入:arr = [3,1,2,4]
    输出:17
    解释:
    子数组为 [3],[1],[2],[4],[3,1],[1,2],[2,4],[3,1,2],[1,2,4],[3,1,2,4]。 
    最小值为 3,1,2,4,1,1,2,1,1,1,和为 17。

    示例 2:

    输入:arr = [11,81,94,43,3]
    输出:444
    

     

    提示:

    • 1 <= arr.length <= 3 * 104
    • 1 <= arr[i] <= 3 * 104

     

    Related Topics
  • 数组
  • 动态规划
  • 单调栈
  • \n
  • 👍 259
  • 👎 0
  • 题解

    
    
    import java.util.Stack;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        private int sum = 0;
    
        public int sumSubarrayMins(int[] arr) {
    
            Stack<int[]> stack = new Stack<>();
            int tmp = 0;
            for (int i = 0; i < arr.length; i++) {
                int count = 1;
                //边界条件等于的情况,后来的数字如果和前面已经入栈的数字大小相同,拿后来的数字覆盖前面的,
                while (!stack.isEmpty() && arr[i] <= stack.peek()[0]){
                    int[] top = stack.pop();
                    count += top[1];
                    tmp -= top[1] * top[0];
                }
                tmp += arr[i] * count;
                sum += tmp ;
                stack.push(new int[]{arr[i],count});
    
                if (sum >= 1000000007){
                    sum -= 1000000007;
                }
            }
    
            return sum;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    LeetCode刷题【456】132模式

    给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]nums[j]nums[k] 组成,并同时满足:i < j < knums[i] < nums[k] < nums[j]

    如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false

     

    示例 1:

    输入:nums = [1,2,3,4]
    输出:false
    解释:序列中不存在 132 模式的子序列。
    

    示例 2:

    输入:nums = [3,1,4,2]
    输出:true
    解释:序列中有 1 个 132 模式的子序列: [1, 4, 2] 。
    

    示例 3:

    输入:nums = [-1,3,2,0]
    输出:true
    解释:序列中有 3 个 132 模式的的子序列:[-1, 3, 2]、[-1, 3, 0] 和 [-1, 2, 0] 。
    

     

    提示:

    • n == nums.length
    • 1 <= n <= 2 * 105
    • -109 <= nums[i] <= 109
    Related Topics
  • 数组
  • 二分查找
  • 有序集合
  • 单调栈
  • \n
  • 👍 530
  • 👎 0
  • 题解

    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public boolean find132pattern(int[] nums) {
            Deque<Integer> deque = new ArrayDeque<>();
            int pollMax = Integer.MIN_VALUE;
            for (int i = nums.length - 1; i >= 0; i--) {
                if (deque.isEmpty()){
                    deque.offer(i);
                    continue;
                }
                if ( nums[i] < pollMax ){
                    return true;
                }
                //维护递增
                while ( !deque.isEmpty() && nums[i] > nums[deque.peekLast()] ){
                    int lastPollIndex = deque.pollLast();
                    pollMax = Math.max(pollMax,nums[lastPollIndex]);
                }
                deque.offer(i);
            }
            return false;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    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
    • 0 <= n <= 3 * 104
    • 0 <= height[i] <= 105
    Related Topics
  • 数组
  • 双指针
  • 动态规划
  • 单调栈
  • \n
  • 👍 2503
  • 👎 0
  • 题解

    看到题目第一个想到的想法是,找峰值,找到峰值之后,找下一个峰值,这样计算两个峰值之间能够的储水量。然后接下来再去后面继续找峰值,但是新的峰值和前面的峰值之间的储水面积之间是有可能有重合的。

    那么有什么办法来计算去掉重合的面积嘛?标记已计算部分也行可行,但是计算起来比较麻烦。拿自己凑的一个数组举例如图数组【7,5,4,1,3,6,2,9】

    如果按照之前的思路可以得到这样的情况

    后面的复杂形状的计算逻辑有点麻烦,但是这个时候换了个想法看了下。我们其实可以横着来计算,思路都在这张图上了。

    依旧是找到第一个峰值,左边的都可以丢掉不管,因为第一个峰值左边的部分储不了水。从峰值开始,每个索引的值按照单调递减入栈,当有新的值比栈顶的值大的时候说明有开始呈上升趋势了,有开始上升的时候就说明这个时候是可以储水的了。

    比如图中的索引4位置的值是3,当前栈内元素,按照从栈顶到栈底的顺序依次为1,4,5,7(实际栈内存的索引位置,后面默认都是不做额外说明)。

    则可以计算得到A区域的面积,栈顶元素弹出,此时最新栈顶元素为4,而当前的3与4之间要储水的话,需要取小的一个,就是3,再减去之前弹出的栈顶元素1作为底部高度,那么这段区间可以储水面积就是3×1=3。并把3压入栈。

    再往后到了索引5的位置,此时栈内元素为3,4,5,7。因为6大于3,把栈顶元素3出栈,按照之前同样的逻辑,6和此时栈顶的4比较取4,那么就得到了图中B区域的面积(4-3)x(5-2-1) = 2。继续和栈顶元素比较,6大于4,将4弹出,相同的操作,得到C区域的面积,同样的算出D区域的面积。最终将5位置6压入栈。

    此时栈中元素为6,7。然后来到了索引6,索引6位置2小于此时小于栈顶的6,直接压入栈。之后到了索引7,再算出E,F区域的面积。最终即为所求总储水量。

    代码

    
    import java.util.ArrayDeque;
    import java.util.Deque;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        int area = 0;
    
        public int trap(int[] arr) {
            if (null==arr||arr.length==0)return area;
            int left = 0;
            int index = 0;
            //找出第一个左边
            while (index < arr.length-1){
                if (arr[index] >= arr[left]){
                    left = index;
                }else{
                    break;
                }
                index++;
            }
            Deque<Integer> deque = new ArrayDeque<>();
            deque.offer(left);
            //此时的left一定是第一个最高点
            for (int currentIndex = left; currentIndex < arr.length; currentIndex++) {
                if (currentIndex > 0 && arr[currentIndex]>arr[currentIndex-1]){
                    while (!deque.isEmpty() && arr[currentIndex] >= arr[deque.peekLast()]){
                        int bottomIndex = deque.pollLast();
                        if (deque.isEmpty()){
                            break;
                        }
                        int leftIndex = deque.peekLast();
                        int height = Math.min(arr[currentIndex],arr[leftIndex]) - arr[bottomIndex];
                        int width = currentIndex - leftIndex - 1;
                        area += height * width;
                    }
    
                }
                deque.offerLast(currentIndex);
            }
            return area;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)