leetcode 区间问题

本文详细介绍了LeetCode中涉及区间问题的算法,包括二维前缀和、动态规划、贪心策略、滑动窗口等。通过具体题目,如304题、995题和300题,讲解如何利用这些方法解决区间求和、翻转次数和最长递增子序列等问题,并提供了解题总结和技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

304. 二维区域和检索 - 矩阵不可变【前缀和】

给定一个二维矩阵 matrix,以下类型的多个请求:

计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。
实现 NumMatrix 类:

NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
int sumRegion(int row1, int col1, int row2, int col2) 返回左上角 (row1, col1) 、右下角 (row2, col2) 的子矩阵的元素总和。

示例 1:

输入:
[“NumMatrix”,“sumRegion”,“sumRegion”,“sumRegion”]
[[[[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]],[2,1,4,3],[1,1,2,2],[1,2,2,4]]
输出:
[null, 8, 11, 12]

解释:
NumMatrix numMatrix = new NumMatrix([[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]]);
numMatrix.sumRegion(2, 1, 4, 3); // return 8 (红色矩形框的元素总和)
numMatrix.sumRegion(1, 1, 2, 2); // return 11 (绿色矩形框的元素总和)
numMatrix.sumRegion(1, 2, 2, 4); // return 12 (蓝色矩形框的元素总和)

提示:

m == matrix.length
n == matrix[i].length
1 <= m, n <= 200
-105 <= matrix[i][j] <= 105
0 <= row1 <= row2 < m
0 <= col1 <= col2 < n
最多调用 104 次 sumRegion 方法

二维前缀和

class NumMatrix {
public:
    vector<vector<int>> sum;
    NumMatrix(vector<vector<int>>& matrix) {
        sum=matrix;
        int n=matrix.size();
        int m=matrix[0].size();
        for(int i=0;i<n;i++){
            int res=0;
            for(int j=0;j<m;j++){
                res+=matrix[i][j];
                if(i)sum[i][j]=sum[i-1][j]+res;
                else sum[i][j]=res;
            }
        }
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        if(!row1 && !col1)return sum[row2][col2];
        if(!row1) return sum[row2][col2]-sum[row2][col1-1];
        if(!col1) return sum[row2][col2]-sum[row1-1][col2]; 
        return sum[row2][col2]-sum[row2][col1-1]-sum[row1-1][col2]+sum[row1-1][col1-1];
        
    }
};

/**
 * Your NumMatrix object will be instantiated and called as such:
 * NumMatrix* obj = new NumMatrix(matrix);
 * int param_1 = obj->sumRegion(row1,col1,row2,col2);
 */

二维前缀和

class NumMatrix {
public:
    vector<vector<int>> sums;

    NumMatrix(vector<vector<int>>& matrix) {
        int m = matrix.size();
        if (m > 0) {
            int n = matrix[0].size();
            sums.resize(m + 1, vector<int>(n + 1));
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    sums[i + 1][j + 1] = sums[i][j + 1] + sums[i + 1][j] - sums[i][j] + matrix[i][j];
                }
            }
        }
    }

    int sumRegion(int row1, int col1, int row2, int col2) {
        return sums[row2 + 1][col2 + 1] - sums[row1][col2 + 1] - sums[row2 + 1][col1] + sums[row1][col1];
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/range-sum-query-2d-immutable/solution/er-wei-qu-yu-he-jian-suo-ju-zhen-bu-ke-b-2z5n/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

一维前缀和

class NumMatrix {
    int[][] sums;

    public NumMatrix(int[][] matrix) {
        int m = matrix.length;
        if (m > 0) {
            int n = matrix[0].length;
            sums = new int[m][n + 1];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    sums[i][j + 1] = sums[i][j] + matrix[i][j];
                }
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        int sum = 0;
        for (int i = row1; i <= row2; i++) {
            sum += sums[i][col2 + 1] - sums[i][col1];
        }
        return sum;
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/range-sum-query-2d-immutable/solution/er-wei-qu-yu-he-jian-suo-ju-zhen-bu-ke-b-2z5n/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

模板

// 预处理前缀和数组
{
    sum.resize(n+1, vector<int>(m+1,0));
    // 预处理除前缀和数组(模板部分)
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            // 当前格子(和) = 上方的格子(和) + 左边的格子(和) - 左上角的格子(和) + 当前格子(值)【和是指对应的前缀和,值是指原数组中的值】
            sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + matrix[i-1][j-1];
        }
    }
}
    
// 首先我们要令左上角为 (x1, y1) 右下角为 (x2, y2)
// 计算 (x1, y1, x2, y2) 的结果
{
    // 前缀和是从 1 开始,原数组是从 0 开始,上来先将原数组坐标全部 +1,转换为前缀和坐标
    x1++; y1++; x2++; y2++;
    // 记作 22 - 12 - 21 + 11,然后 不减,减第一位,减第二位,减两位
    // 也可以记作 22 - 12(x - 1) - 21(y - 1) + 11(x y 都 - 1)
    ans = sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1];

}

