【LeetCode】﹝堆ி﹞移除石子的最大得分,吃苹果的最大数目等

傷城~ 2022-10-21 11:42 191阅读 0赞

【LeetCode】﹝堆ி﹞移除石子的最大得分,吃苹果的最大数目等

文章目录

    • 【LeetCode】﹝堆ி﹞移除石子的最大得分,吃苹果的最大数目等
      • 移除石子的最大得分★★
      • 魔塔游戏★★
      • 吃苹果的最大数目★★
      • 合并K个升序链表★★★
      • 水位上升的泳池中游泳★★★

移除石子的最大得分★★

1753. 移除石子的最大得分

题目】你正在玩一个单人游戏,面前放置着大小分别为 abc 的 三堆 石子。

每回合你都要从两个 不同的非空堆 中取出一颗石子,并在得分上加 1 分。当存在 两个或更多 的空堆时,游戏停止。

给你三个整数 abc ,返回可以得到的 最大分数 。

提示:

  • 1 <= a, b, c <= 105

示例

  1. 输入:a = 2, b = 4, c = 6
  2. 输出:6
  3. 解释:石子起始状态是 (2, 4, 6) ,最优的一组操作是:
  4. - 从第一和第三堆取,石子状态现在是 (1, 4, 5)
  5. - 从第一和第三堆取,石子状态现在是 (0, 4, 4)
  6. - 从第二和第三堆取,石子状态现在是 (0, 3, 3)
  7. - 从第二和第三堆取,石子状态现在是 (0, 2, 2)
  8. - 从第二和第三堆取,石子状态现在是 (0, 1, 1)
  9. - 从第二和第三堆取,石子状态现在是 (0, 0, 0)
  10. 总分:6

解题思路

方法一:排序

  1. class Solution {
  2. public int maximumScore(int a, int b, int c) {
  3. int score = 0;
  4. int[] nums = {
  5. a, b, c};
  6. Arrays.sort(nums);
  7. while (nums[0] > 0 || nums[1] > 0) {
  8. nums[1]--;
  9. nums[2]--;
  10. score += 1;
  11. Arrays.sort(nums);
  12. }
  13. return score;
  14. }
  15. }

方法二:优先队列

  1. class Solution {
  2. public int maximumScore(int a, int b, int c) {
  3. int score = 0;
  4. //大根堆
  5. PriorityQueue<Integer> queue = new PriorityQueue<Integer>( (o1, o2) -> {
  6. return o2 - o1;
  7. });
  8. queue.offer(a);
  9. queue.offer(b);
  10. queue.offer(c);
  11. while (true) {
  12. int max = queue.poll();
  13. int mid = queue.poll();
  14. if (mid == 0) {
  15. break;
  16. }
  17. score += 1;
  18. queue.offer(mid - 1);
  19. queue.offer(max - 1);
  20. }
  21. return score;
  22. }
  23. }

方法三:数学

  1. class Solution {
  2. public int maximumScore(int a, int b, int c) {
  3. int[] nums = {
  4. a, b, c};
  5. Arrays.sort(nums);
  6. if (nums[0] + nums[1] < nums[2]) {
  7. return nums[0] + nums[1];
  8. }
  9. //和为偶数全全空,和为奇数剩一个
  10. return (a + b + c) / 2;
  11. }
  12. }

参考热评@班长夏四果题解


魔塔游戏★★

LCP 30. 魔塔游戏

题目】小扣当前位于魔塔游戏第一层,共有 N 个房间,编号为 0 ~ N-1。每个房间的补血道具/怪物对于血量影响记于数组 nums,其中正数表示道具补血数值,即血量增加对应数值;负数表示怪物造成伤害值,即血量减少对应数值;0 表示房间对血量无影响。

