2923. 找到冠军 I
一场比赛中共有 n
支队伍,按从 0
到 n - 1
编号。
给你一个下标从 0 开始、大小为 n * n
的二维布尔矩阵 grid
。对于满足 0 <= i, j <= n - 1
且 i != j
的所有 i, j
:如果 grid[i][j] == 1
,那么 i
队比 j
队 强 ;否则,j
队比 i
队 强 。
在这场比赛中,如果不存在某支强于 a
队的队伍,则认为 a
队将会是 冠军 。
返回这场比赛中将会成为冠军的队伍。
示例 1:
输入:grid = [[0,1],[0,0]] 输出:0 解释:比赛中有两支队伍。 grid[0][1] == 1 表示 0 队比 1 队强。所以 0 队是冠军。
示例 2:
输入:grid = [[0,0,1],[1,0,1],[0,0,0]] 输出:1 解释:比赛中有三支队伍。 grid[1][0] == 1 表示 1 队比 0 队强。 grid[1][2] == 1 表示 1 队比 2 队强。 所以 1 队是冠军。
提示:
n == grid.length
n == grid[i].length
2 <= n <= 100
grid[i][j]
的值为0
或1
- 对于满足
i != j
的所有i, j
,grid[i][j] != grid[j][i]
均成立 - 生成的输入满足:如果
a
队比b
队强,b
队比c
队强,那么a
队比c
队强
思路:
每个队伍都会比一次,一定会有一个答案,那么其实就很简单了,直接两层循环,直接判断哪个队伍胜场为n-1,哪个就是冠军。
class Solution {
public int findChampion(int[][] grid) {
int ans = 0;
int n = grid.length;
for (int i=0;i<n;i++) {
int cnt = 0;
for (int j=0;j<n;j++) {
if (i != j) {
if (grid[i][j] == 1) cnt +=1;
}
}
if (cnt == n-1) {
ans = i;
break;
}
}
return ans;
}
}
2924. 找到冠军 II
一场比赛中共有 n
支队伍,按从 0
到 n - 1
编号。每支队伍也是 有向无环图(DAG) 上的一个节点。
给你一个整数 n
和一个下标从 0 开始、长度为 m
的二维整数数组 edges
表示这个有向无环图,其中 edges[i] = [ui, vi]
表示图中存在一条从 ui
队到 vi
队的有向边。
从 a
队到 b
队的有向边意味着 a
队比 b
队 强 ,也就是 b
队比 a
队 弱 。
在这场比赛中,如果不存在某支强于 a
队的队伍,则认为 a
队将会是 冠军 。
如果这场比赛存在 唯一 一个冠军,则返回将会成为冠军的队伍。否则,返回 -1
。
注意
- 环 是形如
a1, a2, ..., an, an+1
的一个序列,且满足:节点a1
与节点an+1
是同一个节点;节点a1, a2, ..., an
互不相同;对于范围[1, n]
中的每个i
,均存在一条从节点ai
到节点ai+1
的有向边。 - 有向无环图 是不存在任何环的有向图。
示例 1:
输入:n = 3, edges = [[0,1],[1,2]] 输出:0 解释:1 队比 0 队弱。2 队比 1 队弱。所以冠军是 0 队。
示例 2:
输入:n = 4, edges = [[0,2],[1,3],[1,2]] 输出:-1 解释:2 队比 0 队和 1 队弱。3 队比 1 队弱。但是 1 队和 0 队之间不存在强弱对比。所以答案是 -1 。
提示:
1 <= n <= 100
m == edges.length
0 <= m <= n * (n - 1) / 2
edges[i].length == 2
0 <= edge[i][j] <= n - 1
edges[i][0] != edges[i][1]
- 生成的输入满足:如果
a
队比b
队强,就不存在b
队比a
队强 - 生成的输入满足:如果
a
队比b
队强,b
队比c
队强,那么a
队比c
队强
思路:
因为冠军的队伍不止一个,并且是有向的,那其实就可以直接用入度出度的即可。只要没有败场的队伍,那就是冠军。说白了,其实就是入度为0的队伍,遍历一遍即可。
class Solution {
public int findChampion(int n, int[][] edges) {
int[] ru = new int[n];
int m = edges.length;
for (int i=0;i<m;i++) {
ru[edges[i][1]] += 1;
}
int ans = 0;
int cnt = 0;
for (int i=0;i<n;i++) {
if (ru[i] == 0) {
ans = i;
cnt += 1;
}
}
if (cnt == 1) return ans;
else return -1;
}
}
2925. 在树上执行操作以后得到的最大分数
有一棵 n
个节点的无向树,节点编号为 0
到 n - 1
,根节点编号为 0
。给你一个长度为 n - 1
的二维整数数组 edges
表示这棵树,其中 edges[i] = [ai, bi]
表示树中节点 ai
和 bi
有一条边。
同时给你一个长度为 n
下标从 0 开始的整数数组 values
,其中 values[i]
表示第 i
个节点的值。
一开始你的分数为 0
,每次操作中,你将执行:
- 选择节点
i
。 - 将
values[i]
加入你的分数。 - 将
values[i]
变为0
。
如果从根节点出发,到任意叶子节点经过的路径上的节点值之和都不等于 0 ,那么我们称这棵树是 健康的 。
你可以对这棵树执行任意次操作,但要求执行完所有操作以后树是 健康的 ,请你返回你可以获得的 最大分数 。
示例 1:
输入:edges = [[0,1],[0,2],[0,3],[2,4],[4,5]], values = [5,2,5,2,1,1] 输出:11 解释:我们可以选择节点 1 ,2 ,3 ,4 和 5 。根节点的值是非 0 的。所以从根出发到任意叶子节点路径上节点值之和都不为 0 。所以树是健康的。你的得分之和为 values[1] + values[2] + values[3] + values[4] + values[5] = 11 。 11 是你对树执行任意次操作以后可以获得的最大得分之和。
示例 2:
输入:edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]], values = [20,10,9,7,4,3,5] 输出:40 解释:我们选择节点 0 ,2 ,3 和 4 。 - 从 0 到 4 的节点值之和为 10 。 - 从 0 到 3 的节点值之和为 10 。 - 从 0 到 5 的节点值之和为 3 。 - 从 0 到 6 的节点值之和为 5 。 所以树是健康的。你的得分之和为 values[0] + values[2] + values[3] + values[4] = 40 。 40 是你对树执行任意次操作以后可以获得的最大得分之和。
提示:
2 <= n <= 2 * 10^4
edges.length == n - 1
edges[i].length == 2
0 <= ai, bi < n
values.length == n
1 <= values[i] <= 10^9
- 输入保证
edges
构成一棵合法的树。
思路:
一开始题目没看清楚……我以为是有向树,也没看到根节点是0…
这道题目用到的算法主要是用到了树形DP,其实我们可以用简单的例子看看如何选择。
正难则反,先把所有 values[i] 加到答案中,然后考虑哪些 values[i] 不能选(撤销,不加入答案)。
设当前节点为 x,计算以 x 为根的子树是健康时,失去的最小分数。那么答案就是 values 的元素和,减去「以 0 为根的子树是健康时,失去的最小分数」。
用「选或不选」分类讨论:
第一种情况:失去 values[x],也就是不加入答案,那么 x 的所有子孙节点都可以加入答案,失去的最小分数就是 values[x]。
第二种情况:values[x] 加入答案,问题变成「以 y 为根的子树是健康时,失去的最小分数」,这里 y 是 x 的儿子。如果有多个儿子,累加失去的最小分数。
这两种情况取最小值。注意第一种情况是不会往下递归的,所以当我们递归到叶子的时候,叶子一定不能加入答案,此时直接返回 values[x]。
代码实现时,为了方便判断 x 是否为叶子节点,可以假设还有一条 0 到 −1 的边,这样不会误把根节点 0 当作叶子。
我的代码又臭又长(给大家看看笑话就行)。。。
class Solution {
// 将一些变量直接放入类变量
private ArrayList<ArrayList<Integer>> mp = new ArrayList<ArrayList<Integer>>();
private int[] v;
public long maximumScoreAfterOperations(int[][] edges, int[] values) {
v = values;
mp.clear();
int n = edges.length + 1;
for(int i=0;i<n;i++) {
ArrayList<Integer> tmp = new ArrayList<Integer>();
mp.add(tmp);
}
for (int i=0;i<n-1;i++) {
// 双向图
ArrayList<Integer> tmp = mp.get(edges[i][0]);
tmp.add(edges[i][1]);
ArrayList<Integer> t = mp.get(edges[i][1]);
t.add(edges[i][0]);
}
long[] ans = dfs(0, -1);
return ans[0];
}
// long数组第一个元素表示当前节点的最大价值和,第二个元素是当前节点的sum
public long[] dfs(int root, int last) {
ArrayList<Integer> tmp = mp.get(root);
int n = tmp.size();
if (n == 1 && last != -1) {
// 如果是叶子结点
long[] t = new long[2];
t[0] = 0;
t[1] = v[root];
return t;
}
long[][] child = new long[n][2];
long sum = 0;
long res = 0;
for (int i=0;i<n;i++) {
if (tmp.get(i) == last) continue;
child[i] = dfs(tmp.get(i), root);
sum += child[i][1];
res += child[i][0];
}
long cnt = 0 ;
for (int i=0;i<n;i++) {
cnt += child[i][0];
}
cnt += v[root];
long[] t = new long[2];
// 所有子节点的sum,除去了最小的代价
if (cnt >= sum) {
t[0] = cnt;
t[1] = sum + v[root];
} else {
t[0] = sum;
t[1] = sum + v[root];
}
return t;
}
}
人家的代码:
class Solution {
public long maximumScoreAfterOperations(int[][] edges, int[] values) {
List<Integer>[] g = new ArrayList[values.length];
Arrays.setAll(g, e -> new ArrayList<>());
g[0].add(-1); // 避免误把根节点当作叶子
for (int[] e : edges) {
int x = e[0], y = e[1];
g[x].add(y);
g[y].add(x);
}
// 先把所有分数加入答案
long ans = 0;
for (int v : values) {
ans += v;
}
return ans - dfs(0, -1, g, values);
}
// dfs(x) 计算以 x 为根的子树是健康时,失去的最小分数
private long dfs(int x, int fa, List<Integer>[] g, int[] values) {
if (g[x].size() == 1) { // x 是叶子
return values[x];
}
long loss = 0; // 第二种情况
for (int y : g[x]) {
if (y != fa) {
loss += dfs(y, x, g, values); // 计算以 y 为根的子树是健康时,失去的最小分数
}
}
return Math.min(values[x], loss); // 两种情况取最小值
}
}
2926. 平衡子序列的最大和
给你一个下标从 0 开始的整数数组 nums
。
nums
一个长度为 k
的 子序列 指的是选出 k
个 下标 i0 < i1 < ... < ik-1
,如果这个子序列满足以下条件,我们说它是 平衡的 :
- 对于范围
[1, k - 1]
内的所有j
,nums[ij] - nums[ij-1] >= ij - ij-1
都成立。
nums
长度为 1
的 子序列 是平衡的。
请你返回一个整数,表示 nums
平衡 子序列里面的 最大元素和 。
一个数组的 子序列 指的是从原数组中删除一些元素(也可能一个元素也不删除)后,剩余元素保持相对顺序得到的 非空 新数组。
示例 1:
输入:nums = [3,3,5,6] 输出:14 解释:这个例子中,选择子序列 [3,5,6] ,下标为 0 ,2 和 3 的元素被选中。 nums[2] - nums[0] >= 2 - 0 。 nums[3] - nums[2] >= 3 - 2 。 所以,这是一个平衡子序列,且它的和是所有平衡子序列里最大的。 包含下标 1 ,2 和 3 的子序列也是一个平衡的子序列。 最大平衡子序列和为 14 。
示例 2:
输入:nums = [5,-1,-3,8] 输出:13 解释:这个例子中,选择子序列 [5,8] ,下标为 0 和 3 的元素被选中。 nums[3] - nums[0] >= 3 - 0 。 所以,这是一个平衡子序列,且它的和是所有平衡子序列里最大的。 最大平衡子序列和为 13 。
示例 3:
输入:nums = [-2,-1] 输出:-1 解释:这个例子中,选择子序列 [-1] 。 这是一个平衡子序列,而且它的和是 nums 所有平衡子序列里最大的。
提示:
1 <= nums.length <= 10^5
-10^9 <= nums[i] <= 10^9
思路:
https://leetcode.cn/problems/maximum-balanced-subsequence-sum/
写不出来,直接给个链接先了。。。
class Solution {
public long maxBalancedSubsequenceSum(int[] nums) {
int n = nums.length;
int[] b = new int[n];
for (int i = 0; i < n; i++) {
b[i] = nums[i] - i;
}
Arrays.sort(b);
BIT t = new BIT(b.length + 1);
for (int i = 0; i < n; i++) {
// j 为 nums[i]-i 离散化后的值(从 1 开始)
int j = Arrays.binarySearch(b, nums[i] - i) + 1;
long f = Math.max(t.preMax(j), 0) + nums[i];
t.update(j, f);
}
return t.preMax(b.length);
}
}
// 树状数组模板(维护前缀最大值)
class BIT {
private long[] tree;
public BIT(int n) {
tree = new long[n];
Arrays.fill(tree, Long.MIN_VALUE);
}
public void update(int i, long val) {
while (i < tree.length) {
tree[i] = Math.max(tree[i], val);
i += i & -i;
}
}
public long preMax(int i) {
long res = Long.MIN_VALUE;
while (i > 0) {
res = Math.max(res, tree[i]);
i &= i - 1;
}
return res;
}
}