2187.完成旅途的最少时间

目标

给你一个数组 time ,其中 time[i] 表示第 i 辆公交车完成 一趟旅途 所需要花费的时间。

每辆公交车可以 连续 完成多趟旅途,也就是说,一辆公交车当前旅途完成后,可以 立马开始 下一趟旅途。每辆公交车 独立 运行,也就是说可以同时有多辆公交车在运行且互不影响。

给你一个整数 totalTrips ,表示所有公交车 总共 需要完成的旅途数目。请你返回完成 至少 totalTrips 趟旅途需要花费的 最少 时间。

示例 1:

输入:time = [1,2,3], totalTrips = 5
输出:3
解释:
- 时刻 t = 1 ,每辆公交车完成的旅途数分别为 [1,0,0] 。
  已完成的总旅途数为 1 + 0 + 0 = 1 。
- 时刻 t = 2 ,每辆公交车完成的旅途数分别为 [2,1,0] 。
  已完成的总旅途数为 2 + 1 + 0 = 3 。
- 时刻 t = 3 ,每辆公交车完成的旅途数分别为 [3,1,1] 。
  已完成的总旅途数为 3 + 1 + 1 = 5 。
所以总共完成至少 5 趟旅途的最少时间为 3 。

示例 2:

输入:time = [2], totalTrips = 1
输出:2
解释:
只有一辆公交车,它将在时刻 t = 2 完成第一趟旅途。
所以完成 1 趟旅途的最少时间为 2 。

说明:

  • 1 <= time.length <= 10^5
  • 1 <= time[i], totalTrips <= 10^7

思路

一趟旅行可以选择 n 辆公交车,time[i] 表示第 i 辆公交完成旅途的时间,同一时间每辆公交车可以独立地运行,完成一趟旅途后立刻开始下一趟。问完成 totalTrips 次旅途最少需要花费多少时间。

使用二分法尝试最少花费的时间 least,完成旅途的次数为 sum(least/time[i])。查找的上界为 10^14(当只有一辆车,且它完成一趟旅行需要耗时 10^7,总共需要完成 10^7 趟旅行)。

可以优化的点是上下界的取值,我们可以求出公交车完成旅行所需时间的最大与最小值,假设所有公交完成时间均为最小值 min,那么平均每辆车需要完成 ⌈totalTrips / n⌉ 次旅途,时间的下界为⌈totalTrips / n⌉ * min,同理,上界为 ⌈totalTrips / n⌉ * max

代码


/**
 * @date 2024-10-05 21:34
 */
public class MinimumTime2187 {

    public long minimumTime_v1(int[] time, int totalTrips) {
        int n = time.length;
        int max = 0;
        int min = Integer.MAX_VALUE;
        for (int i : time) {
            min = Math.min(i, min);
            max = Math.max(i, max);
        }
        long average = ((long) totalTrips - 1) / n + 1;
        long l = average * min;
        long r = average * max;
        long m = l + (r - l) / 2;
        while (l <= r) {
            if (check(time, totalTrips, m)) {
                r = m - 1;
            } else {
                l = m + 1;
            }
            m = l + (r - l) / 2;
        }
        return l;
    }

    public long minimumTime(int[] time, int totalTrips) {
        long l = 0L, r = 100000000000000L;
        long m = l + (r - l) / 2;
        while (l <= r) {
            if (check(time, totalTrips, m)) {
                r = m - 1;
            } else {
                l = m + 1;
            }
            m = l + (r - l) / 2;
        }
        return l;
    }

    private boolean check(int[] time, int totalTrips, long least) {
        long cnt = 0L;
        for (int i : time) {
            cnt += least / i;
            if (cnt >= totalTrips) {
                return true;
            }
        }
        return false;
    }
}

性能

优化上下界并没有明显的差距,并且还增加了编码的复杂度。

1227.飞机座位分配概率

目标

有 n 位乘客即将登机,飞机正好有 n 个座位。第一位乘客的票丢了,他随便选了一个座位坐下。

剩下的乘客将会:

  • 如果他们自己的座位还空着,就坐到自己的座位上,
  • 当他们自己的座位被占用时,随机选择其他座位

第 n 位乘客坐在自己的座位上的概率是多少?