小扣初始血量为 1,且无上限。假定小扣原计划按房间编号升序访问所有房间补血/打怪,为保证血量始终为正值,小扣需对房间访问顺序进行调整,每次仅能将一个怪物房间(负数的房间)调整至访问顺序末尾。请返回小扣最少需要调整几次,才能顺利访问所有房间。若调整顺序也无法访问完全部房间,请返回 -1

提示:

  • 1 <= nums.length <= 10^5
  • -10^5 <= nums[i] <= 10^5

示例

  1. 输入:nums = [100,100,100,-250,-60,-140,-50,-50,100,150]
  2. 输出:1
  3. 解释:初始血量为 1。至少需要将 nums[3] 调整至访问顺序末尾以满足要求
  4. ---------------------------------------------------------
  5. 输入:nums = [-200,-300,400,0]
  6. 输出:-1
  7. 解释:调整访问顺序也无法完成全部房间的访问

解题思路

贪心+优先队列

  • 计算数组和,若小于1则不能访问完,返回-1
  • 堆中保存打怪值(即负数),当当前生命值为负数时,从堆中出一个最小的(贪心思想,绝对值最大)放在数组末尾,调整值+1

    class Solution {

    1. public int magicTower(int[] nums) {
    2. int sum = 1;
    3. for (int num : nums) sum += num;
    4. if (sum < 1) return -1;
    5. long blood = 1;
    6. int count = 0;
    7. PriorityQueue<Integer> queue = new PriorityQueue<>();
    8. for (int num : nums) {
    9. blood += num;
    10. if (num < 0) queue.offer(num);
    11. if (blood < 1) {
    12. while (blood < 1 && !queue.isEmpty()) {
    13. blood -= queue.poll();
    14. count++;
    15. }
    16. }
    17. }
    18. return count;
    19. }

    }


吃苹果的最大数目★★

1705. 吃苹果的最大数目

题目】有一棵特殊的苹果树,一连 n 天,每天都可以长出若干个苹果。在第 i 天,树上会长出 apples[i] 个苹果,这些苹果将会在 days[i] 天后(也就是说,第 i + days[i] 天时)腐烂,变得无法食用。也可能有那么几天,树上不会长出新的苹果,此时用 apples[i] == 0days[i] == 0 表示。

你打算每天 最多 吃一个苹果来保证营养均衡。注意,你可以在这 n 天之后继续吃苹果。

给你两个长度为 n 的整数数组 daysapples ,返回你可以吃掉的苹果的最大数目。

提示:

  • apples.length == n
  • days.length == n
  • 1 <= n <= 2 * 104
  • 0 <= apples[i], days[i] <= 2 * 104
  • 只有在 apples[i] = 0 时,days[i] = 0 才成立

示例

  1. 输入:apples = [1,2,3,5,2], days = [3,2,1,4,2]
  2. 输出:7
  3. 解释:你可以吃掉 7 个苹果:
  4. - 第一天,你吃掉第一天长出来的苹果。
  5. - 第二天,你吃掉一个第二天长出来的苹果。
  6. - 第三天,你吃掉一个第二天长出来的苹果。过了这一天,第三天长出来的苹果就已经腐烂了。
  7. - 第四天到第七天,你吃的都是第四天长出来的苹果。

解题思路

  1. class Solution {
  2. public int eatenApples(int[] apples, int[] days) {
  3. PriorityQueue<int[]> queue = new PriorityQueue<int[]>( (a, b) -> {
  4. return a[0] - b[0];
  5. });
  6. int count = 0, i = 0;
  7. while (true) {
  8. if (i < apples.length) {
  9. queue.offer(new int[]{
  10. days[i] + i, apples[i]});
  11. } else if (i >= apples.length && queue.isEmpty()) {
  12. break;
  13. }
  14. //移除腐烂的苹果
  15. while (!queue.isEmpty() && (queue.peek()[0] <= i || queue.peek()[1] == 0)) {
  16. queue.poll();
  17. }
  18. //吃苹果
  19. if (!queue.isEmpty()) {
  20. count++;
  21. queue.peek()[1]--;
  22. }
  23. //天数+1
  24. i++;
  25. }
  26. return count;
  27. }
  28. }

