题目
问题描述
小C喜欢在电子书城阅读书籍,并希望获得尽可能多的满足感。每本书的满意程度books[i]
表示小C阅读这本书的评分。如果按照顺序阅读书籍,time[i]
定义为阅读第i
本书及之前所有书所花费的时间,即第i
本书的快乐时间系数为time[i] * books[i]
。小C可以按照任意顺序调整书籍的阅读顺序,以获得最大的【快乐时间】总和。
例如,给定的书籍评分是 [4, 3, 2]
,如果按评分升序排序来安排阅读,则小C可以获得最大的快乐时间系数。
测试样例
样例1:
输入:
books = [4, 3, 2]
输出:20
说明:选择第一天读第三本书,第二天读第二本书,第三天读第一本书,快乐时间为20,可以证明,没有其他方案获得的快乐时间比此方案更大
样例2:
输入:
books = [-1, -4, -5]
输出:0
说明:无论怎么读,最后的快乐时间均为负数,所以选择不读,快乐时间为0
样例3:
输入:
books = [-1, -8, 0, 5, -9]
输出:14
说明:选择第一天读第一本书,第二天读第三本书,第三天读第四本书,快乐时间为14,可以证明,没有其他方案获得的快乐时间比此方案更大
解析
规则:以时间天数为倍率,假设当天是第t天,则有某本书能获得的快乐 = 书籍评分books[i]
* t。那么我们想要获得最大的快乐时间系数,需亚尽可能地安排评分高的书籍在后面读,这样能获得最大的倍率。因此我们可以确定我们需要将书籍按照升序排列。
我们分析样例入手这个题目
都是非负数
最简单的情况,直接升序排序按倍率求和即可。
都是负数:
不予考虑,看了还伤心。
有正有负
如果我们只考虑非负数,那么答案是(1 * 0) + (2 * 5) = 10
但是如果阅读一本负数的书反而答案更大(1 * (-1)) + (2 * 0) + (3 * 5) = 14
思考步骤:
首先按升序排列
-9 -8 -1 0 5
因为非负数是一定要考虑的,因此我们找到非负数开始的位置pos
,在这个样例中pos = 3
,我们将非负数的计算放入答案sum = (1 * 0) + (2 * 5) = 10
。
接下来我们考虑添加负数,如果是添加负数的话那肯定是插入原来的式子的最前面,但是这样会导致我们原先的式子倍率要发生变化,该怎么变化呢?
假如我们在最前面插入一本书,那么原来的(1 * 0) + (2 * 5)
要变成(2 * 0) + (3 * 5)
再加上新书book[i]
。我们可以观察到,旧书的变化量为0 + 5
,即每次都要增加一份旧书评分的总和,因此我们维护一个变量sigleSum
,用来记录每次看过的书的评分总和,每次挑选剩余书籍里面的评分最大值的书,只要sigleSum
的增量大于每次负数评分的减量,就能使的总快乐增加。
Code
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
int solution(std::vector<int>& books) {
int n = books.size();
sort(books.begin(), books.end());
int pos = 0;
for (;pos < n; pos++)
if (books[pos] >= 0) break;
int singleSum = 0, sum = 0;
for (int i = pos; i < n; i++) {
singleSum += books[i];
sum += (i - pos + 1) * books[i];
}
for (int i = pos-1; i >= 0; i--) {
if (singleSum + books[i] > 0) {
sum += singleSum + books[i];
singleSum += books[i];
}
else break;
}
return sum;
}
int main() {
std::vector<int> books1 = {4, 3, 2};
std::cout << (solution(books1) == 20) << std::endl;
std::vector<int> books2 = {-1, -4, -5};
std::cout << (solution(books2) == 0) << std::endl;
std::vector<int> books3 = {-1, -8, 0, 5, -9};
std::cout << (solution(books3) == 14) << std::endl;
return 0;
}
复杂度
时间复杂度分析
- 排序操作:首先我们对输入数组
books
进行排序。排序的时间复杂度为O(n log n)
,其中n
是书籍数量。 - 遍历数组:接下来,我们需要遍历书籍列表来计算快乐时间,并检查是否可以通过调整负书籍的位置来增加总快乐时间。这个过程的时间复杂度是
O(n)
,因为我们只需要遍历一次数组。
因此,整体的时间复杂度是排序操作和遍历操作的时间复杂度之和:
- 排序:
O(n log n)
- 遍历:
O(n)
所以,总时间复杂度为 O(n log n)
。
空间复杂度分析
- 额外空间:我们只用了几个常量变量来保存总快乐时间、累积的评分总和等,并没有使用额外的辅助空间,因此空间复杂度为
O(1)
。 - 排序空间:由于排序操作是原地排序(使用的是
std::sort
),所以不需要额外的空间。
因此,总空间复杂度为 O(1)
。