【Py/Java/C++/JS四种语言OD2023C卷真题】20天拿下华为OD笔试之【贪心+优先队列】2023C-贪心歌手【欧弟算法】全网注释最详细分类最全的华为OD真题题解

有华为OD考试扣扣交流群可加:948025485
可上全网独家的 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳 od1336了解算法冲刺训练

题目描述与示例

歌手准备从 A 城去 B 城参加演出

  1. 按照合同,他必须在 T 天内赶到。
  2. 歌手途径 N 座城市。
  3. 歌手不能往回走。
  4. 每两座城市之间需要的天数都可以提前获知。
  5. 歌手在每座城市都可以在路边卖唱赚钱。经过调研,歌手提前获知了每座城市卖唱的收入预期。如果在一座城市第一天卖唱可以赚 M,后续每天的收入会减少 D (第二天赚的钱是 M-D,第三天是 M-2D…)。如果收入减到 0 就不会再少了。
  6. 歌手到达后的第二天才能开始卖唱。如果今天卖过唱,第二天才能出发。

问贪心的歌手最多可以赚多少钱?

输入描述

第一行两个数字 TN,中间用空格隔开,T 代表总天数; N 代表路上经过 N 座城市;

0 < T < 1000,0 < N < 100

第二行 N+1 个数字,中间用空格隔开,代表每两座城市之间耗费的时间,其总和<=T

接下来 N 行,每行两个数字 MD,中间用空格隔开。代表每个城市的收入预期。

0 < M < 1000,0 < D < 100

输出描述

一个数字。代表歌手最多可以赚多少钱。以回车结束

示例一

输入
10 2
1 1 2
120 20
90 10
输出
540
说明

总共 10 天,路上经过 2 座城市。 路上共花 1+1+2=4 天。 剩余 6 天最好的计划是在第一座城市待 3 天,在第二座城市待 3 天。 在第一座城市赚的钱:120 + 100 + 80 = 300 在第二座城市赚的钱:90 + 80 + 70 = 240

300 +240 = 540

示例二

输入
10 3
1 1 2 3
120 20
90 10
100 20
输出
320

解题思路

题目的名字已经把这道题用的算法告诉你了——贪心

题干非常长,得好好理解题意。

首先歌手能够卖唱的所有天数是固定的。假设总时间为T,花费在路上的时间数组为days,那么可以停下来卖唱的时间为X = T - sum(days)。这是很容易分析出来的结论。

那么为了能够赚更多的钱,我们一定要把M天都拉满,即尽可能地停留在某个城市赚钱。

X天的分配实际上是任意的,停留在哪一个城市都行,只要停留够X天就肯定能赚到尽可能多的钱。实际上我们并不需要按照歌手的时间线来考虑问题而是按照某一天能够在哪个城市获得的钱数最多来考虑问题的

考虑例子:

停留的卖唱天数X = 3,第一个城市的起始金额M1 = 100,衰减速度D1 = 50,第二个城市的起始金额M2 = 80,衰减速度D1 = 40。我们会做如下分析:

  1. M1 > M2,在第一个城市进行卖唱,赚的钱ans = 100,在第一个城市赚到的钱衰减为M1 = 100 - 50 = 50
  2. M2 < M1,在第二个城市进行卖唱,赚的钱ans = 100 + 80,在第二个城市赚到的钱衰减为M2 = 80 - 40 = 40
  3. M1 < M2,在第一个城市进行卖唱,赚的钱ans = 100 + 80 + 50,在第一个城市赚到的钱衰减为M1 = 50 - 50 = 0

那么X = 3就用完了,最多可以赚ans = 100 + 80 + 50 = 230

虽然真正的时间线为1(两天) -> 2(一天),但是分析问题时我们的过程是1(一天) -> 2(一天) -> 1(一天),但也能够计算出正确的答案。

上述过程涉及到动态维护最大值的情形,很显然可以使用优先队列来维护该过程。具体过程如下:

  1. 构建一个以最大值为排序依据的优先队列(即大根堆)heap
  2. 将每一个城市的(M, D)储存在堆中。
  3. 循环X次,表示最多可以停留X天在进行卖唱。
    1. 弹出堆顶元素M, D,此时可以赚到M,即更新ans += M
    2. 计算衰减后该城市能够赚到的钱M -= D
    3. 如果M衰减了D之后,仍大于0,说明还有可能继续在这个城市赚钱,需要将(M, D)重新入堆
  4. 注意在循环中,必须判断heap的长度是否大于0。如果等于0,说明优先队列中无元素,所有的城市赚的钱都衰减到0了,此时可以直接退出循环。