合并K个升序链表★★★

23. 合并K个升序链表

题目】给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

提示

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i] 按 升序 排列
  • lists[i].length 的总和不超过 10^4

示例

  1. 输入:lists = [[1,4,5],[1,3,4],[2,6]]
  2. 输出:[1,1,2,3,4,4,5,6]
  3. 解释:链表数组如下:
  4. [
  5. 1->4->5,
  6. 1->3->4,
  7. 2->6
  8. ]
  9. 将它们合并到一个有序链表中得到。
  10. 1->1->2->3->4->4->5->6

解题思路

归并排序的解法见往期博客【LeetCode】﹝归并思想ி﹞逆序对、翻转对、排序链表、合并K个升序链表

优先队列:采用小根堆

将链表的头结点加入队列中,每次选最小的,最后将指针后移一位加入优先队列,若空则不必加入。

  1. class Solution {
  2. public ListNode mergeKLists(ListNode[] lists) {
  3. if (lists == null || lists.length == 0) {
  4. return null;
  5. }
  6. ListNode dummy = new ListNode(-1);
  7. ListNode cur = dummy;
  8. PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>((o1, o2) -> {
  9. return o1.val - o2.val;
  10. });
  11. for (ListNode list : lists) {
  12. if (list != null) {
  13. queue.offer(list);
  14. }
  15. }
  16. while (!queue.isEmpty()) {
  17. ListNode head = queue.poll();
  18. cur.next = head;
  19. if (head.next != null) {
  20. queue.offer(head.next);
  21. }
  22. cur = cur.next;
  23. }
  24. return dummy.next;
  25. }
  26. }

水位上升的泳池中游泳★★★

778. 水位上升的泳池中游泳

题目】在一个 N x N 的坐标方格 grid 中,每一个方格的值 grid[i][j] 表示在位置 (i,j) 的平台高度。

现在开始下雨了。当时间为 t 时,此时雨水导致水池中任意位置的水位为 t 。你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。假定你可以瞬间移动无限距离,也就是默认在方格内部游动是不耗时的。当然,在你游泳的时候你必须待在坐标方格里面。

你从坐标方格的左上平台 (0,0) 出发。最少耗时多久你才能到达坐标方格的右下平台 (N-1, N-1)

提示:

  1. 2 <= N <= 50
  2. grid[i][j][0, ..., N*N - 1] 的排列

示例

  1. 输入: [[0,2],[1,3]]
  2. 输出: 3
  3. 解释:
  4. 时间为0时,你位于坐标方格的位置为 (0, 0)。
  5. 此时你不能游向任意方向,因为四个相邻方向平台的高度都大于当前时间为 0 时的水位。
  6. 等时间到达 3 时,你才可以游向平台 (1, 1). 因为此时的水位是 3,坐标方格中的平台没有比水位 3 更高的,所以你可以游向坐标方格中的任意位置

解题思路

方法一:优先队列

  1. class Solution {
  2. public int swimInWater(int[][] grid) {
  3. int n = grid.length;
  4. int[][] dp = new int[n][n];
  5. int[] dirs = {
  6. -1, 0, 1, 0, -1};
  7. //堆中的三元组[i, j, height]即下标和高度
  8. PriorityQueue<int[]> heap = new PriorityQueue<int[]>((o1, o2) -> {
  9. return o1[2] - o2[2];
  10. });
  11. //初始化
  12. for (int[] p : dp) {
  13. Arrays.fill(p, 10000);
  14. }
  15. dp[0][0] = grid[0][0];
  16. heap.offer(new int[]{
  17. 0, 0, grid[0][0]});
  18. while (!heap.isEmpty()) {
  19. int[] cur = heap.poll();
  20. int x = cur[0], y = cur[1];
  21. if (x == n - 1 && y == n - 1) {
  22. break;
  23. }
  24. for (int i = 0; i < 4; i++) {
  25. int posx = x + dirs[i];
  26. int posy = y + dirs[i + 1];
  27. if (posx >= 0 && posx < n && posy >= 0 && posy < n) {
  28. int h = Math.max(grid[posx][posy], dp[x][y]);
  29. if (h < dp[posx][posy]) {
  30. dp[posx][posy] = h;
  31. heap.offer(new int[]{
  32. posx, posy, h});
  33. }
  34. }
  35. }
  36. }
  37. return dp[n - 1][n - 1];
  38. }
  39. }

