100115. Find Champion 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 队将会是 冠军 。
返回这场比赛中将会成为冠军的队伍。
测试样例:
输入:grid = [[0,1],[0,0]]
输出:0
解释:
比赛中有两支队伍。
grid[0][1] == 1 表示 0 队比 1 队强。所以 0 队是冠军。
解答:这题就是利用暴力运算,查看那支队可以打败所有其他队伍,即sum(row[i]) == n - 1
class Solution {
public int findChampion(int[][] grid) {
int n = grid.length;
for (int i = 0; i < n; ++i) {
int add = 0;
for (int j = 0; j < n; ++j) {
if (j != i) {
add += grid[i][j];
}
}
if (add == n - 1) {
return i;
}
}
return -1;
}
}
100116. Find Champion 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 的有向边。
- 有向无环图 是不存在任何环的有向图。
测试样例:
输入:n = 3, edges = [[0,1],[1,2]]
输出:0
解释:
1 队比 0 队弱。2 队比 1 队弱。所以冠军是 0 队。
解答:这道题目范围比较小,其实暴力DFS就行了。注意存在环的情况(没认真看题,WA几次。。。)
class Solution {
public int findChampion(int n, int[][] _edges) {
List<Integer>[] edges = new List[n];
for (int i = 0; i < n; ++i) {
edges[i] = new ArrayList<>();
}
for (int[] e : _edges) {
edges[e[0]].add(e[1]);
}
for (int i = 0; i < n; ++i) {
if (isValid(i, edges)) {
return i;
}
}
return -1;
}
private boolean isValid(int pos, List<Integer>[] edges) {
Set<Integer> set = new HashSet<>();
dfs(pos, edges, set);
return set.size() == edges.length;
}
private void dfs(int pos, List<Integer>[] edges, Set<Integer> set) {
if (set.contains(pos)) {
return;
}
set.add(pos);
for (int n : edges[pos]) {
dfs(n, edges, set);
}
}
}
100118. Maximum Score After Applying Operations on a Tree
有一棵 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 ,那么我们称这棵树是 健康的 。
你可以对这棵树执行任意次操作,但要求执行完所有操作以后树是 健康的 ,请你返回你可以获得的 最大分数 。
测试样例:
输入: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种状态。
class Solution {
private List<Integer>[] edges;
private int[] values;
private Long[][] mem;
public long maximumScoreAfterOperations(int[][] _edges, int[] values) {
this.edges = new List[values.length];
for (int i = 0; i < values.length; ++i) {
edges[i] = new ArrayList<>();
}
for (int[] e : _edges) {
edges[e[0]].add(e[1]);
edges[e[1]].add(e[0]);
}
this.values = values;
mem = new Long[values.length][2];
return dfs(0, 0, 0);
}
private long dfs(int pos, int status, int last) {
if (status == 0 && edges[pos].size() == 1 && pos != 0) {
return 0;
} else if (mem[pos][status] == null) {
if (status == 1) {
mem[pos][status] = (long) values[pos];
for (int n : edges[pos]) {
if (n != last) {
mem[pos][status] += dfs(n, status, pos);
}
}
} else {
long n1 = 0, n2 = values[pos];
for (int n : edges[pos]) {
if (n != last) {
n1 += dfs(n, 1, pos);
n2 += dfs(n, 0, pos);
}
}
mem[pos][0] = Math.max(n1, n2);
}
}
return mem[pos][status];
}
}
100112. Maximum Balanced Subsequence Sum
给你一个下标从 0 开始的整数数组 nums 。
nums 一个长度为 k 的 子序列 指的是选出 k 个 下标 i0 < i1 < ... < ik-1 ,如果这个子序列满足以下条件,我们说它是 平衡的 :
- 对于范围 [1, k - 1] 内的所有 j ,nums[ij] - nums[ij-1] >= ij - ij-1 都成立。
nums 长度为 1 的 子序列 是平衡的。
请你返回一个整数,表示 nums 平衡 子序列里面的 最大元素和 。
一个数组的 子序列 指的是从原数组中删除一些元素(也可能一个元素也不删除)后,剩余元素保持相对顺序得到的 非空 新数组。
测试样例
输入: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 。
解答:nums[ij] - nums[ij-1] >= ij - ij-1可以变成nums[ij] - ij >= nums[ij-1] - ij-1。这样每个位置只需要查看nums[i] - i的值,然后保证之前的nums[i] - i只能是单调递增。可以利用线段数来简化coding,这样就能一边遍历,一边动态的变化最大值。
class Solution {
private class SegmentTree {
long[] tree;
int n;
public SegmentTree(int n) {
if (n > 0) {
this.n = n;
tree = new long[n * 2];
}
}
public void update(int pos, long val) {
pos += n;
tree[pos] = val;
while (pos > 0) {
int left = pos;
int right = pos;
if (pos % 2 == 0) {
right = pos + 1;
} else {
left = pos - 1;
}
tree[pos / 2] = Math.max(tree[left], tree[right]);
pos /= 2;
}
}
public long maxRange(int r) {
int l = n;
r += n;
long max = Long.MIN_VALUE;
while (l <= r) {
if ((l % 2) == 1) {
max = Math.max(max, tree[l]);
l++;
}
if ((r % 2) == 0) {
max = Math.max(tree[r], max);
r--;
}
l /= 2;
r /= 2;
}
return max;
}
}
public long maxBalancedSubsequenceSum(int[] nums) {
int len = nums.length;
int[] diff = new int[len];
for (int i = 0; i < len; ++i) {
diff[i] = nums[i] - i;
}
Map<Integer, Integer> sortMap = sort(diff);
long res = Long.MIN_VALUE;
SegmentTree tree = new SegmentTree(len);
for (int i = 0; i < len; ++i) {
int curDiff = diff[i];
int offset = sortMap.get(curDiff);
long oldMax = tree.maxRange(offset);
res = Math.max(oldMax + nums[i], res);
long updateMax = Math.max(oldMax, oldMax + nums[i]);
tree.update(offset, updateMax);
}
return res;
}
private Map<Integer, Integer> sort(int[] diff) {
TreeSet<Integer> set = new TreeSet<>();
for (int n : diff) {
set.add(n);
}
int offset = 0;
Map<Integer, Integer> res = new HashMap<>();
for (int n : set) {
res.put(n, offset++);
}
return res;
}
}