区间子数组个数 - LeetCode 795 - 从左向右与从右向左查找

一、问题描述

        给你一个整数数组 nums 和 两个整数:left 及 right 。找出 nums 中连续、非空且其中最大元素在范围 [left, right] 内的子数组,并返回满足条件的子数组的个数。

        示例:

        输入:nums = [ 2, 1, 4, 3] ; left = 2; right = 2;

        输出:3;

        解释:根据题意,满足的子区间有 [2], [2,1], [3] 共三个;

        原题:LeetCode 795 区间子数组个数

二、解题思路

        1、思路1:固定左端点,从左往右查找

        根据题意,不难发现所求的子区间总是被 “大于 right 的数” 完全分割成互不相交的一个个“一级子区间”,然后在这些子区间中,分割出所求的各个相互之间存在交集的“二级子区间”,这个“二级子区间”的数量即为最终所求,“二级子区间”数量符合一定的规律:假如这个端点介于 [left, right] 之间,那么以他为首,1、2、3、4……为长度向后获取子数组,直到右边界,得到的数量即为这个首部对应的子数组数量,即右边界与他的距离即为所求;倘若这个值小于 left,那么他退化为与下一个(右边)介于 [left, right] 之间的数开头相同(注意这里的“查找下一个”)。

        例如,给定 nums = [2, 1, 1, 2, 4, 3],left = 2,right = 3,求解如下:

        (1)根据大于right的数,进行一级分割:

        大于right的数为4,这个分割点将nums分割为两个“一级子区间”: [2, 1, 1, 2] 和 [3];

        (2)对这两个区间,分别求他们的满足题意的区间数,求解时固定左端点向右查找:

        1>对于区间 [2, 1, 1, 2]:

        以第一个 2 开头的区间均满足题意,包括 [2], [2, 1], [2, 1, 1], [2, 1, 1, 2] 共4个;

        以第一个 1 开头的区间,需要向后包括一个大于 left 的数,即向后查到 [1, 1, 2]共1个;

        以第二个 1 开头的区间与上面相同,向后查到 [1, 2]共1个;

        以第二个 2 开头的区间,与第一个相同,但是后面无元素,向后查到 [2] 共1个;

        2>对于区间 [3]:

        易知只有一个 [3] 区间满足题意;

        综上 1> 2>,共有8个符合题意的子区间。

        2、思路2:固定右端点,从右往左查找

        分析思路1的过程,有一部分时间花在了重复的从左向右查找上(即查找“下一个”),那么,能不能记录左边已经遍历过的状态,然后在后面命中“目标值”的时候直接根据记录的信息得到答案呢?

        第一步的分割仍然是基础,但我们把这一步分割用变量的状态来记录,每当命中一个大于right的值,我们就重新求解一个新区间;求解一个区间时,每当遍历到一个不大于right的值,我们就把他作为右端点,向左去查找符合题意的区间;倘若这个值介于 [left, right] 之间,任意向左(直到到达左边界)的区间均符合题意;倘若这个值小于 left,退化为与上一个(左边)介于 [left, right] 之间的数为右端点相同;这样,我们在前面的遍历就能确定并记录“上一个”,而无需重复查找。

        还是给定上面的条件,nums = [2, 1, 1, 2, 4, 3],left = 2,right = 3,设 i 为当前指针,求解如下:

       i = 0 (2):介于 [left, right] 之间,向左边界求数量:1;

       i = 1 (1):小于left,退化为 i = 0 的情况,数量:1;

       i = 2 (1):小于left,退化为 i = 0 的情况,数量:1;

       i = 3 (2):介于 [left, right] 之间,向左边界求数量:4;

       i = 4 (4):大于right,向后开启下一循环;

       i = 5 (3):介于 [left, right] 之间,向左边界上一个左边界为(i = 4)求数量:1;

        共 1 + 1 + 1 + 4 + 1 = 8 个;

三、C++代码

        1、思路1:固定左端点,从左往右查找

#include <iostream>
#include <vector>

using namespace std;

