作者: CheungQ

LeetCode刷题【268】丢失的数字

给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。

 

进阶:

  • 你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?

 

示例 1:

输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 2:

输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 3:

输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。

示例 4:

输入:nums = [0]
输出:1
解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。

 

提示:

  • n == nums.length
  • 1 <= n <= 104
  • 0 <= nums[i] <= n
  • nums 中的所有数字都 独一无二
Related Topics
  • 位运算
  • 数组
  • 哈希表
  • 数学
  • 排序
  • \n
  • 👍 421
  • 👎 0
  • 题解

    方法很多,先列一个数学方法,根据题意,数字不重复,那么其实可以根据特性知道从0-n的合比0-n的丢失了其中一个数字x的合大x,所以两组数的求和相减即为所求数字。而考虑到大数相加可能溢出的问题,可以用一边加一边减的作法。

    代码如下

    
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int missingNumber(int[] nums) {
            int count = nums.length+1;
            int tmp = 0;
            for (int i = 0; i < count; i++) {
                tmp -= i;
                if (i<count-1){
                    tmp += nums[i];
                }
            }
            return -tmp;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)
    

    而最常规的作法,其实应该是按顺序排列,再挨个遍历,找到丢失的那一个。

    其次借助hashMap映射关系的额外空间,找到丢失的数字,不过可以换个写法

    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int missingNumber(int[] nums) {
            int[] arr = new int[nums.length+1];
            Arrays.fill(arr,-1);
            for (int num : nums) {
                arr[num] = num;
            }
            for (int i = 0; i < arr.length; i++) {
                if (arr[i]==-1){
                    return i;
                }
            }
            return -1;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    思路和hashmap一样,只是用int[] arr的数组下标代替的hashmap的key

    LeetCode刷题【1673】找出最具竞争力的子序列

    给你一个整数数组 nums 和一个正整数 k ,返回长度为 k 且最具 竞争力 nums 子序列。

    数组的子序列是从数组中删除一些元素(可能不删除元素)得到的序列。

    在子序列 a 和子序列 b 第一个不相同的位置上,如果 a 中的数字小于 b 中对应的数字,那么我们称子序列 a 比子序列 b(相同长度下)更具 竞争力 。 例如,[1,3,4][1,3,5] 更具竞争力,在第一个不相同的位置,也就是最后一个位置上, 4 小于 5

     

    示例 1:

    输入:nums = [3,5,2,6], k = 2
    输出:[2,6]
    解释:在所有可能的子序列集合 {[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]} 中,[2,6] 最具竞争力。
    

    示例 2:

    输入:nums = [2,4,3,3,5,4,9,6], k = 4
    输出:[2,3,3,4]
    

     

    提示:

    • 1 <= nums.length <= 105
    • 0 <= nums[i] <= 109
    • 1 <= k <= nums.length
    Related Topics
  • 贪心
  • 数组
  • 单调栈
  • \n
  • 👍 61
  • 👎 0
  • 题解

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

    LeetCode刷题【739】每日温度

    请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 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]

     

    提示:

    • 1 <= temperatures.length <= 105
    • 30 <= temperatures[i] <= 100
    Related Topics
  • 数组
  • 单调栈
  • \n
  • 👍 824
  • 👎 0
  • 题解

    一样的,最直接的办法,遍历循环往后探测到一个比当天天温度高的。

    而对于题中的描述,要求的是后面第一个比当前天气高的天数,换个说法,就是找后面的数据的单调递增栈的栈顶元素。

    思路就出来了。

    从后往前遍历,如果当前天的气温比栈定元素的气温高(或者等于),则持续对栈弹出操作。直到弹出到最后栈顶的天气是比当前天气高的。

    那么此时的栈顶元素就是从当天开始往后找到的第一个比当前天温度高的那一天。

    
    import java.util.Stack;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
    
        private Stack<Integer> stack;
    
        public int[] dailyTemperatures(int[] temperatures) {
            stack = new Stack<>();
            int[] res = new int[temperatures.length];
            for (int i = temperatures.length-1; i >= 0; i--) {
                if (stack.isEmpty()){
                    stack.push(i);
                }else{
                    //把后面的比当前温度高的弹出去
                    while (!stack.isEmpty() && temperatures[i] >= temperatures[stack.peek()]){
                        stack.pop();
                    }
                    if (!stack.isEmpty()){
                        //此时栈顶的那个就是当前天数后面第一个比当前天温度高的
                        res[i] = stack.peek()-i;
                    }
                    stack.push(i);
                }
            }
            return res;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)
    

    这个思路对于我而言是比较显而易见的。其实也可以从前面开始正向遍历。具体思路和另外一题LeetCode刷题【503】下一个更大元素 II一致。

    正向遍历,存递减栈数据,判断,如果当前值小于等于栈顶值,直接加入,如果大于栈顶值,挨个从栈顶弹出比当前天的温度小的天,并给他们赋值,他们后面的第一个比他们的温度高是当前这一天。

    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)