示例 1:

输入:n = 1
输出:1.00000
解释:第一个人只会坐在自己的位置上。

示例 2:

输入: n = 2
输出: 0.50000
解释:在第一个人选好座位坐下后,第二个人坐在自己的座位上的概率是 0.5。

说明:

  • 1 <= n <= 10^5

思路

飞机上有 n 个座位,第一位乘客的票丢了,它随机找了一个位置坐下。后面的乘客如果发现自己的位置空着就直接入座,否则随机找一个位置坐下,问最后一个人坐在自己位置上的概率是多少?

这是一个纯数学问题,使用数学归纳法可以证明概率为:n == 1 ? 1 : 0.5

代码

性能

1928.规定时间内到达终点的最小花费

目标

一个国家有 n 个城市,城市编号为 0 到 n - 1 ,题目保证 所有城市 都由双向道路 连接在一起 。道路由二维整数数组 edges 表示,其中 edges[i] = [xi, yi, timei] 表示城市 xi 和 yi 之间有一条双向道路,耗费时间为 timei 分钟。两个城市之间可能会有多条耗费时间不同的道路,但是不会有道路两头连接着同一座城市。

每次经过一个城市时,你需要付通行费。通行费用一个长度为 n 且下标从 0 开始的整数数组 passingFees 表示,其中 passingFees[j] 是你经过城市 j 需要支付的费用。

一开始,你在城市 0 ,你想要在 maxTime 分钟以内 (包含 maxTime 分钟)到达城市 n - 1 。旅行的 费用 为你经过的所有城市 通行费之和 (包括 起点和终点城市的通行费)。

给你 maxTime,edges 和 passingFees ,请你返回完成旅行的 最小费用 ,如果无法在 maxTime 分钟以内完成旅行,请你返回 -1 。

示例 1:

输入:maxTime = 30, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]
输出:11
解释:最优路径为 0 -> 1 -> 2 -> 5 ,总共需要耗费 30 分钟,需要支付 11 的通行费。

示例 2:

输入:maxTime = 29, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]
输出:48
解释:最优路径为 0 -> 3 -> 4 -> 5 ,总共需要耗费 26 分钟,需要支付 48 的通行费。
你不能选择路径 0 -> 1 -> 2 -> 5 ,因为这条路径耗费的时间太长。

示例 3:

输入:maxTime = 25, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]
输出:-1
解释:无法在 25 分钟以内从城市 0 到达城市 5 。

说明:

  • 1 <= maxTime <= 1000
  • n == passingFees.length
  • 2 <= n <= 1000
  • n - 1 <= edges.length <= 1000
  • 0 <= xi, yi <= n - 1
  • 1 <= timei <= 1000
  • 1 <= passingFees[j] <= 1000
  • 图中两个节点之间可能有多条路径。
  • 图中不含有自环。

思路

// todo

代码

性能

1870.准时到达的列车最小时速

目标

给你一个浮点数 hour ,表示你到达办公室可用的总通勤时间。要到达办公室,你必须按给定次序乘坐 n 趟列车。另给你一个长度为 n 的整数数组 dist ,其中 dist[i] 表示第 i 趟列车的行驶距离(单位是千米)。

每趟列车均只能在整点发车,所以你可能需要在两趟列车之间等待一段时间。

  • 例如,第 1 趟列车需要 1.5 小时,那你必须再等待 0.5 小时,搭乘在第 2 小时发车的第 2 趟列车。

返回能满足你准时到达办公室所要求全部列车的 最小正整数 时速(单位:千米每小时),如果无法准时到达,则返回 -1 。

生成的测试用例保证答案不超过 10^7 ,且 hour 的 小数点后最多存在两位数字 。

示例 1:

输入:dist = [1,3,2], hour = 6
输出:1
解释:速度为 1 时:
- 第 1 趟列车运行需要 1/1 = 1 小时。
- 由于是在整数时间到达,可以立即换乘在第 1 小时发车的列车。第 2 趟列车运行需要 3/1 = 3 小时。
- 由于是在整数时间到达,可以立即换乘在第 4 小时发车的列车。第 3 趟列车运行需要 2/1 = 2 小时。
- 你将会恰好在第 6 小时到达。

示例 2:

输入:dist = [1,3,2], hour = 2.7
输出:3
解释:速度为 3 时:
- 第 1 趟列车运行需要 1/3 = 0.33333 小时。
- 由于不是在整数时间到达,故需要等待至第 1 小时才能搭乘列车。第 2 趟列车运行需要 3/3 = 1 小时。
- 由于是在整数时间到达,可以立即换乘在第 2 小时发车的列车。第 3 趟列车运行需要 2/3 = 0.66667 小时。
- 你将会在第 2.66667 小时到达。

示例 3:

输入:dist = [1,3,2], hour = 1.9
输出:-1
解释:不可能准时到达,因为第 3 趟列车最早是在第 2 小时发车。

说明:

  • n == dist.length
  • 1 <= n <= 10^5
  • 1 <= dist[i] <= 10^5
  • 1 <= hour <= 10^9
  • hours 中,小数点后最多存在两位数字

思路

从家到办公室需要依次乘坐 n 趟列车,列车只在整点发车,已知每趟车的行驶距离 dist[i],问在给定通勤时间 hour 内到达办公室,列车的最低时速是多少,取正整数,如果无法按时到达返回 -1

我们假设时速为 v,那么到达办公室的时间为 cost = Σ⌈dist[i]/v⌉ + dist[n-1]/v 前面 n - 1 趟车通勤时间需要考虑等车时间,所以要向上取整,最后一趟车则不需要向上取整。我们只需要满足cost <= hour 即可。由于时速需要取正整数,那么 v 也应该向上取整。

这道题的难点在于如何在 v 未知的情况下,向上取整后再求和,没办法直接计算。只能搜索解空间了,我们可以估算出 v 的取值范围,然后使用二分查找代入式子计算并与 hour 比较。v 的下界为 Σdist[i]/hour,上界是 max(dist[i]) * 100,这相当于是一趟车最大的距离除以最小的时间,如果这个速度还赶不上,那就赶不上了。

求和的时间复杂度为 O(n)n 最大 10^5 ,距离最大 10^5 有可能溢出,应使用 long 类型。二分查找的复杂度为 O(nlogv)v 最大值为 10^10log2(10^10) ≈ 33.2,总的规模为 10 ^ 6 可行。

代码


/**
 * @date 2024-10-02 21:44
 */
public class MinSpeedOnTime1870 {

    public int minSpeedOnTime(int[] dist, double hour) {
        long sum = 0;
        for (int value : dist) {
            sum += value;
        }
        int v = (int) Math.ceil(sum / hour - 0.5);
        long l = v, r = 200 * sum, m = l + (r - l) / 2;
        while (l <= r) {
            if (check(dist, hour, m)) {
                r = m - 1;
            } else {
                l = m + 1;
            }
            m = l + (r - l) / 2;
        }
        return l > 200 * sum ? -1 : (int)l;
    }

    private boolean check(int[] dist, double hour, long m) {
        int n = dist.length;
        double cost = 0;
        for (int i = 0; i < n - 1; i++) {
            cost += Math.ceil((double) dist[i] / m);
        }
        cost += (double) dist[n - 1] / m;
        return cost <= hour;
    }

}

性能

983.最低票价

目标

在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。

火车票有 三种不同的销售方式 :

  • 一张 为期一天 的通行证售价为 costs[0] 美元;
  • 一张 为期七天 的通行证售价为 costs[1] 美元;
  • 一张 为期三十天 的通行证售价为 costs[2] 美元。

通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张 为期 7 天 的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回 你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费 。

示例 1:

输入:days = [1,4,6,7,8,20], costs = [2,7,15]
输出:11
解释: 
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。
在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, ..., 9 天生效。
在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。
你总共花了 $11,并完成了你计划的每一天旅行。

示例 2:

输入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
输出:17
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划: 
在第 1 天,你花了 costs[2] = $15 买了一张为期 30 天的通行证,它将在第 1, 2, ..., 30 天生效。
在第 31 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 31 天生效。 
你总共花了 $17,并完成了你计划的每一天旅行。

说明:

  • 1 <= days.length <= 365
  • 1 <= days[i] <= 365
  • days 按顺序严格递增
  • costs.length == 3
  • 1 <= costs[i] <= 1000

