LeetCode 2104. 子数组范围和题解

2104. 子数组范围和题解

题目来源:2104. 子数组范围和

2022.03.04 每日一题

每日一题专栏地址:LeetCode 每日一题题解更新中❤️💕

今天的这道题目,我第一个想法就是使用回溯,然后不断更新最大值以及最小值,然后计算差值的和

法一:回溯

创建 backtrack 函数进行回溯,不停的更新最大值与最小值,然后统计差值的和,最后返回res

具体代码以及注释如下:

class Solution {
public:
    // 创建变量统计差值
    long long res = 0;

    long long subArrayRanges(vector<int> &nums) {
        // 进行回溯
        backtrack(nums, 0, INT32_MIN, INT32_MAX, 1);
        return res;
    }

    // 创建一个队列,来记录数组
    stack<int> st;

    // nums 数组
    // index 索引
    // _max 当前最大值
    // _min 当前最小值
    // tag 判断当前是否继续进行
    void backtrack(vector<int> &nums, int index, int _max, int _min, bool tag) {
        // 如果栈是空的,或者是1 ,差值都为 0 
        // 可以直接跳过
        if (st.size() > 1) {
            res += (_max - _min);
        }

        // 从 index 开始遍历数组
        for (int i = index; i < nums.size(); i++) {
            // 使用两个变量来存放当前的极值
            int minTemp = _min, maxTemp = _max;
            
            // 更新极值
            _min = min(_min, nums[i]);
            _max = max(_max, nums[i]);
            // 将当前索引对应的值放入栈中
            st.push(nums[i]);

            // 进行下一步递归
            backtrack(nums, i + 1, _max, _min, 0);
            
            // 递归结束以后,将当前索引对应的值移除栈中
            st.pop();

            // 将极值返还
            // 进行回溯
            _min = minTemp;
            _max = maxTemp;

            // 退出
            if (!tag)
                return;
        }
    }
};
class Solution {
    // 创建变量统计差值
    public long res = 0;

    public long subArrayRanges(int[] nums) {
        // 进行回溯
        backtrack(nums, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
        return res;
    }

    // 创建一个队列,来记录数组
    Stack st = new Stack();

    // nums 数组
    // index 索引
    // _max 当前最大值
    // _min 当前最小值
    // tag 判断当前是否继续进行
    void backtrack(int[] nums, int index, int _max, int _min, boolean tag) {
        // 如果栈是空的,或者是1 ,差值都为 0
        // 可以直接跳过
        if (st.size() > 1) {
            res += (_max - _min);
        }

        // 从 index 开始遍历数组
        for (int i = index; i < nums.length; i++) {
            // 使用两个变量来存放当前的极值
            int minTemp = _min, maxTemp = _max;

            // 更新极值
            _min = Math.min(_min, nums[i]);
            _max = Math.max(_max, nums[i]);
            // 将当前索引对应的值放入栈中
            st.push(nums[i]);

            // 进行下一步递归
            backtrack(nums, i + 1, _max, _min, false);

            // 递归结束以后,将当前索引对应的值移除栈中
            st.pop();
            
            // 将极值返还
            // 进行回溯
            _min = minTemp;
            _max = maxTemp;

            // 退出
            if (!tag) return;
        }
    }
}

虽然这个思路比较好像,但是回溯的效率不是很好看,耗时太长,而且空间占用也大,那么能不能有更好的方法?

请添加图片描述
请添加图片描述

那么是不是可以使用单调栈

法二:单调栈

我们可以使用单调栈来维护一个每一个入栈元素左侧比它大或者小的第一个元素。

首先我们需要知道会有多少了区间 m,最后的答案就包含了 m 组区间里面的极值的差值

我们就可以统计每一个数字 num 成为区间极值的次数,成为最大值 t1 次,成为最小值 t2 次,最后统计 ( t 1 − t 2 ) ∗ n u m (t1-t2)*num (t1t2)num的总和就能获得最后的答案。

现在问题就从 统计每个子集的极值之差之和 演变成为计算 每个数字成为极大值的次数,以及最小值的次数。

设 一个数字 num 的索引为 i

  • 找到作为区间最大值的次数:

    i的左右寻找最近的一个大于 num 的值,分别其索引记录为pq,因此 n u m s [ p ] nums[p] nums[p] n u m num num之间就有 i − p − 1 i-p-1 ip1个数字,左端点就有 i − p i-p ip 个选择,同理,右端点就有 q − i q-i qi 个选择,由此我们可以算出,我们可以得到的区间有 ( i − p ) ∗ ( q − i ) (i-p)*(q-i) (ip)(qi)个。

  • 找到作为区间最小值的次数:

    原理同作为区间最大值的次数的方法,找到左右最近的一个小于 n u m num num​ 的值,分别记录其索引,最后同样可以算出可得到的区间有 ( i − p ) ∗ ( q − i ) (i-p)*(q-i) (ip)(qi)个。

    我们可以创建一个_max[]数组_min[],分别统计 num 为区间极值的次数

    class Solution {
    public:
        int n;
    
        long long subArrayRanges(vector<int> &nums) {
            // 记录数组 nums 的长度
            this->n = nums.size();
    
            // 创建数组统计成为i极值的次数
            vector<long long> _max = getCount(nums, 0), _min = getCount(nums, 1);
    
            long res = 0;
            // 开始对数组进行遍历查询
            for (int i = 0; i < n; i++)
                res += (_max[i] - _min[i]) * nums[i];
            return res;
    
        }
    
        vector<long long> getCount(vector<int> &nums, bool isMin) {
            // 创建两个数组,分别统计 num 两边的 p 与 q
            vector<long long> l(n), r(n);
            stack<int> st;
    
            for (int i = 0; i < n; i++) {
                // && 后面部分判断是计算最大值次数还是最小值次数
                // 不停的寻找
                while (!st.empty() && (isMin ? (nums[st.top()] >= nums[i]) : (nums[st.top()] <= nums[i])))
                    st.pop();
                l[i] = st.empty() ? -1 : st.top();
                st.push(i);
            }
            // 清空栈
            while (!st.empty()) st.pop();
    
            // 另一侧同理
            for (int i = n - 1; i >= 0; i--) {
                // && 后面部分判断是计算最大值次数还是最小值次数
                // 不停的寻找
                while (!st.empty() && (isMin ? (nums[st.top()] > nums[i]) : (nums[st.top()] < nums[i])))
                    st.pop();
                r[i] = st.empty() ? n : st.top();
                st.push(i);
            }
    
            // 现在已经统计好 nums[i] 的极值次数
            // 依次计算即可
            vector<long long> ans;
            for (int i = 0; i < n; i++)
                ans.push_back((i - l[i]) * (r[i] - i));
            return ans;
        }
    };
    
    import java.util.Stack;
    
    class Solution {
        int n;
    
        public long subArrayRanges(int[] nums) {
            // 记录数组 nums 的长度
            n = nums.length;
    
            // 创建数组统计成为i极值的次数
            long[] _max = getCount(nums, false), _min = getCount(nums, true);
    
            long res = 0;
            // 开始对数组进行遍历查询
            for (int i = 0; i < n; i++)
                res += (_max[i] - _min[i]) * nums[i];
            return res;
        }
    
        long[] getCount(int[] nums, boolean isMin) {
            // 创建两个数组,分别统计 num 两边的 p 与 q
            long[] l = new long[n], r = new long[n];
    
            Stack<Integer> st = new Stack<>();
    
            for (int i = 0; i < n; i++) {
                // && 后面部分判断是计算最大值次数还是最小值次数
                // 不停的寻找
                while (!st.empty() && (isMin ? (nums[st.peek()] >= nums[i]) : (nums[st.peek()] <= nums[i]))) st.pop();
                l[i] = st.empty() ? -1 : st.peek();
                st.push(i);
            }
            // 清空栈
            st.clear();
            // 另一侧同理
            for (int i = n - 1; i >= 0; i--) {
                // && 后面部分判断是计算最大值次数还是最小值次数
                // 不停的寻找
                while (!st.empty() && (isMin ? (nums[st.peek()] > nums[i]) : (nums[st.peek()] < nums[i]))) st.pop();
                r[i] = st.empty() ? n : st.peek();
                st.push(i);
            }
    
            // 现在已经统计好 nums[i] 的极值次数
            // 依次计算即可
            long[] ans = new long[n];
            for (int i = 0; i < n; i++)
                ans[i] = (i - l[i]) * (r[i] - i);
            return ans;
        }
    }
    

    这样就比原来的效率好得多啦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值