【题解】—— LeetCode一周小结40

简介: LeetCode每日一道一周小结40

🌟欢迎来到 我的博客 —— 探索技术的无限可能!

🌟博客的简介(文章目录)


【题解】—— 每日一道题目栏


上接:【题解】—— LeetCode一周小结39

30.座位预约管理系统

题目链接: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 <= 105

1 <= seatNumber <= n

每一次对 reserve 的调用,题目保证至少存在一个可以预约的座位。

每一次对 unreserve 的调用,题目保证 seatNumber 在调用函数前都是被预约状态。

对 reserve 和 unreserve 的调用 总共 不超过 105 次。

题解:

方法:维护可预约的座位

       

class SeatManager {
    private final PriorityQueue<Integer> available = new PriorityQueue<>();
    public SeatManager(int n) {
        for (int i = 1; i <= n; i++) {
            available.add(i);
        }
    }
    public int reserve() {
        return available.poll();
    }
    public void unreserve(int seatNumber) {
        available.add(seatNumber);
    }
}

2024.10

1.最低票价

题目链接: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

题解:

方法:递归 记忆化搜索 双指针 动态规划

       

class Solution {
    public int mincostTickets(int[] days, int[] costs) {
        int lastDay = days[days.length - 1];
        boolean[] isTravel = new boolean[lastDay + 1];
        for (int d : days) {
            isTravel[d] = true;
        }
        int[] memo = new int[lastDay + 1];
        return dfs(lastDay, isTravel, costs, memo);
    }
    private int dfs(int i, boolean[] isTravel, int[] costs, int[] memo) {
        if (i <= 0) {
            return 0;
        }
        if (memo[i] > 0) { // 之前计算过
            return memo[i];
        }
        if (!isTravel[i]) {
            return memo[i] = dfs(i - 1, isTravel, costs, memo);
        }
        return memo[i] = Math.min(dfs(i - 1, isTravel, costs, memo) + costs[0],
                         Math.min(dfs(i - 7, isTravel, costs, memo) + costs[1],
                                  dfs(i - 30, isTravel, costs, memo) + costs[2]));
    }
}

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

题目链接:1870. 准时到达的列车最小时速

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

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

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

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

生成的测试用例保证答案不超过 107 ,且 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 <= 105

1 <= dist[i] <= 105

1 <= hour <= 109

hours 中,小数点后最多存在两位数字

题解:

方法:数学 二分查找

       

class Solution {
    public int minSpeedOnTime(int[] dist, double hour) {
        int n = dist.length;
        long h100 = Math.round(hour * 100); // 下面不会用到任何浮点数
        long delta = h100 - (n - 1) * 100;
        if (delta <= 0) { // 无法到达终点
            return -1;
        }
        int maxDist = 0;
        long sumDist = 0;
        for (int d : dist) {
            maxDist = Math.max(maxDist, d);
            sumDist += d;
        }
        if (h100 <= n * 100) { // 特判
            // 见题解中的公式
            return Math.max(maxDist, (int) ((dist[n - 1] * 100 - 1) / delta + 1));
        }
        int left = (int) ((sumDist * 100 - 1) / h100); // 也可以初始化成 0(简单写法)
        int h = (int) (h100 / (n * 100));
        int right = (maxDist - 1) / h + 1; // 也可以初始化成 maxDist(简单写法)
        while (left + 1 < right) {
            int mid = (left + right) >>> 1;
            if (check(mid, dist, h100)) {
                right = mid;
            } else {
                left = mid;
            }
        }
        return right;
    }
    private boolean check(int v, int[] dist, long h100) {
        int n = dist.length;
        long t = 0;
        for (int i = 0; i < n - 1; i++) {
            t += (dist[i] - 1) / v + 1;
        }
        return (t * v + dist[n - 1]) * 100 <= h100 * v;
    }
}

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

题目链接: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

图中两个节点之间可能有多条路径。

图中不含有自环。

题解:

方法:动态规划

       