作者:AC_OIer
链接:https://leetcode-cn.com/problems/range-sum-query-2d-immutable/solution/xia-ci-ru-he-zai-30-miao-nei-zuo-chu-lai-ptlo/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

总结

  1. 分别是对每一行计算一维前缀和,以及对整个矩阵计算二维前缀和。
  2. 下标从1开始比较好处理

995. K 连续位的最小翻转次数【贪心、差分、滑动窗口】!!!

在仅包含 0 和 1 的数组 A 中,一次 K 位翻转包括选择一个长度为 K 的(连续)子数组,同时将子数组中的每个 0 更改为 1,而每个 1 更改为 0。

返回所需的 K 位翻转的最小次数,以便数组没有值为 0 的元素。如果不可能,返回 -1。

示例 1:

输入:A = [0,1,0], K = 1
输出:2
解释:先翻转 A[0],然后翻转 A[2]。
示例 2:

输入:A = [1,1,0], K = 2
输出:-1
解释:无论我们怎样翻转大小为 2 的子数组,我们都不能使数组变为 [1,1,1]。
示例 3:

输入:A = [0,0,0,1,0,1,1,0], K = 3
输出:3
解释:
翻转 A[0],A[1],A[2]: A变成 [1,1,1,1,0,1,1,0]
翻转 A[4],A[5],A[6]: A变成 [1,1,1,1,1,0,0,0]
翻转 A[5],A[6],A[7]: A变成 [1,1,1,1,1,1,1,1]

提示:

1 <= A.length <= 30000
1 <= K <= A.length

贪心差分

class Solution {
public:
    int minKBitFlips(vector<int>& nums, int k) {
        int n=nums.size();
        vector<int> diff(n+1,0);
        int ans=0;
        int sum=0;
        for(int i=0;i<n;i++){
            sum+=diff[i];
            if((sum+nums[i])%2==0){
                if(i+k>n)return -1;
                ans++;
                sum++;
                diff[i+k]--;
            }
        }
        return ans;
    }
};
  1. 时间复杂度O(n)

滑动窗口

