LeetCode 2104. 子数组范围和(转变思维,单调栈) / 521. 最长特殊序列 Ⅰ / 2100. 适合打劫银行的日子

2104. 子数组范围和

2022.3.4 每日一题

题目描述

给你一个整数数组 nums 。nums 中,子数组的 范围 是子数组中最大元素和最小元素的差值。

返回 nums 中 所有 子数组范围的 和 。

子数组是数组中一个连续 非空 的元素序列。

示例 1:

输入:nums = [1,2,3]
输出:4
解释:nums 的 6 个子数组如下所示:
[1],范围 = 最大 - 最小 = 1 - 1 = 0
[2],范围 = 2 - 2 = 0
[3],范围 = 3 - 3 = 0
[1,2],范围 = 2 - 1 = 1
[2,3],范围 = 3 - 2 = 1
[1,2,3],范围 = 3 - 1 = 2
所有范围的和是 0 + 0 + 0 + 1 + 1 + 2 = 4

示例 2:

输入:nums = [1,3,3]
输出:4
解释:nums 的 6 个子数组如下所示:
[1],范围 = 最大 - 最小 = 1 - 1 = 0
[3],范围 = 3 - 3 = 0
[3],范围 = 3 - 3 = 0
[1,3],范围 = 3 - 1 = 2
[3,3],范围 = 3 - 3 = 0
[1,3,3],范围 = 3 - 1 = 2
所有范围的和是 0 + 0 + 0 + 2 + 0 + 2 = 4

示例 3:

输入:nums = [4,-2,-3,4,1]
输出:59
解释:nums 中所有子数组范围的和是 59

提示:

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

进阶:你可以设计一种时间复杂度为 O(n) 的解决方案吗?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sum-of-subarray-ranges
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

class Solution {
    public long subArrayRanges(int[] nums) {
        //看到数据范围,能想到的最直接的方法就是两层循环找每个子数组的最大最小值
        //先写一下过了

        int n = nums.length;
        long res = 0;
        for(int i = 0; i < n; i++){
            int max = nums[i];
            int min = nums[i];
            for(int j = i; j < n; j++){
                max = Math.max(max, nums[j]);
                min = Math.min(min, nums[j]);
                res += max - min;
            }
        }
        return res;
    }
}

转变一下思维,不是去一个一个求每个子数组的最大最小
而是求所有子数组的最小值之和,所有子数组的最大值之和,然后相减
而在求最小值的时候,如果nums[i]在一个范围内是最小呢,那么在这个范围内包含nums[i]的所有子数组最小值都是num[i],可以根据这个思想找到这个范围
创建左右两边的单调栈,分别处理小于和大于i位置的最小值,就找到了这个范围

但是需要特别注意的是,需要规定一个逻辑大小
也就是说需要规定如果数组中两个数是相同呢,那么下标小的数逻辑上是小的
这样规定以后,所有处理都遵循这个规则,就不会出错

class Solution {
    public long subArrayRanges(int[] nums) {
        //看到数据范围,能想到的最直接的方法就是两层循环找每个子数组的最大最小值
        //先写一下过了

        //接下来就是On的复杂度
        //空间换时间,用优先队列行不行,不太行
        //先用一个数组记录下nums[0]组成的所有子数组的范围
        //然后开始遍历整个数组,如果这个值改变了


        //不会,看题解了
        //首先要想做成On的复杂度,必须先想到 所有子数组最大值和最小值的差
        //就等于所有子数组的最大值减去所有子数组的最小值
        //那么接下来的问题就不是求每个子数组的对应的最大值最小值了,
        //而可以求所有子数组的最小值,然后再求一遍最大值
        //那么,怎么求所有子数组的最小值呢
        //对于nums[i],左边第一个比它小的下标是left,右边第一个比它小的下标是right
        //那么在left到right范围内的所有子数组的最小值都是nums[i],也就是(i - left) * (right - i)
        //然后现在的问题就是找这两个left 和right了
        //这里用的是单调栈,用两个单调栈分别处理两边


        int n = nums.length;
        int[] minleft = new int[n];
        int[] minright = new int[n];
        int[] maxleft = new int[n];
        int[] maxright = new int[n];

        Stack<Integer> minStack = new Stack<Integer>();
        Stack<Integer> maxStack = new Stack<Integer>();

        //处理左边
        for(int i = 0; i < n; i++){
            //如果栈顶元素大于当前元素,弹出
            while(!minStack.isEmpty() && nums[minStack.peek()] > nums[i]){
                minStack.pop();
            }
            //如果栈为空的话,说明左边没有比它小的元素,所以赋值为-1
            if(minStack.isEmpty()){
                minleft[i] = -1;
            }else{
                minleft[i] = minStack.peek();
            }
            minStack.push(i);

            //这里为什么要加等于,因为上面在处理最小值的时候没有加等于,默认的比较规则是如果数值相等
            //那么如果位置在前,那么就小,
            //如果用这个规则统一处理的话,那么这里位置在前同样是小数,所以是小于等于
            while(!maxStack.isEmpty() && nums[maxStack.peek()] <= nums[i]){
                maxStack.pop();
            }
            if(maxStack.isEmpty()){
                maxleft[i] = -1;
            }else{
                maxleft[i] = maxStack.peek();
            }
            maxStack.push(i);
        }
        
        minStack.clear();
        maxStack.clear();
        
        //处理右边
        for(int i = n - 1; i >= 0; i--){
            //如果栈顶元素大于当前元素,弹出
            while(!minStack.isEmpty() && nums[minStack.peek()] >= nums[i]){
                minStack.pop();
            }
            //如果栈为空的话,说明左边没有比它小的元素,所以赋值为-1
            //为了后面方便计算,这里赋值为n
            if(minStack.isEmpty()){
                minright[i] = n;
            }else{
                minright[i] = minStack.peek();
            }
            minStack.push(i);

            while(!maxStack.isEmpty() && nums[maxStack.peek()] < nums[i]){
                maxStack.pop();
            }
            if(maxStack.isEmpty()){
                maxright[i] = n;
            }else{
                maxright[i] = maxStack.peek();
            }
            maxStack.push(i);
        }

        long sumMax = 0;
        long sumMin = 0;
        for(int i = 0; i < n; i++){
            sumMax += nums[i] * (long)(i - maxleft[i]) * (maxright[i] - i);
            sumMin += nums[i] * (long)(i - minleft[i]) * (minright[i] - i);
        }

        return sumMax - sumMin;
    }
}