思路

有一个严格递增的出行计划表 daysdays[i] 表示计划在这一天出行。出行需要持有通行证,通行证有三种,1 天有效期,7 天有效期,30 天有效期,价格各不相同。求完成出行计划所需的最低花费。

定义 dp[i] 表示截止到第 i 天的最小花费,初始化数组大小为 days[n - 1] + 1

  • 如果第 i 天需要出行,dp[i] = Math.min(dp[i - 1] + cost[0], dp[i - 7] + cost[1], dp[i - 30] + cost[2])
  • 否则,dp[i] = dp[i - 1]

网友最快题解定义 dp[i] 为旅行了 i 天的最小花费,这样与 days[i] 的数据范围无关,仅与出行天数 days.length 有关。

代码


/**
 * @date 2024-10-01 20:43
 */
public class MincostTickets983 {

    /**
     * 针对 前面方法 的优化
     * 去掉初始化 dp[i] 为截止到第i天 使用一天票的总花费,
     * 使用数组记录是否出行
     */
    public int mincostTickets_v1(int[] days, int[] costs) {
        int n = days.length;
        int end = days[n - 1];
        int[] dp = new int[end + 1];
        boolean[] isTravel = new boolean[end + 1];
        for (int day : days) {
            isTravel[day] = true;
        }
        for (int i = 1; i <= end; i++) {
            int tmp7 = Math.max(0, i - 7);
            int tmp30 = Math.max(0, i - 30);
            if (isTravel[i]) {
                dp[i] = Math.min(dp[i - 1] + costs[0], dp[tmp7] + costs[1]);
                dp[i] = Math.min(dp[i], dp[tmp30] + costs[2]);
            } else {
                dp[i] = dp[i - 1];
            }
        }
        return dp[end];
    }

    public int mincostTickets(int[] days, int[] costs) {
        int n = days.length;
        int end = days[n - 1];
        int[] dp = new int[end + 1];
        int last = 0;
        int index = 0;
        Set<Integer> set = new HashSet<>(n);
        for (int day : days) {
            set.add(day);
            while (index < day) {
                dp[index++] = last;
            }
            dp[day] += last + costs[0];
            last = dp[day];
        }
        for (int i = 1; i <= end; i++) {
            int tmp7 = Math.max(0, i - 7);
            int tmp30 = Math.max(0, i - 30);
            if (!set.contains(i)) {
                dp[i] = dp[i - 1];
            } else {
                dp[i] = Math.min(dp[i - 1] + costs[0], dp[tmp7] + costs[1]);
                dp[i] = Math.min(dp[i], dp[tmp30] + costs[2]);
            }
        }
        return dp[end];
    }

}

性能

  • 去掉 dp[i] 的初始化,刚开始写的是将其初始化为截止到第 i 天使用一天票的总花费
  • 使用数组记录是否出行

1845.座位预约管理系统

目标

请你设计一个管理 n 个座位预约的系统,座位编号从 1 到 n 。

请你实现 SeatManager 类:

  • SeatManager(int n) 初始化一个 SeatManager 对象,它管理从 1 到 n 编号的 n 个座位。所有座位初始都是可预约的。
  • int reserve() 返回可以预约座位的 最小编号 ,此座位变为不可预约。
  • void unreserve(int seatNumber) 将给定编号 seatNumber 对应的座位变成可以预约。

示例 1:

输入:
["SeatManager", "reserve", "reserve", "unreserve", "reserve", "reserve", "reserve", "reserve", "unreserve"]
[[5], [], [], [2], [], [], [], [], [5]]
输出:
[null, 1, 2, null, 2, 3, 4, 5, null]

解释:
SeatManager seatManager = new SeatManager(5); // 初始化 SeatManager ,有 5 个座位。
seatManager.reserve();    // 所有座位都可以预约,所以返回最小编号的座位,也就是 1 。
seatManager.reserve();    // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。
seatManager.unreserve(2); // 将座位 2 变为可以预约,现在可预约的座位为 [2,3,4,5] 。
seatManager.reserve();    // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。
seatManager.reserve();    // 可以预约的座位为 [3,4,5] ,返回最小编号的座位,也就是 3 。
seatManager.reserve();    // 可以预约的座位为 [4,5] ,返回最小编号的座位,也就是 4 。
seatManager.reserve();    // 唯一可以预约的是座位 5 ,所以返回 5 。
seatManager.unreserve(5); // 将座位 5 变为可以预约,现在可预约的座位为 [5] 。