方法二:并查集

  1. class Solution {
  2. public int swimInWater(int[][] grid) {
  3. int N = grid.length;
  4. int len = N * N;
  5. int[] index = new int[len];
  6. for (int i = 0; i < N; i++) {
  7. for (int j = 0; j < N; j++) {
  8. index[grid[i][j]] = i * N + j;
  9. }
  10. }
  11. int[] dirs = {
  12. -1, 0, 1, 0, -1};
  13. UnionFind uf = new UnionFind(len);
  14. for (int i = 0; i < len; i++) {
  15. int x = index[i] / N;
  16. int y = index[i] % N;
  17. for (int j = 0; j < 4; j++) {
  18. int posX = x + dirs[j];
  19. int posY = y + dirs[j + 1];
  20. if (check(posX, posY, N) && grid[posX][posY] <= i) {
  21. uf.union(index[i], posX * N + posY);
  22. }
  23. }
  24. if (uf.connected(0, len - 1)) {
  25. return i;
  26. }
  27. }
  28. return -1;
  29. }
  30. private boolean check (int x, int y, int N) {
  31. return x >= 0 && x < N && y >= 0 && y < N;
  32. }
  33. private class UnionFind {
  34. //使用路径压缩的加权quick_union算法
  35. private int[] id; //父链接数组
  36. private int[] sz; //根节点所对应分量大小
  37. private int count; //连通分量数量
  38. UnionFind(int N) {
  39. count = N;
  40. id = new int[N];
  41. sz = new int[N];
  42. for (int i = 0; i < N; i++) {
  43. id[i] = i;
  44. sz[i] = 1;
  45. }
  46. }
  47. public int count() {
  48. return count;
  49. }
  50. public boolean connected(int p, int q) {
  51. return find(p) == find(q);
  52. }
  53. public int find(int p) {
  54. if (p == id[p]) return p;
  55. id[p] = find(id[p]);
  56. return id[p];
  57. }
  58. public void union(int p, int q) {
  59. int pRoot = find(p);
  60. int qRoot = find(q);
  61. if (pRoot == qRoot) {
  62. return;
  63. }
  64. if (sz[pRoot] >= sz[qRoot]) {
  65. id[qRoot] = pRoot;
  66. sz[pRoot] += sz[qRoot];
  67. } else {
  68. id[pRoot] = qRoot;
  69. sz[qRoot] += sz[pRoot];
  70. }
  71. count--;
  72. }
  73. }
  74. }

简单版并查集模板

  1. class UnionFind {
  2. private int[] id; //父链接数组
  3. UnionFind(int N) {
  4. id = new int[N];
  5. for (int i = 0; i < N; i++) {
  6. id[i] = i;
  7. }
  8. }
  9. public boolean connected(int p, int q) {
  10. return find(p) == find(q);
  11. }
  12. public int find(int p) {
  13. if (p == id[p]) return p;
  14. id[p] = find(id[p]);
  15. return id[p];
  16. }
  17. public void union(int p, int q) {
  18. int pRoot = find(p);
  19. int qRoot = find(q);
  20. if (pRoot == qRoot) {
  21. return;
  22. }
  23. id[qRoot] = pRoot;
  24. }
  25. }

参考官方题解

发表评论

表情:
评论列表 (有 0 条评论,191人围观)

还没有评论,来说两句吧...

相关阅读