521. 最长特殊序列 Ⅰ

2022.3.5 每日一题

题目描述

给你两个字符串 a 和 b,请返回 这两个字符串中 最长的特殊序列 。如果不存在,则返回 -1 。

「最长特殊序列」 定义如下:该序列为 某字符串独有的最长子序列(即不能是其他字符串的子序列) 。

字符串 s 的子序列是在从 s 中删除任意数量的字符后可以获得的字符串。

例如,“abc” 是 “aebdc” 的子序列,因为删除 “aebdc” 中斜体加粗的字符得到 “abc” 。 “aebdc” 的子序列还包括 “aebdc” 、 “aeb” 和 “” (空字符串)。

示例 1:

输入: a = “aba”, b = “cdc”
输出: 3
解释: 最长特殊序列可为 “aba” (或 “cdc”),两者均为自身的子序列且不是对方的子序列。

示例 2:

输入:a = “aaa”, b = “bbb”
输出:3
解释: 最长特殊序列是 “aaa” 和 “bbb” 。

示例 3:

输入:a = “aaa”, b = “aaa”
输出:-1
解释: 字符串 a 的每个子序列也是字符串 b 的每个子序列。同样,字符串 b 的每个子序列也是字符串 a 的子序列。

提示:

1 <= a.length, b.length <= 100
a 和 b 由小写英文字母组成

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-uncommon-subsequence-i
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

class Solution {
    public int findLUSlength(String a, String b) {
        //这个题是什么意思呢,就是在a中找到b中没有的最长子序列
        //看起来好像很复杂
        //其实想想,如果两个字符串相同,那么是-1
        //如果两个字符串不相同,那么长度大于等于的肯定不会出现在另一个中
        //所以直接返回长度长的就行了

        int la = a.length();
        int lb = b.length();
        if(la > lb)
            return la;
        else if(lb > la)
            return lb;
        for(int i = 0; i < la; i++){
            if(a.charAt(i) != b.charAt(i))
                return la;
        }
        return -1;
    }
}

2100. 适合打劫银行的日子

2022.3.6 每日一题

题目描述

你和一群强盗准备打劫银行。给你一个下标从 0 开始的整数数组 security ,其中 security[i] 是第 i 天执勤警卫的数量。日子从 0 开始编号。同时给你一个整数 time 。

如果第 i 天满足以下所有条件,我们称它为一个适合打劫银行的日子:

第 i 天前和后都分别至少有 time 天。
第 i 天前连续 time 天警卫数目都是非递增的。
第 i 天后连续 time 天警卫数目都是非递减的。
更正式的,第 i 天是一个合适打劫银行的日子当且仅当:security[i - time] >= security[i - time + 1] >= … >= security[i] <= … <= security[i + time - 1] <= security[i + time].

请你返回一个数组,包含 所有 适合打劫银行的日子(下标从 0 开始)。返回的日子可以 任意 顺序排列。

示例 1:

输入:security = [5,3,3,3,5,6,2], time = 2
输出:[2,3]
解释:
第 2 天,我们有 security[0] >= security[1] >= security[2] <= security[3] <= security[4] 。
第 3 天,我们有 security[1] >= security[2] >= security[3] <= security[4] <= security[5] 。
没有其他日子符合这个条件,所以日子 2 和 3 是适合打劫银行的日子。

示例 2:

输入:security = [1,1,1,1,1], time = 0
输出:[0,1,2,3,4]
解释:
因为 time 等于 0 ,所以每一天都是适合打劫银行的日子,所以返回每一天。

示例 3:

输入:security = [1,2,3,4,5,6], time = 2
输出:[]
解释:
没有任何一天的前 2 天警卫数目是非递增的。
所以没有适合打劫银行的日子,返回空数组。

示例 4:

输入:security = [1], time = 5
输出:[]
解释:
没有日子前面和后面有 5 天时间。
所以没有适合打劫银行的日子,返回空数组。

提示:

1 <= security.length <= 10^5
0 <= security[i], time <= 10^5

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-good-days-to-rob-the-bank
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

左右遍历两次,把满足条件的找出来

class Solution {
    public List<Integer> goodDaysToRobBank(int[] security, int time) {
        //从左到右先找递减的,然后找递增的
        
        int n = security.length;
        Set<Integer> jian = new HashSet<>();
        int temp = 100001;
        int count = -1;
        for(int i = 0; i < n; i++){
            if(security[i] <= temp){
                count++;
            }else{
                count = 0;
            }
            if(count >= time){
                jian.add(i);
            }
            temp = security[i];
        }

        Set<Integer> jia = new HashSet<>();
        temp = 100001;
        count = -1;
        for(int i = n - 1; i >= 0; i--){
            if(security[i] <= temp){
                count++;
            }else{
                count = 0;
            }
            if(count >= time){
                jia.add(i);
            }
            temp = security[i];
        }

        List<Integer> res = new ArrayList<>();
        for(int t : jian){
            if(jia.contains(t))
                res.add(t);
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值