说明:

  • 1 <= n <= 10^5
  • 1 <= seatNumber <= n
  • 每一次对 reserve 的调用,题目保证至少存在一个可以预约的座位。
  • 每一次对 unreserve 的调用,题目保证 seatNumber 在调用函数前都是被预约状态。
  • 对 reserve 和 unreserve 的调用 总共 不超过 10^5 次。

思路

设计一个座位预约系统,初始化 n 个座位,可以预约尚未预约的编号最小的座位,支持取消预约操作。

核心是解决取消预约后如何获取编号最小值的问题,可以使用最小堆维护剩余座位。

网友指出,初始化的复杂度与 n 有关,当 n 规模过大时会超时。可以使用最小堆维护取消预订的座位,显然取消预订的座位编号一定小于未被预定的座位编号。记录已预定出去的座位最高位 max,如果堆不为空则取堆顶元素,否则返回 max + 1

代码


/**
 * @date 2024-09-30 21:46
 */
public  class SeatManager {

    private PriorityQueue<Integer> q;

    public SeatManager(int n) {
        q = new PriorityQueue<>();
        for (int i = 1; i <= n; i++) {
            q.offer(i);
        }
    }

    public int reserve() {
        return q.poll();
    }

    public void unreserve(int seatNumber) {
        q.offer(seatNumber);
    }

}

性能

2073.买票需要的时间

目标

有 n 个人前来排队买票,其中第 0 人站在队伍 最前方 ,第 (n - 1) 人站在队伍 最后方 。

给你一个下标从 0 开始的整数数组 tickets ,数组长度为 n ,其中第 i 人想要购买的票数为 tickets[i] 。

每个人买票都需要用掉 恰好 1 秒 。一个人 一次只能买一张票 ,如果需要购买更多票,他必须走到 队尾 重新排队(瞬间 发生,不计时间)。如果一个人没有剩下需要买的票,那他将会 离开 队伍。

返回位于位置 k(下标从 0 开始)的人完成买票需要的时间(以秒为单位)。

示例 1:

输入:tickets = [2,3,2], k = 2
输出:6
解释:

队伍一开始为 [2,3,2],第 k 个人以下划线标识。
在最前面的人买完票后,队伍在第 1 秒变成 [3,2,1]。
继续这个过程,队伍在第 2 秒变为[2,1,2]。
继续这个过程,队伍在第 3 秒变为[1,2,1]。
继续这个过程,队伍在第 4 秒变为[2,1]。
继续这个过程,队伍在第 5 秒变为[1,1]。
继续这个过程,队伍在第 6 秒变为[1]。第 k 个人完成买票,所以返回 6。

示例 2:

输入:tickets = [5,1,1,1], k = 0
输出:8
解释:
队伍一开始为 [5,1,1,1],第 k 个人以下划线标识。
在最前面的人买完票后,队伍在第 1 秒变成 [1,1,1,4]。
继续这个过程 3 秒,队伍在第 4 秒变为[4]。
继续这个过程 4 秒,队伍在第 8 秒变为[]。第 k 个人完成买票,所以返回 8。

说明:

  • n == tickets.length
  • 1 <= n <= 100
  • 1 <= tickets[i] <= 100
  • 0 <= k < n

思路

n 个人在排队买票,每人每次限买一张,每买一张票耗时 1 秒,如果要买多张需要重新排队。现在已知 tickets 数组,表示第 i 个人想买票的数量,问第 k 个人完成买票需要多长时间(仅记录买票时间)。

由于数据量不大,直接的解法是用队列模拟排队。假设所有人都要买 m 张票,kn - 1,那么时间复杂度为 O(mn)

考虑整体计算,当第 k 个人排到队尾时,将其前面所有人剩余要买的 tickets[i] 减去 第 k 个人剩余需要买的票数 r = tickets[k] - 1,那么剩余的等待时间为 n * r + (tickets[i] 为负的和)。时间复杂度为 O(n)

