每日抑题 1109. 航班预订统计

本文介绍了一种利用Java编程解决航班预订统计问题的方法,通过差分数组巧妙地实现了区间修改和单点查询的高效操作,避免了线段树的复杂性。解法分析了区间求和的不同策略,展示了如何在面对这类问题时优先选择合适的数据结构。
摘要由CSDN通过智能技术生成

1109. 航班预订统计

难度:中等
语言:java

题目内容

这里有 n 个航班,它们分别从 1 到 n 进行编号。

有一份航班预订表 bookings ,表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi] 意味着在从 firsti 到 lasti (包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。

请你返回一个长度为 n 的数组 answer,其中 answer[i] 是航班 i 上预订的座位总数。

在这里插入图片描述

解法分析

很久没有记录每日一题了,今天这个比较有意思,所以写一下

第一反应肯定是暴力法,n给定了,那只要遍历过去,把对应的位置加上bookings[x][2]的值就可以了,并且复杂度也不是非常高,最大是o(n*n),据说是暴力也能过,我就没尝试了

第二个解法比较有意思,差分法,这类题目都属于,区间求和,在一段区间内求和

针对不同的题目,我们有不同的方案可以选择(假设我们有一个数组):

  • 数组不变,求区间和:「前缀和」、「树状数组」、「线段树」
  • 多次修改某个数,求区间和:「树状数组」、「线段树」
  • 多次整体修改某个区间,求区间和:「线段树」、「树状数组」(看修改区间的数据范围)
  • 多次将某个区间变成同一个数,求区间和:「线段树」、「树状数组」(看修改区间的数据范围)
  • 数组不变,区间查询:前缀和、树状数组、线段树;
  • 数组单点修改,区间查询:树状数组、线段树;
  • 数组区间修改,单点查询:差分、线段树;
  • 数组区间修改,区间查询:线段树。

这样看来,「线段树」能解决的问题是最多的,那我们是不是无论什么情况都写「线段树」呢?

答案并不是,而且恰好相反,只有在我们遇到第 4 类问题,不得不写「线段树」的时候,我们才考虑线段树。

因为「线段树」代码很长,而且常数很大,实际表现不算很好。我们只有在不得不用的时候才考虑「线段树」。

总结一下,我们应该按这样的优先级进行考虑:

简单求区间和,用「前缀和」
多次将某个区间变成同一个数,用「线段树」
其他情况,用「树状数组」

  • 前缀和:在之前的文章里面写过,建立一个数组记录int[n],对应为前n个数的和,要求第n个数到n+k个数的和,就用sum(n+k)-sum(n)。
  • 树状数组:类似于树的结构,具体代码模板如下
 
 // 上来先把三个方法写出来
{
    int[] tree;
    int lowbit(int x) {
        return x & -x;
    }
    // 查询前缀和的方法
    int query(int x) {
        int ans = 0;
        for (int i = x; i > 0; i -= lowbit(i)) ans += tree[i];
        return ans;
    }
    // 在树状数组 x 位置中增加值 u
    void add(int x, int u) {
        for (int i = x; i <= n; i += lowbit(i)) tree[i] += u;
    }
}

// 初始化「树状数组」,要默认数组是从 1 开始
{
    for (int i = 0; i < n; i++) add(i + 1, nums[i]);
}

// 使用「树状数组」:
{   
    void update(int i, int val) {
        // 原有的值是 nums[i],要使得修改为 val,需要增加 val - nums[i]
        add(i + 1, val - nums[i]); 
        nums[i] = val;
    }
    
    int sumRange(int l, int r) {
        return query(r + 1) - query(l);
    }
}
  • 线段树:比较复杂,在这里能不使用就不使用,之后遇到我会再次记录

以上来自@宫水三叶大神的总结以及我的一些个人理解

回到本题来,这是一道数组区间修改,单点查询的题目,可以用差分去解决,也可以用线段树。
差分数组对应的概念是前缀和数组,对于数组 [1,2,2,4],其差分数组为 [1,1,0,2],差分数组的第 i 个数即为原数组的第 i-1 个元素和第 i 个元素的差值,也就是说我们对差分数组求前缀和即可得到原数组。

差分数组的性质是,当我们希望对原数组的某一个区间 [l,r]施加一个增量inc 时,差分数组 d 对应的改变是:d[l]增加inc,d[r+1]减少 inc。这样对于区间的修改就变为了对于两个位置的修改。并且这种修改是可以叠加的,即当我们多次对原数组的不同区间施加不同的增量,我们只要按规则修改差分数组即可。

class Solution {
    public int[] corpFlightBookings(int[][] bookings, int n) {
        int[] nums = new int[n];
        for (int[] booking : bookings) {
            nums[booking[0] - 1] += booking[2];
            if (booking[1] < n) {
                nums[booking[1]] -= booking[2];
            }
        }
        for (int i = 1; i < n; i++) {
            nums[i] += nums[i - 1];
        }
        return nums;
    }
}

总结

差分这个解法需要熟悉才能做,我第一次看答案的时候都在犹豫,修改部分加inc,后一部分减inc,那两个之间不就差了两倍的inc,怎么能做到差值是inc呢
其实主要要弄清楚前缀和和差分的关系,差分的前缀和为具体数值,如果前面的数加inc,而后面不减,则这段内容均为加inc,正是一加一减,才保证了区间内增加,而不是从一个数向后全部增加。
希望下次遇到这题的时候能有意识选择正确的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值