//方法1:固定左端点,向后查找
class Solution1 {
public:
    int getSubNum(vector<int>& nums, int l, int r, int left) {
        int sub_res = 0;
        for (int i = l; i < r; i++) {
            if (nums[i] < left) {
                //Searching behind until get legal num.
                int j = i;
                while (j < r && nums[j] < left) j++;
                sub_res += (r - j);//if (j < r) 
            }
            else
            sub_res += (r - i);
        }
        return sub_res;
    }

    int numSubarrayBoundedMax(vector<int>& nums, int left, int right) {
        int l = 0;
        int res = 0;
        bool find_left = true;
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (find_left) {
                if (nums[i] <= right) {
                    l = i;
                    find_left = false;
                }
            }
            else {
                if (nums[i] > right) {
                    find_left = true;
                    res += getSubNum(nums, l, i, left);
                    //cout << "db A: " << "le: " << l << " r:" << i 
                    //    << " addition:" << getSubNum(nums, l, i, left) << " res:" << res << endl;
                }
            }
        }
        if (!find_left) {
            res += getSubNum(nums, l, size, left);
            //cout << "db B: " << "le: " << l << " r(size):" << size
            //    << " addition:" << getSubNum(nums, l, size, left) << " res:" << res << endl;
        }
        return res;
    }
};


int main()
{
    Solution s;
    vector<int> nums1 = { 2,1,4,3 };
    vector<int> nums2 = { 2,9,2,5,6 };
    vector<int> nums3 = { 2,2,1,1,2,1,2,4,2,3 };
    vector<int> nums4 = { 73,55,36,5,55,14,9,7,72,52 };
    cout << "Debug: " << endl
        //<< "input: { 2,1,4,3 }              2  3 ; Excepted: 3  ; Actually: " << s.numSubarrayBoundedMax(nums1, 2, 3) << endl
        //<< "input: { 2,9,2,5,6 }            2  8 ; Excepted: 7  ; Actually: " << s.numSubarrayBoundedMax(nums2, 2, 8) << endl
        //<< "input: { 2,2,1,1,2,1,2,4,2,3 }  2  3 ; Excepted: 20 ; Actually: " << s.numSubarrayBoundedMax(nums3, 2, 3) << endl
        << "input: { 73,55,36,5,55,14,9,7,72,52 }  32  69 ; Excepted: 22 ; Actually: " << s.numSubarrayBoundedMax(nums4, 32, 69) << endl;
    return 0;
}

        2、思路2:固定右端点,从右往左查找

#include <iostream>
#include <vector>

using namespace std;

//方法2:固定右端点,向前查找
class Solution {
public:
    int numSubarrayBoundedMax(vector<int>& nums, int left, int right) {
        int last2 = -1, last1 = -1;
        int res = 0;
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (nums[i] > right) {      //status 2
                last2 = i;
                last1 = -1;
            }
            else if (nums[i] < left) {  //status 0
                if (last1 != -1) res += (last1 - last2);
            }
            else {                      //status 1
                last1 = i;
                res += (last1 - last2);
            }
        }
        return res;
    }
};

int main()
{
    Solution s;
    vector<int> nums1 = { 2,1,4,3 };
    vector<int> nums2 = { 2,9,2,5,6 };
    vector<int> nums3 = { 2,2,1,1,2,1,2,4,2,3 };
    vector<int> nums4 = { 73,55,36,5,55,14,9,7,72,52 };
    cout << "Debug: " << endl
        //<< "input: { 2,1,4,3 }              2  3 ; Excepted: 3  ; Actually: " << s.numSubarrayBoundedMax(nums1, 2, 3) << endl
        //<< "input: { 2,9,2,5,6 }            2  8 ; Excepted: 7  ; Actually: " << s.numSubarrayBoundedMax(nums2, 2, 8) << endl
        //<< "input: { 2,2,1,1,2,1,2,4,2,3 }  2  3 ; Excepted: 20 ; Actually: " << s.numSubarrayBoundedMax(nums3, 2, 3) << endl
        << "input: { 73,55,36,5,55,14,9,7,72,52 }  32  69 ; Excepted: 22 ; Actually: " << s.numSubarrayBoundedMax(nums4, 32, 69) << endl;
    return 0;
}

四、复杂度

        方法2优化为:时间复杂度 O(N) ; 空间复杂度O(N)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值