网友有一个解法是客户不动,让售票员从头到尾卖票,如果顾客已经完成买票则跳过,这也算是模拟的另一种思路。

代码


/**
 * @date 2024-09-29 8:45
 */
public class TimeRequiredToBuy2073 {

    /**
     * 整体计算
     */
    public int timeRequiredToBuy_v1(int[] tickets, int k) {
        int n = tickets.length;
        int res = 0;
        for (int i = 0; i <= k; i++) {
            tickets[i]--;
            res++;
        }
        int r = tickets[k];
        res += r * n;
        for (int ticket : tickets) {
            int i = ticket - r;
            if (i < 0) {
                res += i;
            }
        }
        return res;
    }

    /**
     * 模拟
     */
    public int timeRequiredToBuy(int[] tickets, int k) {
        ArrayDeque<Integer> q = new ArrayDeque<>();
        int n = tickets.length;
        for (int i = 0; i < n; i++) {
            q.offer(i);
        }
        int res = 0;
        while (!q.isEmpty()) {
            int i = q.poll();
            int remainder = --tickets[i];
            res++;
            if (remainder > 0) {
                q.offer(i);
            } else if (i == k) {
                break;
            }
        }
        return res;
    }
}

性能

2286.以组为单位订音乐会的门票

目标

一个音乐会总共有 n 排座位,编号从 0 到 n - 1 ,每一排有 m 个座椅,编号为 0 到 m - 1 。你需要设计一个买票系统,针对以下情况进行座位安排:

  • 同一组的 k 位观众坐在 同一排座位,且座位连续 。
  • k 位观众中 每一位 都有座位坐,但他们 不一定 坐在一起。

由于观众非常挑剔,所以:

  • 只有当一个组里所有成员座位的排数都 小于等于 maxRow ,这个组才能订座位。每一组的 maxRow 可能 不同 。
  • 如果有多排座位可以选择,优先选择 最小 的排数。如果同一排中有多个座位可以坐,优先选择号码 最小 的。

请你实现 BookMyShow 类:

  • BookMyShow(int n, int m) ,初始化对象,n 是排数,m 是每一排的座位数。
  • int[] gather(int k, int maxRow) 返回长度为 2 的数组,表示 k 个成员中 第一个座位 的排数和座位编号,这 k 位成员必须坐在 同一排座位,且座位连续 。换言之,返回最小可能的 r 和 c 满足第 r 排中 [c, c + k - 1] 的座位都是空的,且 r <= maxRow 。如果 无法 安排座位,返回 [] 。
  • boolean scatter(int k, int maxRow) 如果组里所有 k 个成员 不一定 要坐在一起的前提下,都能在第 0 排到第 maxRow 排之间找到座位,那么请返回 true 。这种情况下,每个成员都优先找排数 最小 ,然后是座位编号最小的座位。如果不能安排所有 k 个成员的座位,请返回 false 。

示例 1:

输入:
["BookMyShow", "gather", "gather", "scatter", "scatter"]
[[2, 5], [4, 0], [2, 0], [5, 1], [5, 1]]
输出:
[null, [0, 0], [], true, false]

解释:
BookMyShow bms = new BookMyShow(2, 5); // 总共有 2 排,每排 5 个座位。
bms.gather(4, 0); // 返回 [0, 0]
                  // 这一组安排第 0 排 [0, 3] 的座位。
bms.gather(2, 0); // 返回 []
                  // 第 0 排只剩下 1 个座位。
                  // 所以无法安排 2 个连续座位。
bms.scatter(5, 1); // 返回 True
                   // 这一组安排第 0 排第 4 个座位和第 1 排 [0, 3] 的座位。
bms.scatter(5, 1); // 返回 False
                   // 总共只剩下 1 个座位。

说明:

  • 1 <= n <= 5 * 10^4
  • 1 <= m, k <= 10^9
  • 0 <= maxRow <= n - 1
  • gather 和 scatter 总 调用次数不超过 5 * 10^4 次。

思路

// todo 今天没时间做了

代码

性能

2516.每种字符至少取K个

目标

给你一个由字符 'a'、'b'、'c' 组成的字符串 s 和一个非负整数 k 。每分钟,你可以选择取走 s 最左侧 还是 最右侧 的那个字符。