class Solution {
public:
    int minKBitFlips(vector<int>& nums, int k) {
        int n = nums.size();
        int ans = 0, revCnt = 0;
        for (int i = 0; i < n; ++i) {
            if (i >= k && nums[i - k] > 1) {
                revCnt ^= 1;
                nums[i - k] -= 2; // 复原数组元素,若允许修改数组 nums,则可以省略
            }
            if (nums[i] == revCnt) {
                if (i + k > n) {
                    return -1;
                }
                ++ans;
                revCnt ^= 1;
                nums[i] += 2;
            }
        }
        return ans;
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/minimum-number-of-k-consecutive-bit-flips/solution/k-lian-xu-wei-de-zui-xiao-fan-zhuan-ci-s-bikk/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  1. 时间复杂度O(n)
  2. 空间复杂度O(1)

总结

  1. 得会观察样例,我觉得贪心还是比较巧妙的
  2. 为什么「一遇到 0 就马上进行翻转」这样的做法得到的是最优解?
    本质上是在证明当我在处理第 k 个位置的 0 的时候,前面 k - 1 个位置不存在 0,接下来要如何进行操作,可使得总的翻转次数最小。 作者:AC_OIer
  3. 回顾差分的代码,当遍历到位置 i 时,若能知道位置 i-k上发生了翻转操作,便可以直接修改 revCnt,从而去掉 diff 数组。优化空间复杂度,就是倒着来

354. 俄罗斯套娃信封问题【朴素dp,二分dp,树状数组】

给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。

当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。

请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。

注意:不允许旋转信封。

示例 1:

输入:envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出:3
解释:最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。
示例 2:

输入:envelopes = [[1,1],[1,1],[1,1]]
输出:1

提示:

1 <= envelopes.length <= 5000
envelopes[i].length == 2
1 <= wi, hi <= 104

朴素dp

bool cmp(vector<int> a,vector<int> b){
    if(a[0]==b[0])return a[1]<=b[1];
    return a[0]<b[0];
}

class Solution {
public:
    vector<int>dp;
    int n;
    int ans;

    int getLongestPath(vector<vector<int>>& envelopes, int dep){
        if(dep>=n)return 0;
        if(dp[dep])return dp[dep];

        int res=0;
        for(int i=dep+1;i<n;i++){
            if(envelopes[i][0]>envelopes[dep][0] && envelopes[i][1]>envelopes[dep][1])
            res=max(res,getLongestPath(envelopes,i));
        }

        res++;
        dp[dep]=res;
        return res;
    }

    
    int maxEnvelopes(vector<vector<int>>& envelopes) {
        n=envelopes.size();
        dp.resize(n);
        ans=0;

        sort(envelopes.begin(),envelopes.end(),cmp);

        for(int i=0;i<n;i++){
            dp[i]=getLongestPath(envelopes,i);
            if(dp[i]>ans)ans=dp[i];
        }

        return ans;

    }
};

时间复杂度O(n2)

二分+dp

class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& envelopes) {
        if (envelopes.empty()) {
            return 0;
        }
        
        int n = envelopes.size();
        sort(envelopes.begin(), envelopes.end(), [](const auto& e1, const auto& e2) {
            return e1[0] < e2[0] || (e1[0] == e2[0] && e1[1] > e2[1]);
        });

        vector<int> f = {envelopes[0][1]};
        for (int i = 1; i < n; ++i) {
            if (int num = envelopes[i][1]; num > f.back()) {
                f.push_back(num);
            }
            else {
                auto it = lower_bound(f.begin(), f.end(), num);
                *it = num;
            }
        }
        return f.size();
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/russian-doll-envelopes/solution/e-luo-si-tao-wa-xin-feng-wen-ti-by-leetc-wj68/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

时间复杂度O(nlogn)

树状数组

class Solution {
    int[] tree;
    int lowbit(int x) {
        return x & -x;
    }

    public int maxEnvelopes(int[][] es) {
        int n = es.length;
        if (n == 0) return n;

        // 由于我们使用了 g 记录高度,因此这里只需将 w 从小到达排序即可
        Arrays.sort(es, (a, b)->a[0] - b[0]);

        // 先将所有的 h 进行离散化
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < n; i++) set.add(es[i][1]);
        int cnt = set.size();
        int[] hs = new int[cnt];
        int idx = 0;
        for (int i : set) hs[idx++] = i;
        Arrays.sort(hs);
        for (int i = 0; i < n; i++) es[i][1] = Arrays.binarySearch(hs, es[i][1]) + 1;

        // 创建树状数组
        tree = new int[cnt + 1];