不熟悉优先队列的同学,每一次最大值挑选完衰减后再次插入原数组的操作,也可以直接用排序的API来完成。由于插入完毕后至多只有一个元素是无序的,加上N的最大值只有100,因此这里直接使用排序并不会比优先队列慢很多。

代码

Python

# # 题目:【贪心】2023C-贪心歌手
# # 分值:200
# # 作者:闭着眼睛学数理化
# # 算法:贪心,优先队列
# # 代码看不懂的地方,请直接在群上提问


from heapq import heappush, heappop


# 总天数,城市数
T, N = map(int, input().split())
# 路程天数
days = list(map(int, input().split()))

# 初始化一个大根堆
heap = list()
# N个城市的初始金额M和衰减速度D
for _ in range(N):
    M, D = map(int, input().split())
    # 将(M, D)入堆,
    # 由于维护的是大根堆,所以储存的是(-M, D)
    heappush(heap, (-M, D))

# 可以卖唱的总天数X
X = T - sum(days)

ans = 0
# 遍历X天
for i in range(X):
    # 如果堆中无元素,则说明所有的城市赚的钱都衰减到0了
    # 直接退出循环
    if len(heap) == 0:
        break
    # 弹出堆中绝对值最大的M
    M, D = heappop(heap)
    # 之前储存的M是负数,改为正数
    M = -M
    # 选择在这个城市卖唱,可以赚到M
    ans += M
    # 赚完了M,需要衰减D,更新M的值
    M -= D
    # 如果M衰减了D之后,仍大于0,说明还有可能继续在这里赚钱,重新入堆
    if M > 0:
        heappush(heap, (-M, D))

print(ans)

Java

import java.util.PriorityQueue;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int T = scanner.nextInt();
        int N = scanner.nextInt();
        int[] days = new int[N+1];
        for (int i = 0; i < N+1; i++) {
            days[i] = scanner.nextInt();
        }
        PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> Integer.compare(b[0], a[0])); // Max heap
        for (int i = 0; i < N; i++) {
            int M = scanner.nextInt();
            int D = scanner.nextInt();
            heap.offer(new int[]{M, D});
        }
        int X = T - sum(days);
        int ans = 0;
        for (int i = 0; i < X; i++) {
            if (heap.isEmpty()) {
                break;
            }
            int[] city = heap.poll();
            int M = city[0];
            int D = city[1];
            ans += M;
            M -= D;
            if (M > 0) {
                heap.offer(new int[]{M, D});
            }
        }
        System.out.println(ans);
    }

    private static int sum(int[] arr) {
        int sum = 0;
        for (int num : arr) {
            sum += num;
        }
        return sum;
    }
}

C++

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int main() {
    int T, N;
    cin >> T >> N;
    vector<int> days(N+1);
    for (int i = 0; i < N+1; i++) {
        cin >> days[i];
    }
    priority_queue<pair<int, int>> heap; // Max heap
    for (int i = 0; i < N; i++) {
        int M, D;
        cin >> M >> D;
        heap.push({M, D});
    }
    int X = T;
    for (int i = 0; i < N+1; i++) {
        X -= days[i];
    }
    int ans = 0;
    for (int i = 0; i < X; i++) {
        if (heap.empty()) {
            break;
        }
        auto city = heap.top();
        heap.pop();
        int M = city.first;
        int D = city.second;
        ans += M;
        M -= D;
        if (M > 0) {
            heap.push({M, D});
        }
    }
    cout << ans << endl;
    return 0;
}

JavaScript

const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