你必须取走每种字符 至少 k 个,返回需要的 最少 分钟数;如果无法取到,则返回 -1 。

示例 1:

输入:s = "aabaaaacaabc", k = 2
输出:8
解释:
从 s 的左侧取三个字符,现在共取到两个字符 'a' 、一个字符 'b' 。
从 s 的右侧取五个字符,现在共取到四个字符 'a' 、两个字符 'b' 和两个字符 'c' 。
共需要 3 + 5 = 8 分钟。
可以证明需要的最少分钟数是 8 。

示例 2:

输入:s = "a", k = 1
输出:-1
解释:无法取到一个字符 'b' 或者 'c',所以返回 -1 。

说明:

  • 1 <= s.length <= 10^5
  • s 仅由字母 'a'、'b'、'c' 组成
  • 0 <= k <= s.length

思路

有一个仅由 abc 组成的字符串,每一分钟可以选择访问两端(最左侧/最右侧)的未访问字符,求访问 abc 每种字符至少 k 次最少需要多少分钟。

可以将字符串拼接到末尾,问题转化为滑动窗口问题,求使窗口内包含k个 abc 的最小窗口长度。但这里有一个限制,窗口必须包含首尾交界点,就像有一个轴在窗口里面,窗口可以左右延展。

我们可以先向左侧滑动,找到满足条件的最左侧下标,然后枚举左端点向右滑动,直到左边界越过交界点,枚举的过程中延展右边界直到满足条件。

官网题解中使用了逆向思维,先求出字符串中 abc 的个数,窗口在中间滑动,窗口内的 abc 是不选的,使用总数减去窗口内的计数判断是否满足条件。

代码


/**
 * @date 2024-09-27 9:12
 */
public class TakeCharacters2516 {

    public int takeCharacters(String s, int k) {
        if (k == 0) {
            return 0;
        }
        int res = Integer.MAX_VALUE;
        int[] cnt = new int[3];
        char[] chars = s.toCharArray();
        int n = chars.length;
        int l = n - 1;
        while (l >= 0 && (cnt[0] < k || cnt[1] < k || cnt[2] < k)) {
            cnt[chars[l--] - 'a']++;
        }
        if (cnt[0] < k || cnt[1] < k || cnt[2] < k) {
            return -1;
        }
        l++;
        int r = n;
        int doubleN = 2 * chars.length;
        for (; l <= n; l++) {
            while (r < doubleN && (cnt[0] < k || cnt[1] < k || cnt[2] < k)) {
                cnt[chars[r++ % n] - 'a']++;
            }
            res = Math.min(res, r - l);
            if (l == n) {
                break;
            }
            cnt[chars[l] - 'a']--;
        }
        return res;
    }

}

性能

2535.数组元素和与数字和的绝对差

目标

给你一个正整数数组 nums 。

  • 元素和 是 nums 中的所有元素相加求和。
  • 数字和 是 nums 中每一个元素的每一数位(重复数位需多次求和)相加求和。

返回 元素和 与 数字和 的绝对差。

注意:两个整数 x 和 y 的绝对差定义为 |x - y| 。

示例 1:

输入:nums = [1,15,6,3]
输出:9
解释:
nums 的元素和是 1 + 15 + 6 + 3 = 25 。
nums 的数字和是 1 + 1 + 5 + 6 + 3 = 16 。
元素和与数字和的绝对差是 |25 - 16| = 9 。

示例 2:

输入:nums = [1,2,3,4]
输出:0
解释:
nums 的元素和是 1 + 2 + 3 + 4 = 10 。
nums 的数字和是 1 + 2 + 3 + 4 = 10 。
元素和与数字和的绝对差是 |10 - 10| = 0 。

说明:

  • 1 <= nums.length <= 2000
  • 1 <= nums[i] <= 2000

思路

直接根据题意计算即可。由于元素和一定大于数字和,不用分开计算。

代码


/**
 * @date 2024-09-26 8:45
 */
public class DifferenceOfSum2535 {

    public int differenceOfSum(int[] nums) {
        int res = 0;
        for (int num : nums) {
            res += num;
            while (num > 0) {
                res -= num % 10;
                num /= 10;
            }
        }
        return res;
    }

}

性能