2928. 给小朋友们分糖果 I
给你两个正整数 n 和 limit 。
请你将 n 颗糖果分给 3 位小朋友,确保没有任何小朋友得到超过 limit 颗糖果,请你返回满足此条件下的 总方案数 。
示例 1:
输入:n = 5, limit = 2
输出:3
解释:总共有 3 种方法分配 5 颗糖果,且每位小朋友的糖果数不超过 2 :(1, 2, 2) ,(2, 1, 2) 和 (2, 2, 1) 。
示例 2:
输入:n = 3, limit = 3
输出:10
解释:总共有 10 种方法分配 3 颗糖果,且每位小朋友的糖果数不超过 3 :(0, 0, 3) ,(0, 1, 2) ,(0, 2, 1) ,(0, 3, 0) ,(1, 0, 2) ,(1, 1, 1) ,(1, 2, 0) ,(2, 0, 1) ,(2, 1, 0) 和 (3, 0, 0) 。
提示:
1 <= n <= 50
1 <= limit <= 50
枚举即可:
class Solution {
public:
int distributeCandies(int n, int limit) {
int ans = 0;
for (int i = 0; i <= limit; i++) {
for (int j = 0; j <= limit; j++) {
if (i + j > n) {
break;
}
if (n - i - j <= limit) {
ans++;
}
}
}
return ans;
}
};
575. 分糖果
Alice 有 n 枚糖,其中第 i 枚糖的类型为 candyType[i] 。Alice 注意到她的体重正在增长,所以前去拜访了一位医生。
医生建议 Alice 要少摄入糖分,只吃掉她所有糖的 n / 2 即可(n 是一个偶数)。Alice 非常喜欢这些糖,她想要在遵循医生建议的情况下,尽可能吃到最多不同种类的糖。
给你一个长度为 n 的整数数组 candyType ,返回: Alice 在仅吃掉 n / 2 枚糖的情况下,可以吃到糖的 最多 种类数。
示例 1:
输入:candyType = [1,1,2,2,3,3]
输出:3
解释:Alice 只能吃 6 / 2 = 3 枚糖,由于只有 3 种糖,她可以每种吃一枚。
示例 2:
输入:candyType = [1,1,2,3]
输出:2
解释:Alice 只能吃 4 / 2 = 2 枚糖,不管她选择吃的种类是 [1,2]、[1,3] 还是 [2,3],她只能吃到两种不同类的糖。
示例 3:
输入:candyType = [6,6,6,6]
输出:1
解释:Alice 只能吃 4 / 2 = 2 枚糖,尽管她能吃 2 枚,但只能吃到 1 种糖。
提示:
n == candyType.length
2 <= n <= 104
n 是一个偶数
-1e5 <= candyType[i] <= 1e5
哈希,看了题解qaq:
class Solution {
public:
int distributeCandies(vector<int>& candyType) {
unordered_set<int> uniqueCandies(candyType.begin(), candyType.end());
return min(uniqueCandies.size(), candyType.size() / 2);
}
};
1103. 分糖果 II
排排坐,分糖果。
我们买了一些糖果 candies,打算把它们分给排好队的 n = num_people 个小朋友。
给第一个小朋友 1 颗糖果,第二个小朋友 2 颗,依此类推,直到给最后一个小朋友 n 颗糖果。
然后,我们再回到队伍的起点,给第一个小朋友 n + 1 颗糖果,第二个小朋友 n + 2 颗,依此类推,直到给最后一个小朋友 2 * n 颗糖果。
重复上述过程(每次都比上一次多给出一颗糖果,当到达队伍终点后再次从队伍起点开始),直到我们分完所有的糖果。注意,就算我们手中的剩下糖果数不够(不比前一次发出的糖果多),这些糖果也会全部发给当前的小朋友。
返回一个长度为 num_people、元素之和为 candies 的数组,以表示糖果的最终分发情况(即 ans[i] 表示第 i 个小朋友分到的糖果数)。
示例 1:
输入:candies = 7, num_people = 4
输出:[1,2,3,1]
解释:
第一次,ans[0] += 1,数组变为 [1,0,0,0]。
第二次,ans[1] += 2,数组变为 [1,2,0,0]。
第三次,ans[2] += 3,数组变为 [1,2,3,0]。
第四次,ans[3] += 1(因为此时只剩下 1 颗糖果),最终数组变为 [1,2,3,1]。
示例 2:
输入:candies = 10, num_people = 3
输出:[5,2,3]
解释:
第一次,ans[0] += 1,数组变为 [1,0,0]。
第二次,ans[1] += 2,数组变为 [1,2,0]。
第三次,ans[2] += 3,数组变为 [1,2,3]。
第四次,ans[0] += 4,最终数组变为 [5,2,3]。
提示:
1 <= candies <= 10^9
1 <= num_people <= 1000
数学方法:
class Solution {
public:
vector<int> distributeCandies(int candies, int n) {
vector<int> ans(n);
for (int i = 1; candies > 0; i++) {
ans[(i - 1) % n] += min(i, candies);
candies -= i;
}
return ans;
}
};
3067. 在带权树网络中统计可连接服务器对数目
给你一棵无根带权树,树中总共有 n 个节点,分别表示 n 个服务器,服务器从 0 到 n - 1 编号。同时给你一个数组 edges ,其中 edges[i] = [ai, bi, weighti] 表示节点 ai 和 bi 之间有一条双向边,边的权值为 weighti 。再给你一个整数 signalSpeed 。
如果两个服务器 a ,b 和 c 满足以下条件,那么我们称服务器 a 和 b 是通过服务器 c 可连接的 :
a < b ,a != c 且 b != c 。
从 c 到 a 的距离是可以被 signalSpeed 整除的。
从 c 到 b 的距离是可以被 signalSpeed 整除的。
从 c 到 b 的路径与从 c 到 a 的路径没有任何公共边。
请你返回一个长度为 n 的整数数组 count ,其中 count[i] 表示通过服务器 i 可连接 的服务器对的 数目 。
示例 1:
输入:edges = [[0,1,1],[1,2,5],[2,3,13],[3,4,9],[4,5,2]], signalSpeed = 1
输出:[0,4,6,6,4,0]
解释:由于 signalSpeed 等于 1 ,count[c] 等于所有从 c 开始且没有公共边的路径对数目。
在输入图中,count[c] 等于服务器 c 左边服务器数目乘以右边服务器数目。
示例 2:
输入:edges = [[0,6,3],[6,5,3],[0,3,1],[3,2,7],[3,1,6],[3,4,2]], signalSpeed = 3
输出:[2,0,0,0,0,0,2]
解释:通过服务器 0 ,有 2 个可连接服务器对(4, 5) 和 (4, 6) 。
通过服务器 6 ,有 2 个可连接服务器对 (4, 5) 和 (0, 5) 。
所有服务器对都必须通过服务器 0 或 6 才可连接,所以其他服务器对应的可连接服务器对数目都为 0 。
提示:
2 <= n <= 1000
edges.length == n - 1
edges[i].length == 3
0 <= ai, bi < n
edges[i] = [ai, bi, weighti]
1 <= weighti <= 1e6
1 <= signalSpeed <= 1e6
输入保证 edges 构成一棵合法的树。
枚举根 DFS + 乘法原理(参考灵神题解):
class Solution {
public:
vector<int> countPairsOfConnectableServers(vector<vector<int>>& edges,
int signalSpeed) {
int n = edges.size() + 1;
vector<pair<int, int>> g[n];
for (auto& e : edges) {
int a = e[0], b = e[1], w = e[2];
g[a].emplace_back(b, w);
g[b].emplace_back(a, w);
}
function<int(int, int, int)> dfs = [&](int a, int fa, int ws) {
int cnt = ws % signalSpeed == 0;
for (auto& [b, w] : g[a]) {
if (b != fa) {
cnt += dfs(b, a, ws + w);
}
}
return cnt;
};
vector<int> ans(n);
for (int a = 0; a < n; ++a) {
int s = 0;
for (auto& [b, w] : g[a]) {
int t = dfs(b, a, w);
ans[a] += s * t;
s += t;
}
}
return ans;
}
};
3072. 将元素分配到两个数组中 II(Hard)
给你一个下标从 1 开始、长度为 n 的整数数组 nums 。
现定义函数 greaterCount ,使得 greaterCount(arr, val) 返回数组 arr 中 严格大于 val 的元素数量。
你需要使用 n 次操作,将 nums 的所有元素分配到两个数组 arr1 和 arr2 中。在第一次操作中,将 nums[1] 追加到 arr1 。在第二次操作中,将 nums[2] 追加到 arr2 。之后,在第 i 次操作中:
如果 greaterCount(arr1, nums[i]) > greaterCount(arr2, nums[i]) ,将 nums[i] 追加到 arr1 。
如果 greaterCount(arr1, nums[i]) < greaterCount(arr2, nums[i]) ,将 nums[i] 追加到 arr2 。
如果 greaterCount(arr1, nums[i]) == greaterCount(arr2, nums[i]) ,将 nums[i] 追加到元素数量较少的数组中。
如果仍然相等,那么将 nums[i] 追加到 arr1 。
连接数组 arr1 和 arr2 形成数组 result 。例如,如果 arr1 == [1,2,3] 且 arr2 == [4,5,6] ,那么 result = [1,2,3,4,5,6] 。
返回整数数组 result 。
示例 1:
输入:nums = [2,1,3,3]
输出:[2,3,1,3]
解释:在前两次操作后,arr1 = [2] ,arr2 = [1] 。
在第 3 次操作中,两个数组中大于 3 的元素数量都是零,并且长度相等,因此,将 nums[3] 追加到 arr1 。
在第 4 次操作中,两个数组中大于 3 的元素数量都是零,但 arr2 的长度较小,因此,将 nums[4] 追加到 arr2 。
在 4 次操作后,arr1 = [2,3] ,arr2 = [1,3] 。
因此,连接形成的数组 result 是 [2,3,1,3] 。
示例 2:
输入:nums = [5,14,3,1,2]
输出:[5,3,1,2,14]
解释:在前两次操作后,arr1 = [5] ,arr2 = [14] 。
在第 3 次操作中,两个数组中大于 3 的元素数量都是一,并且长度相等,因此,将 nums[3] 追加到 arr1 。
在第 4 次操作中,arr1 中大于 1 的元素数量大于 arr2 中的数量(2 > 1),因此,将 nums[4] 追加到 arr1 。
在第 5 次操作中,arr1 中大于 2 的元素数量大于 arr2 中的数量(2 > 1),因此,将 nums[5] 追加到 arr1 。
在 5 次操作后,arr1 = [5,3,1,2] ,arr2 = [14] 。
因此,连接形成的数组 result 是 [5,3,1,2,14] 。
示例 3:
输入:nums = [3,3,3,3]
输出:[3,3,3,3]
解释:在 4 次操作后,arr1 = [3,3] ,arr2 = [3,3] 。
因此,连接形成的数组 result 是 [3,3,3,3] 。
提示:
3 <= n <= 1e5
1 <= nums[i] <= 1e9
树状数组,灵神题解:
class Fenwick {
vector<int> tree;
public:
Fenwick(int n) : tree(n) {
}
// 把下标为 i 的元素增加 1
void add(int i) {
while (i < tree.size()) {
tree[i]++;
i += i & -i;
}
}
// 返回下标在 [1,i] 的元素之和
int pre(int i) {
int res = 0;
while (i > 0) {
res += tree[i];
i &= i - 1;
}
return res;
}
};
class Solution {
public:
vector<int> resultArray(vector<int>& nums) {
auto sorted = nums;
ranges::sort(sorted);
sorted.erase(unique(sorted.begin(), sorted.end()), sorted.end());
int m = sorted.size();
vector<int> a{
nums[0]}, b{
nums[1]};
Fenwick t1(m + 1), t2(m + 1);
t1.add(ranges::lower_bound(sorted, nums[0]) - sorted.begin() + 1);
t2.add(ranges::lower_bound(sorted, nums[1]) - sorted.begin() + 1);
for (int i = 2; i < nums.size(); i++) {
int x = nums[i];
int v = ranges::lower_bound(sorted, x) - sorted.begin() + 1;
int gc1 = a.size() - t1.pre(v); // greaterCount(a, v)
int gc2 = b.size() - t2.pre(v); // greaterCount(b, v)
if (gc1 > gc2 || gc1 == gc2 && a.size() <= b.size()) {
a.push_back(x);
t1.add(v);
} else {
b.push_back(x);
t2.add(v);
}
}
a.insert(a.end(), b.begin(), b.end());
return a;
}
};
题解链接:树状数组
2938. 区分黑球与白球
桌子上有 n 个球,每个球的颜色不是黑色,就是白色。
给你一个长度为 n 、下标从 0 开始的二进制字符串 s,其中 1 和 0 分别代表黑色和白色的球。
在每一步中,你可以选择两个相邻的球并交换它们。
返回「将所有黑色球都移到右侧,所有白色球都移到左侧所需的 最小步数」。
示例 1:
输入:s = “101”
输出:1
解释:我们可以按以下方式将所有黑色球移到右侧:
- 交换 s[0] 和 s[1],s = “011”。
最开始,1 没有都在右侧,需要至少 1 步将其移到右侧。
示例 2:
输入:s = “100”
输出:2
解释:我们可以按以下方式将所有黑色球移到右侧:
- 交换 s[0] 和 s[1],s = “010”。
- 交换 s[1] 和 s[2],s = “001”。
可以证明所需的最小步数为 2 。
示例 3:
输入:s = “0111”
输出:0
解释:所有黑色球都已经在右侧。
提示:
1 <= n == s.length <= 1e5
s[i] 不是 ‘0’,就是 ‘1’。
简单贪心:
class Solution {
public:
long long minimumSteps(string s) {
long long black = 0, swaps = 0;
for (char c : s) {
if (c == '1') {
black++;
} else {
swaps += black;
}
}
return swaps;
}
};
3038. 相同分数的最大操作数目 I
给你一个整数数组 nums ,如果 nums 至少 包含 2 个元素,你可以执行以下操作:
选择 nums 中的前两个元素并将它们删除。
一次操作的 分数 是被删除元素的和。
在确保 所有操作分数相同 的前提下,请你求出 最多 能进行多少次操作。
请你返回按照上述要求 最多 可以进行的操作次数。
示例 1:
输入:nums = [3,2,1,4,5]
输出:2
解释:我们执行以下操作:
- 删除前两个元素,分数为 3 + 2 = 5 ,nums = [1,4,5] 。
- 删除前两个元素,分数为 1 + 4 = 5 ,nums = [5] 。
由于只剩下 1 个元素,我们无法继续进行任何操作。
示例 2:
输入:nums = [3,2,6,1,4]
输出:1
解释:我们执行以下操作:
- 删除前两个元素,分数为 3 + 2 = 5 ,nums = [6,1,4] 。
由于下一次操作的分数与前一次不相等,我们无法继续进行任何操作。
提示:
2 <= nums.length <= 100
1 <= nums[i] <= 1000
简单模拟:
class Solution {
public:
int maxOperations(vector<int> &nums) {
int s = nums[0] + nums[1];
int ans = 1;
for (int i = 3; i < nums.size() && nums[i - 1] + nums[i] == s; i += 2) {
ans++;
}
return ans;
}
};
3040. 相同分数的最大操作数目 II
给你一个整数数组 nums ,如果 nums 至少 包含 2 个元素,你可以执行以下操作中的 任意 一个:
选择 nums 中最前面两个元素并且删除它们。
选择 nums 中最后两个元素并且删除它们。
选择 nums 中第一个和最后一个元素并且删除它们。
一次操作的 分数 是被删除元素的和。
在确保 所有操作分数相同 的前提下,请你求出 最多 能进行多少次操作。
请你返回按照上述要求 最多 可以进行的操作次数。
示例 1:
输入:nums = [3,2,1,2,3,4]
输出:3
解释:我们执行以下操作:
- 删除前两个元素,分数为 3 + 2 = 5 ,nums = [1,2,3,4] 。
- 删除第一个元素和最后一个元素,分数为 1 + 4 = 5 ,nums = [2,3] 。
- 删除第一个元素和最后一个元素,分数为 2 + 3 = 5 ,nums = [] 。
由于 nums 为空,我们无法继续进行任何操作。
示例 2:
输入:nums = [3,2,6,1,4]
输出:2
解释:我们执行以下操作:
- 删除前两个元素,分数为 3 + 2 = 5 ,nums = [6,1,4] 。
- 删除最后两个元素,分数为 1 + 4 = 5 ,nums = [6] 。
至多进行 2 次操作。
提示:
2 <= nums.length <= 2000
1 <= nums[i] <= 1000
记忆化搜索,灵神题解,学到了学到了:
class Solution:
def maxOperations(self, nums: List[int]) -> int:
@cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)
def dfs(i: int, j: int, target: int) -> int:
nonlocal done
if done:
return 0
if i >= j:
done = True
return 0
res = 0
if nums[i] + nums[i + 1] == target: # 删除前两个数
res = max(res, dfs(i + 2, j, target) + 1)
if nums[j - 1] + nums[j] == target: # 删除后两个数
res = max(res, dfs(i, j - 2, target) + 1)
if nums[i] + nums[j] == target: # 删除第一个和最后一个数
res = max(res, dfs(i + 1, j - 1, target) + 1)
return res
done = False
n = len(nums)
res1 = dfs(2, n - 1, nums[0] + nums[1]) # 删除前两个数
res2 = dfs(0, n - 3, nums[-2] + nums[-1]) # 删除后两个数
res3 = dfs(1, n - 2, nums[0] + nums[-1]) # 删除第一个和最后一个数
return max(res1, res2, res3) + 1 # 加上第一次操作
312. 戳气球(Hard)
有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。
求所能获得硬币的最大数量。
示例 1:
输入:nums = [3,1,5,8]
输出:167
解释:
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 315 + 358 + 138 + 181 = 167
示例 2:
输入:nums = [1,5]
输出:10
提示:
n == nums.length
1 <= n <= 300
0 <= nums[i] <= 100
还是记忆化搜索,菜鸡还是不会,抄的官方题解:
class Solution {
public:
vector<vector<int>> rec;
vector<int> val;
public:
int solve(int left, int right) {
if (left >= right - 1) {
return 0;
}
if (rec[left][right] != -1) {
return rec[left][right];
}
for (int i = left + 1; i < right; i++) {
int sum = val[left] * val[i] * val[right];
sum += solve(left, i) + solve(i, right);
rec[left][right] = max(rec[left][right], sum);
}
return rec[left][right];
}
int maxCoins(vector<int>& nums) {
int n = nums.size();
val.resize(n + 2);
for (int i = 1; i <= n; i++) {
val[i] = nums[i - 1];
}
val[0] = val[n + 1] = 1;
rec.resize(n + 2, vector<int>(n + 2, -1));
return solve(0, n + 1);
}
};
881. 救生艇
给定数组 people 。people[i]表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit。
每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。
返回 承载所有人所需的最小船数 。
示例 1:
输入:people = [1,2], limit = 3
输出:1
解释:1 艘船载 (1, 2)
示例 2:
输入:people = [3,2,2,1], limit = 3
输出:3
解释:3 艘船分别载 (1, 2), (2) 和 (3)
示例 3:
输入:people = [3,5,3,4], limit = 5
输出:4
解释:4 艘船分别载 (3), (3), (4), (5)
提示:
1 <= people.length <= 5 * 104
1 <= people[i] <= limit <= 3 * 104
贪心:
class Solution {
public:
int numRescueBoats(vector<int>& people, int limit) {
sort(people.begin(), people.end());
int i = 0, j = people.size() - 1;
int ans = 0;
while (i <= j) {
ans++;
if (people[i] + people[j] <= limit)
i++;
j--;
}
return ans;
}
};
419. 甲板上的战舰
给你一个大小为 m x n 的矩阵 board 表示甲板,其中,每个单元格可以是一艘战舰 ‘X’ 或者是一个空位 ‘.’ ,返回在甲板 board 上放置的 战舰 的数量。
战舰 只能水平或者垂直放置在 board 上。换句话说,战舰只能按 1 x k(1 行,k 列)或 k x 1(k 行,1 列)的形状建造,其中 k 可以是任意大小。两艘战舰之间至少有一个水平或垂直的空位分隔 (即没有相邻的战舰)。
示例 1:
输入:board = [[“X”,“.”,“.”,“X”],[“.”,“.”,“.”,“X”],[“.”,“.”,“.”,“X”]]
输出:2
示例 2:
输入:board = [[“.”]]
输出:0
提示:
m == board.length
n == board[i].length
1 <= m, n <= 200
board[i][j] 是 ‘.’ 或 ‘X’
进阶:你可以实现一次扫描算法,并只使用 O(1) 额外空间,并且不修改 board 的值来解决这个问题吗?
遍历枚举即可:
class Solution {
public:
int countBattleships(vector<vector<char>>& board) {
int count = 0;
for (int i = 0; i < board.size(); i++) {
for (int j = 0; j < board[0].size(); j++) {
if (board[i][j] == 'X') {
if (i > 0 && board[i - 1][j] == 'X')
continue;
if (j > 0 && board[i][j - 1] == 'X')
continue;
count++;
}
}
}
return count;
}
};
2806. 取整购买后的账户余额
一开始,你的银行账户里有 100 块钱。
给你一个整数purchaseAmount ,它表示你在一次购买中愿意支出的金额。
在一个商店里,你进行一次购买,实际支出的金额会向 最近 的 10 的 倍数 取整。换句话说,你实际会支付一个 非负 金额 roundedAmount ,满足 roundedAmount 是 10 的倍数且 abs(roundedAmount - purchaseAmount) 的值 最小 。
如果存在多于一个最接近的 10 的倍数,较大的倍数 是你的实际支出金额。
请你返回一个整数,表示你在愿意支出金额为 purchaseAmount 块钱的前提下,购买之后剩下的余额。
注意: 0 也是 10 的倍数。
示例 1:
输入:purchaseAmount = 9
输出:90
解释:这个例子中,最接近 9 的 10 的倍数是 10 。所以你的账户余额为 100 - 10 = 90 。
示例 2:
输入:purchaseAmount = 15
输出:80
解释:这个例子中,有 2 个最接近 15 的 10 的倍数:10 和 20,较大的数 20 是你的实际开销。
所以你的账户余额为 100 - 20 = 80 。
提示:
0 <= purchaseAmount <= 100
数学推导出式子:
class Solution {
public:
int accountBalanceAfterPurchase(int purchaseAmount) {
return 100 - ((purchaseAmount + 5) / 10) * 10;
}
};
2813. 子序列最大优雅度(Hard)
给你一个长度为 n 的二维整数数组 items 和一个整数 k 。
items[i] = [profiti, categoryi],其中 profiti 和 categoryi 分别表示第 i 个项目的利润和类别。
现定义 items 的 子序列 的 优雅度 可以用 total_profit + distinct_categories2 计算,其中 total_profit 是子序列中所有项目的利润总和,distinct_categories 是所选子序列所含的所有类别中不同类别的数量。
你的任务是从 items 所有长度为 k 的子序列中,找出 最大优雅度 。
用整数形式表示并返回 items 中所有长度恰好为 k 的子序列的最大优雅度。
注意:数组的子序列是经由原数组删除一些元素(可能不删除)而产生的新数组,且删除不改变其余元素相对顺序。
示例 1:
输入:items = [[3,2],[5,1],[10,1]], k = 2
输出:17
解释:
在这个例子中,我们需要选出长度为 2 的子序列。
其中一种方案是 items[0] = [3,2] 和 items[2] = [10,1] 。
子序列的总利润为 3 + 10 = 13 ,子序列包含 2 种不同类别 [2,1] 。
因此,优雅度为 13 + 22 = 17 ,可以证明 17 是可以获得的最大优雅度。
示例 2:
输入:items = [[3,1],[3,1],[2,2],[5,3]], k = 3
输出:19
解释:
在这个例子中,我们需要选出长度为 3 的子序列。
其中一种方案是 items[0] = [3,1] ,items[2] = [2,2] 和 items[3] = [5,3] 。
子序列的总利润为 3 + 2 + 5 = 10 ,子序列包含 3 种不同类别 [1, 2, 3] 。
因此,优雅度为 10 + 32 = 19 ,可以证明 19 是可以获得的最大优雅度。
示例 3:
输入:items = [[1,1],[2,1],[3,1]], k = 3
输出:7
解释:
在这个例子中,我们需要选出长度为 3 的子序列。
我们需要选中所有项目。
子序列的总利润为 1 + 2 + 3 = 6,子序列包含 1 种不同类别 [1] 。
因此,最大优雅度为 6 + 12 = 7 。
提示:
1 <= items.length == n <= 1e5
items[i].length == 2
items[i][0] == profiti
items[i][1] == categoryi
1 <= profiti <= 1e9
1 <= categoryi <= n
1 <= k <= n
反悔贪心,灵神题解,第一次见到这种题:
class Solution {
public:
long long findMaximumElegance(vector<vector<int>>& items, int k) {
// 把利润从大到小排序
ranges::sort(items,
[](const auto& a, const auto& b) {
return a[0] > b[0]; });
long long ans = 0, total_profit = 0;
unordered_set<int> vis;
stack<int> duplicate; // 重复类别的利润
for (int i = 0; i < items.size(); i++) {
int profit = items[i][0], category = items[i][1];
if (i < k) {
total_profit += profit; // 累加前 k 个项目的利润
if (!vis.insert(category).second) {
// 重复类别
duplicate.push(profit);
}
} else if (!duplicate.empty() &&
vis.insert(category).second) {
// 之前没有的类别
total_profit +=
profit - duplicate.top(); // 选一个重复类别中的最小利润替换
duplicate.pop();
} // else:比前面的利润小,而且类别还重复了,选它只会让 total_profit
// 变小,vis.size() 不变,优雅度不会变大
ans = max(ans, total_profit +
(long long)vis.size() * (long long)vis.size());
}
return ans;
}
};
题解链接:反悔贪心
2786. 访问数组中的位置使分数最大
给你一个下标从 0 开始的整数数组 nums 和一个正整数 x 。
你 一开始 在数组的位置 0 处,你可以按照下述规则访问数组中的其他位置:
如果你当前在位置 i ,那么你可以移动到满足 i < j 的 任意 位置 j 。
对于你访问的位置 i ,你可以获得分数 nums[i] 。
如果你从位置 i 移动到位置 j 且 nums[i] 和 nums[j] 的 奇偶性 不同,那么你将失去分数 x 。
请你返回你能得到的 最大 得分之和。
注意 ,你一开始的分数为 nums[0] 。
示例 1:
输入:nums = [2,3,6,1,9,2], x = 5
输出:13
解释:我们可以按顺序访问数组中的位置:0 -> 2 -> 3 -> 4 。
对应位置的值为 2 ,6 ,1 和 9 。因为 6 和 1 的奇偶性不同,所以下标从 2 -> 3 让你失去 x = 5 分。
总得分为:2 + 6 + 1 + 9 - 5 = 13 。
示例 2:
输入:nums = [2,4,6,8], x = 3
输出:20
解释:数组中的所有元素奇偶性都一样,所以我们可以将每个元素都访问一次,而且不会失去任何分数。
总得分为:2 + 4 + 6 + 8 = 20 。
提示:
2 <= nums.length <= 1e5
1 <= nums[i], x <= 1e6
DP,但是wa五发没出,灵神题解:
class Solution {
public:
long long maxScore(vector<int>& nums, int x) {
int n = nums.size();
vector<array<long long, 2>> memo(n, {
-1, -1}); // -1 表示没有计算过
auto dfs = [&](auto&& dfs, int i, int j) -> long long {
if (i == n) {
return 0;
}
long long& res = memo[i][j]; // 注意这里是引用
if (res != -1) {
// 之前计算过
return res;
}
if (nums[i] % 2 != j) {
return res = dfs(dfs, i + 1, j);
}
return res = max(dfs(dfs, i + 1, j), dfs(dfs, i + 1, j ^ 1) - x) +
nums[i];
};
return dfs(dfs, 0, nums[0] % 2);
}
};