class Solution {
    public int minCost(int maxTime, int[][] edges, int[] passingFees) {
        int n = passingFees.length;
        List<int[]>[] graph = new ArrayList[n];  // 用邻接表存储图
        Arrays.setAll(graph, i -> new ArrayList<>());
        
        for (int[] edge : edges) {
            int u = edge[0], v = edge[1], time = edge[2];
            graph[u].add(new int[]{v, time}); // 添加边 u -> v
            graph[v].add(new int[]{u, time}); // 添加边 v -> u
        }
        int[][] dist = new int[n][maxTime + 1]; // 初始化距离数组
        for (int[] row : dist) {
            Arrays.fill(row, Integer.MAX_VALUE);
        }
        dist[0][0] = passingFees[0]; // 起点费用
        
        PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
        pq.offer(new int[]{dist[0][0], 0, 0}); // (最小费用, 当前节点, 累计时间)
        
        while (!pq.isEmpty()) {
            int cost = pq.peek()[0];
            int city = pq.peek()[1];
            int curTime = pq.peek()[2];
            pq.poll();
            for (int[] neighbor : graph[city]) {
                int nextCity = neighbor[0];
                int edgeTime = neighbor[1];
                int newCost = cost + passingFees[nextCity]; // 通行费
                int newTime = curTime + edgeTime; // 总时间
                
                if (newTime <= maxTime && newCost < dist[nextCity][newTime]) {
                    dist[nextCity][newTime] = newCost;
                    pq.offer(new int[]{newCost, nextCity, newTime});
                }
            }
        }
        int res = Integer.MAX_VALUE;
        for (int i = 0; i <= maxTime; i++) {
            res = Math.min(res, dist[n - 1][i]);
        }
        
        return res == Integer.MAX_VALUE ? -1 : res;
    }
}

4.飞机座位分配概率

题目链接:1227. 飞机座位分配概率

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

剩下的乘客将会:

如果他们自己的座位还空着,就坐到自己的座位上,

当他们自己的座位被占用时,随机选择其他座位

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

示例 1:

输入:n = 1

输出:1.00000

解释:第一个人只会坐在自己的位置上。

示例 2:

输入: n = 2

输出: 0.50000

解释:在第一个人选好座位坐下后,第二个人坐在自己的座位上的概率是 0.5。

提示:

1 <= n <= 10^5

题解:

方法:动态规划

       

class Solution {
    public double nthPersonGetsNthSeat(int n) {
        return n == 1 ? 1 : 0.5;
    }
}

5.完成旅途的最少时间

题目链接: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 <= 105

1 <= time[i], totalTrips <= 107

题解:

方法:二分查找

       

class Solution {
    public long minimumTime(int[] time, int totalTrips) {
        int minT = Integer.MAX_VALUE;
        int maxT = 0;
        for (int t : time) {
            minT = Math.min(minT, t);
            maxT = Math.max(maxT, t);
        }
        int avg = (totalTrips - 1) / time.length + 1;
        // 循环不变量:check(left) 恒为 false
        long left = (long) minT * avg - 1;
        // 循环不变量:check(right) 恒为 true
        long right = Math.min((long) maxT * avg, (long) minT * totalTrips);
        // 开区间 (left, right) 不为空
        while (left + 1 < right) {
            long mid = (left + right) >>> 1;
            if (check(mid, time, totalTrips)) {
                // 缩小二分区间为 (left, mid)
                right = mid;
            } else {
                // 缩小二分区间为 (mid, right)
                left = mid;
            }
        }
        // 此时 left 等于 right-1
        // check(left) = false 且 check(right) = true,所以答案是 right
        return right; // 最小的 true
    }
    private boolean check(long x, int[] time, int totalTrips) {
        long sum = 0;
        for (int t : time) {
            sum += x / t;
            if (sum >= totalTrips) {
                return true;
            }
        }
        return false;
    }
}

6.加油站

题目链接:134. 加油站

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]

输出: 3

解释:

从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油

开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油

开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油

开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油

开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油

开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。

因此,3 可为起始索引。

示例 2:

输入: gas = [2,3,4], cost = [3,4,3]

输出: -1

解释:

你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。

我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油

开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油

开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油

你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。

因此,无论怎样,你都不可能绕环路行驶一周。

提示:

gas.length == n

cost.length == n

1 <= n <= 105

0 <= gas[i], cost[i] <= 104

题解:

方法:贪心 图 数组

       

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int ans = 0;
        int minS = 0; // 最小油量
        int s = 0; // 油量
        for (int i = 0; i < gas.length; i++) {
            s += gas[i] - cost[i]; // 在 i 处加油,然后从 i 到 i+1
            if (s < minS) {
                minS = s; // 更新最小油量
                ans = i + 1; // 注意 s 减去 cost[i] 之后,汽车在 i+1 而不是 i
            }
        }
        // 循环结束后,s 即为 gas 之和减去 cost 之和
        return s < 0 ? -1 : ans;
    }
}

下接:【题解】—— LeetCode一周小结41


相关文章
|
13天前
|
人工智能 BI
|
13天前
|
机器学习/深度学习
|
2月前
|
索引
|
3月前
|
算法
【题解】—— LeetCode一周小结36
LeetCode每日一道一周小结36
|
4月前
|
人工智能 BI
|
5月前
|
算法
|
6月前
|
算法
|
6月前
|
机器学习/深度学习