        // f(i) 为考虑前 i 个物品,并以第 i 个物品为结尾的最大值
        int[] f = new int[n]; 
        int ans = 1;
        for (int i = 0, j = 0; i < n; i++) {
            // 对于 w 相同的数据,不更新 tree 数组
            if (es[i][0] != es[j][0]) {
                // 限制 j 不能越过 i,确保 tree 数组中只会出现第 i 个信封前的「历史信封」
                while (j < i) {
                    for (int u = es[j][1]; u <= cnt; u += lowbit(u)) {
                        tree[u] = Math.max(tree[u], f[j]);
                    }
                    j++;
                }
            }
            f[i] = 1;
            for (int u = es[i][1] - 1; u > 0; u -= lowbit(u)) {
                f[i] = Math.max(f[i], tree[u] + 1);
            }
            ans = Math.max(ans, f[i]);
        }
        return ans;
    }
}

作者:AC_OIer
链接:https://leetcode-cn.com/problems/russian-doll-envelopes/solution/zui-chang-shang-sheng-zi-xu-lie-bian-xin-6s8d/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

时间复杂度O(nlogn)

在「二分 + 动态规划」的解法中,我们通过「二分」来优化找第 i 个文件的前一个文件过程。

这个过程同样能通过「树状数组」来实现。

首先仍然是对 w 进行排序,然后使用「树状数组」来维护 h 维度的前缀最大值。

对于 h 的高度,我们只关心多个信封之间的大小关系,而不关心具体相差多少,我们需要对 h 进行离散化。

通常使用「树状数组」都需要进行离散化,尤其是这里我们本身就要使用 O(n)O(n) 的空间来存储 dp 值。

作者:AC_OIer

300. 最长递增子序列【朴素dp、贪心+二分】!!!

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

1 <= nums.length <= 2500
-104 <= nums[i] <= 104

进阶:

你可以设计时间复杂度为 O(n2) 的解决方案吗?
你能将算法的时间复杂度降低到 O(n log(n)) 吗?

贪心+二分

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int len=1;
        int n=nums.size();

        vector<int> lis(n+1);
        lis[len-1]=nums[0];

        for(int i=1;i<n;i++){
            if(nums[i]>lis[len-1])lis[len++]=nums[i];
            else{
                int l=0,r=len-1,mid,x=0;
                while(l<=r){
                    mid=l+(r-l)/2;
                    if(lis[mid]==nums[i]){
                        x=mid;
                        break;
                    }
                    else if(lis[mid]>nums[i]){
                        x=mid;
                        r=mid-1;
                    }else{
                        l=mid+1;
                    }
                }
                lis[x]=nums[i];
            }
        }
        return len;
    }
};

思路

  1. 朴素dp时间复杂度为O(n2),dp[i]=max(dp[j]+1,dp[i]), num[i]>num[j] i>j
  2. 贪心+二分时间复杂度为O(nlogn)
  3. 贪心十分巧妙,如果num[i]大于队列最后的,那么就直接加入队列
  4. 否则,在队列中找到最小的大于等于num[i]的进行替换,因为是有顺序的,所以二分查找
  5. 其实一开始看到单调,我又想到了栈,但栈显然不能实现最长
  6. 而这种贪心并没有改变之前找到的最长序列,只是“替换”,就是以该数结尾的最长序列的位置,在同等长度下,若是有后面的数,显然后面的数的选择范围更大了。
  7. 所以lis[i-1]的意思其实是:长度为i的递增序列第i个位置最小的数

1893. 检查是否区域内所有整数都被覆盖【差分】

给你一个二维整数数组 ranges 和两个整数 left 和 right 。每个 ranges[i] = [starti, endi] 表示一个从 starti 到 endi 的 闭区间 。

如果闭区间 [left, right] 内每个整数都被 ranges 中 至少一个 区间覆盖,那么请你返回 true ,否则返回 false 。

