leetcode 2970/2972 统计移除递增子数组的数目

摆烂了挺长时间的,重新开始写题解

题目大义

给你一个数组,然后让你在里面选择一个连续的子数组,然后删掉它,剩下的部分是一个严格递增的数组,问你有多少种选法。(eazy版,n是 1 − 50 1-50 150,hard版是 1 − 1 e 5 1-1e5 11e5
原地址

eazy版本基本思路

题目让你选择删掉的数组,其实我们也可以把它变成选择剩下的数组,我们把这个数组叫做 a a a,把题目所给出的数组叫 b b b,长度为 n n n a a a 总共有三种情况。

  • 是一个前缀数组(不会的话可以去度娘那里问一问前缀和后缀的概念),即: a = b 0 , b 1 , b 2 . . . a=b_0,b_1,b_2... a=b0,b1,b2...
  • 是一个后缀数组,即: a = b i , b i + 1 , b i + 2 . . . b n − 1 a=b_i,b_{i+1},b_{i+2}...b_{n-1} a=bi,bi+1,bi+2...bn1
  • 是一个前缀数组加上一个后缀数组,就是删除了 b b b 中间的一部分,然后剩下的数组

我们先考虑前两种情况,我们可以轻易的使用一个 O ( n ) O(n) O(n) 的操作求出来。我们以第一种情况举例,我们从 i = 1 i=1 i=1 开始枚举,看看最长严格递增数组多长。如下:

for (int i = 0 ; i < n - 1; i++)
            if (nums[i] < nums[i + 1]) // 这里的nums表示题目给出的数组b
                fword++; // 表示前缀并且严格单调递增的数组的最后一个元素的位置
            else
                break;

接下来,显然第一种情况的 a a a,是这个前缀最长严格递增数组的一个前缀。即第一种情况,有 f w o r d + 1 fword + 1 fword+1 种可能(数组下标从0开始)。假如前缀最长严格递增数组是 [ 1 , 2 , 3 , 4 , 5 ] [1, 2, 3, 4, 5] [1,2,3,4,5],那么第一种数组有: [ 1 ] , [ 1 , 2 ] , [ 1 , 2 , 3 ] , [ 1 , 2 , 3 , 4 ] , [ 1 , 2 , 3 , 4 , 5 ] [1],[1,2],[1,2,3],[1,2,3,4],[1,2,3,4,5] [1],[1,2],[1,2,3],[1,2,3,4],[1,2,3,4,5] 五种。

第二种情况和第一种类似:

        for (int i = n - 1; i > 0; i--)
            if (nums[i] > nums[i - 1]) 
                bword--; // 表示后缀并且严格单调递增的数组的第一个元素的位置
            else
                break;

只是换了一个方向而已。

比较难的是第三种情况,剩下的数组是一个严格递增的前缀数组和一个严格递增的后缀数组,假设我们现在有两个指针 i , j i,j i,j j j j 表示删除中间部分后前半部分数组的最后一个元素的位置,而 i i i 表示删除后,后半部分数组的第一个元素的位置,那么 i i i j j j 要满足以下条件,我们就可以说发现了一个合法的数组 a a a

  • i > j i > j i>j
  • b [ i ] > b [ j ] b[i] > b[j] b[i]>b[j]
  • i ≥ b w o r d i \ge bword ibword
  • j ≤ f w o r d j \le fword jfword

对于简单版本来说,只要双重for循环,然后判断有多少对合法的 ( i , j ) (i,j) (i,j) 就可以了。

代码

const int maxn = 55;

class Solution {
public:
    int incremovableSubarrayCount(vector<int>& nums)
    {
        int n = nums.size();
        int fword = 0, bword = n - 1;
        for (int i = 0 ; i < n - 1; i++) // 第一种情况
            if (nums[i] < nums[i + 1])
                fword++;
            else
                break;

        for (int i = n - 1; i > 0; i--) // 第二种情况
            if (nums[i] > nums[i - 1]) 
                bword--;
            else
                break;

        int ans = 1; // a 可以是一个全空的数组
        ans += fword + 1 + (n - bword);
        if (bword == 0) // 当题目给出的数组b本身就是一个严格递增数组的时候,我们统计的所有情况里面,有什么都没删除的情况,即a=b,前缀一种,后缀一种,需要删掉
            ans -= 2;
        for (int i = 2; i < n; i++) // 第三种情况,因为中间删除了数组,所以这里枚举i和j的时候,i-j>=2
            for (int j = 0; j < i - 1; j++)
            {
                if (nums[j] < nums[i] && i >= bword && j <= fword) // 看看是不是符合条件
                    ans++;
            }
        return ans;
    }
};

hard版本大致思路

对于前两种情况,我们按照之前的方法做就可以了,对于第三种情况,我们可以加一个树状数组,我们只要看 0 , 1... i − 2 0,1...{i-2} 0,1...i2 这些位置上有多少个比 b [ i ] b[i] b[i] 小的值就可以了。但是这里还有一个问题:
在这里插入图片描述
b 数组里面的值很大,怎么办呢?
其实我们直接离散化(看我下面的代码你就知道离散化是什么了)就可以了,数组本身的值其实不重要,重要的是值与值之间的相对大小。

代码

const int maxn = 1e5 + 5;

class Solution {
#define lowbit(i) i&(-i)
public:
    vector<int> t; // 树状数组
    int n;

    void update(int x) // 更新操作
    {
        for (int i = x; i <= n; i += lowbit(i))
            t[i] += 1;
    }

    int get_ans(int x) // 查找操作
    {
        int ans = 0;
        for (int i = x; i; i -= lowbit(i))
            ans += t[i];
        return ans;
    }

    long long incremovableSubarrayCount(vector<int>& nums)
    {
        vector<int> tmp(nums);
        sort(tmp.begin(), tmp.end());
        n = nums.size();
        t.resize(n + 1, 0);
        for (int i = 0; i < n; i++) // 离散化操作,可以输出一下,看看结果
            nums[i] = lower_bound(tmp.begin(), tmp.end(), nums[i]) - tmp.begin() + 1;
        int fword = 0, bword = n - 1;
        for (int i = 0 ; i < n - 1; i++) // 第一种情况
            if (nums[i] < nums[i + 1])
                fword++;
            else
                break;
        for (int i = n - 1; i > 0; i--) // 第二种情况
            if (nums[i] > nums[i - 1]) 
                bword--;
            else
                break;
        long long ans = 1;
        ans += fword + (n - bword) + 1;
        if (bword == 0)
            ans -= 2;
        update(nums[0]); // 我们的 i 从 2 开始枚举,0 是有可能成为 j 的
        for (int i = 2; i < n; i++)
        {
            if (i >= bword) // 看看 0 到 i-2 这些位置里面,有多少值是小于 nums[i] 的
                ans += get_ans(nums[i] - 1);
            if (i - 1 <= fword) // 对于枚举的下一个位置,即 i+1 来说,i 是不可能出现在剩下的数组里面的,因为中间必须删除一些东西,所以只把 i-1 这个位置的值加到树状数组里面就行
                update(nums[i - 1]);
        }
        return ans;
    }
};

结果

在这里插入图片描述
很慢,但是能过就行呗[doge]








作者能力有限,如果有任何错误之处,还请各位指教。(~ ̄▽ ̄)~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值