let input = [];
rl.on('line', line => {
  input.push(line);
}).on('close', () => {
  let [T, N] = input[0].split(' ').map(Number);
  let days = input[1].split(' ').map(Number);
  let heap = new PriorityQueue((a, b) => a[0] - b[0]); // Max heap

  for (let i = 0; i < N; i++) {
    let [M, D] = input[i + 2].split(' ').map(Number);
    heap.offer([M, D]);
  }

  let X = T - days.reduce((a, b) => a + b, 0);
  let ans = 0;

  for (let i = 0; i < X; i++) {
    if (heap.isEmpty()) break;
    let city = heap.poll();
    let [M, D] = city;
    ans += M;
    M -= D;
    if (M > 0) heap.offer([M, D]);
  }

  console.log(ans);
});

class PriorityQueue {
  constructor(comparator) {
    this.comparator = comparator;
    this.heap = [];
  }

  offer(val) {
    this.heap.push(val);
    this.bubbleUp(this.heap.length - 1);
  }

  poll() {
    if (!this.isEmpty()) {
      let len = this.heap.length;
      this.swap(0, len - 1);
      let val = this.heap.pop();
      this.bubbleDown(0);
      return val;
    }
  }

  isEmpty() {
    return this.heap.length === 0;
  }

  bubbleUp(idx) {
    while (idx > 0) {
      let parent = Math.floor((idx - 1) / 2);
      if (this.comparator(this.heap[idx], this.heap[parent]) > 0) {
        this.swap(idx, parent);
        idx = parent;
      } else {
        break;
      }
    }
  }

  bubbleDown(idx) {
    let len = this.heap.length;
    while (idx < len) {
      let leftChild = idx * 2 + 1;
      let rightChild = idx * 2 + 2;
      let largest = idx;
      if (leftChild < len && this.comparator(this.heap[leftChild], this.heap[largest]) > 0) {
        largest = leftChild;
      }
      if (rightChild < len && this.comparator(this.heap[rightChild], this.heap[largest]) > 0) {
        largest = rightChild;
      }
      if (largest !== idx) {
        this.swap(idx, largest);
        idx = largest;
      } else {
        break;
      }
    }
  }

  swap(i, j) {
    [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
  }
}

时空复杂度

时间复杂度:O(XlogN)。优先队列单次插入操作的时间复杂度为O(logN)

空间复杂度:O(N)。优先队列最多时储存N个元素。


华为OD算法/大厂面试高频题算法练习冲刺训练

  • 华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!

  • 课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化

  • 每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!

  • 60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁

  • 可上全网独家的欧弟OJ系统练习华子OD、大厂真题

  • 可查看链接 大厂真题汇总 & OD真题汇总(持续更新)

  • 绿色聊天软件戳 od1336了解更多

  • 45
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
贪心算法是一种基于贪心思想的算法,它在每一步选择中都采取当前状态下最优的选择,从而希望最终得到全局最优解。贪心算法通常可以用来解决一些最优化问题,比如最小生成树、背包问题、最短路径等。 贪心算法的实现步骤一般如下: 1. 定义问题的贪心策略。 2. 根据贪心策略,选择当前状态下的最优解。 3. 更新问题的状态,继续步骤 2 直到达到终止条件。 需要注意的是,贪心算法并不是所有问题都能使用的算法。对于某些问题,贪心算法可能得到的不是全局最优解,而是局部最优解。因此,在使用贪心算法时,需要保证问题具有贪心选择性质和无后效性质。 下面以一个简单的例子来说明贪心算法的应用。 给定一个数组,每个元素表示一个活动的结束时间和开始时间。你作为一个主办人,需要在这些活动中选择尽可能多的活动进行安排,使得不同的活动之间不会产生时间冲突。求最多能安排多少个活动。 示例代码: ```csharp public class Activity { public int start; public int end; public Activity(int start, int end) { this.start = start; this.end = end; } } public int MaxActivities(Activity[] activities) { int count = 0; int currentEnd = 0; Array.Sort(activities, (a, b) => a.end - b.end); foreach (Activity activity in activities) { if (activity.start >= currentEnd) { count++; currentEnd = activity.end; } } return count; } ``` 在这个示例代码中,我们定义了一个 `Activity` 类来表示活动的开始时间和结束时间。然后,我们通过贪心策略来选择活动,即每次选择结束时间最早的活动。具体实现中,我们将活动按照结束时间从小到大排序,然后依次选择活动,如果当前活动的开始时间大于等于前一个活动的结束时间,则可以选择该活动。最后返回选择的活动数即可。 时间复杂度为 O(n log n),其中 n 是活动数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值