已知区间 ranges[i] = [starti, endi] ,如果整数 x 满足 starti <= x <= endi ,那么我们称整数x 被覆盖了。

示例 1:

输入:ranges = [[1,2],[3,4],[5,6]], left = 2, right = 5
输出:true
解释:2 到 5 的每个整数都被覆盖了:

  • 2 被第一个区间覆盖。
  • 3 和 4 被第二个区间覆盖。
  • 5 被第三个区间覆盖。
    示例 2:

输入:ranges = [[1,10],[10,20]], left = 21, right = 21
输出:false
解释:21 没有被任何一个区间覆盖。

提示:

1 <= ranges.length <= 50
1 <= starti <= endi <= 50
1 <= left <= right <= 50

差分

class Solution {
public:
    bool isCovered(vector<vector<int>>& ranges, int left, int right) {
        vector<int> cover(52,0);

        for(auto r:ranges){
            cover[r[0]]+=1;
            cover[r[1]+1]-=1;
        }

        for(int i=1;i<left;i++){
            cover[i]+=cover[i-1];
        }

        for(int i=left;i<=right;i++){
            cover[i]+=cover[i-1];
            if(cover[i]<=0)return 0;
        }

        return 1;

    }
};

时间复杂度O(n+l)

1310. 子数组异或查询【前缀和、树状数组】

有一个正整数数组 arr,现给你一个对应的查询数组 queries,其中 queries[i] = [Li, Ri]。

对于每个查询 i,请你计算从 Li 到 Ri 的 XOR 值(即 arr[Li] xor arr[Li+1] xor … xor arr[Ri])作为本次查询的结果。

并返回一个包含给定查询 queries 所有结果的数组。

示例 1:

输入:arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]]
输出:[2,7,14,8]
解释:
数组中元素的二进制表示形式是:
1 = 0001
3 = 0011
4 = 0100
8 = 1000
查询的 XOR 值为:
[0,1] = 1 xor 3 = 2
[1,2] = 3 xor 4 = 7
[0,3] = 1 xor 3 xor 4 xor 8 = 14
[3,3] = 8
示例 2:

输入:arr = [4,8,2,10], queries = [[2,3],[1,3],[0,0],[0,3]]
输出:[8,0,4,4]

提示:

1 <= arr.length <= 3 * 10^4
1 <= arr[i] <= 10^9
1 <= queries.length <= 3 * 10^4
queries[i].length == 2
0 <= queries[i][0] <= queries[i][1] < arr.length

前缀和

class Solution {
public:
    vector<int> xorQueries(vector<int>& arr, vector<vector<int>>& queries) {
        int n=arr.size();
        int m=queries.size();
        for(int i=1;i<n;i++){
            arr[i]=arr[i]^arr[i-1];
        }
        vector<int> ans(m);
        for(int i=0;i<m;i++){
            //cout<<i<<endl;
            if(queries[i][0])ans[i]=arr[queries[i][1]]^arr[queries[i][0]-1];
            else ans[i]=arr[queries[i][1]];
        }
        return ans;
    }
};
  1. 时间复杂度O(n+m)
  2. 主要是个数学问题:x^x=0, 0^x=x

lower_bound

头文件:#include

时间复杂度:一次查询O(log n),n为数组长度。

功能:查找非递减序列 [first,last)第一个大于或等于某个元素的位置。

返回值:如果找到返回找到元素的地址,否则返回last的地址。(这样不注意的话会越界,小心)

用法:int t=lower_bound(a+l,a+r,key)-a;(a是数组)。

auto it = lower_bound(f.begin(), f.end(), num);
*it = num;

upper_bound:

功能:查找非递减序列[first,last) 内第一个大于某个元素的位置。

返回值:如果找到返回找到元素的地址否则返回last的地址。(同样这样不注意的话会越界,小心)

用法:int t=upper_bound(a+l,a+r,key)-a;(a是数组)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值