[Leetcode Weekly Contest]299
链接:LeetCode
[Leetcode]2319. 判断矩阵是否是一个 X 矩阵
如果一个正方形矩阵满足下述 全部 条件,则称之为一个 X 矩阵 :
- 矩阵对角线上的所有元素都 不是 0
- 矩阵中所有其他元素都是 0
给你一个大小为 n x n 的二维整数数组 grid ,表示一个正方形矩阵。如果 grid 是一个 X 矩阵 ,返回 true ;否则,返回 false 。
模拟遍历即可。
class Solution {
public boolean checkXMatrix(int[][] grid) {
int n = grid.length;
for(int i=0;i<n;++i) {
for(int j=0;j<n;++j) {
int val = grid[i][j];
if(i==j || i==n-1-j) {
if(val==0) return false;
}
else {
if(val!=0) return false;
}
}
}
return true;
}
}
[Leetcode]2320. 统计放置房子的方式数
一条街道上共有 n * 2 个 地块 ,街道的两侧各有 n 个地块。每一边的地块都按从 1 到 n 编号。每个地块上都可以放置一所房子。
现要求街道同一侧不能存在两所房子相邻的情况,请你计算并返回放置房屋的方式数目。由于答案可能很大,需要对 1E9 + 7 取余后再返回。
注意,如果一所房子放置在这条街某一侧上的第 i 个地块,不影响在另一侧的第 i 个地块放置房子。
单独考虑一侧的房子,定义 \(f[i]\) 表示前 i 个地块的放置方案数,其中第 i 个地块可以放房子,也可以不放房子。
考虑第 i 个地块:
- 若不放房子,那么第 i-1 个地块可放可不放,则有 \(f[i] = f[i-1]\);
- 若放房子,那么第 i-1 个地块无法放房子,第 i-2 个地块可放可不放,则有 \(f[i] = f[i-2]\)。
因此
边界为
- \(f[0]=1\),空也是一种方案;
- \(f[1]=2\),放与不放两种方案。
由于两侧的房屋互相独立,根据乘法原理,答案为 \(f[n]^2\)。
class Solution {
static final int MOD = (int) 1e9 + 7, MX = (int) 1e4 + 1;
static final int[] f = new int[MX];
static {
f[0] = 1;
f[1] = 2;
for (var i = 2; i < MX; ++i)
f[i] = (f[i - 1] + f[i - 2]) % MOD;
}
public int countHousePlacements(int n) {
return (int) ((long) f[n] * f[n] % MOD);
}
}
[Leetcode]2321. 拼接数组的最大分数
给你两个下标从 0 开始的整数数组 nums1 和 nums2 ,长度都是 n 。
你可以选择两个整数 left 和 right ,其中 0 <= left <= right < n ,接着 交换 两个子数组 nums1[left...right] 和 nums2[left...right] 。
- 例如,设 nums1 = [1,2,3,4,5] 和 nums2 = [11,12,13,14,15] ,整数选择 left = 1 和 right = 2,那么 nums1 会变为 [1,12,13,4,5] 而 nums2 会变为 [11,2,3,14,15] 。
你可以选择执行上述操作 一次 或不执行任何操作。
数组的 分数 取 sum(nums1) 和 sum(nums2) 中的最大值,其中 sum(arr) 是数组 arr 中所有元素之和。
返回 可能的最大分数 。
子数组 是数组中连续的一个元素序列。arr[left...right] 表示子数组包含 nums 中下标 left 和 right 之间的元素(含 下标 left 和 right 对应元素)。
转化为最大子数组和求解。
class Solution {
public int maximumsSplicedArray(int[] nums1, int[] nums2) {
return Math.max(solve(nums1, nums2), solve(nums2,nums1));
}
public int solve(int[] nums1, int[] nums2) {
int curSum = 0, res = 0;
int cur = 0, n = nums1.length;
for(int i=0;i<n;++i) {
curSum += nums1[i];
cur = Math.max(0, cur + nums2[i]-nums1[i]);
res = Math.max(cur, res);
}
return curSum + res;
}
}
[Leetcode]2322. 从树中删除边的最小分数
存在一棵无向连通树,树中有编号从 0 到 n - 1 的 n 个节点, 以及 n - 1 条边。
给你一个下标从 0 开始的整数数组 nums ,长度为 n ,其中 nums[i] 表示第 i 个节点的值。另给你一个二维整数数组 edges ,长度为 n - 1 ,其中 edges[i] = [ai, bi] 表示树中存在一条位于节点 ai 和 bi 之间的边。
删除树中两条 不同 的边以形成三个连通组件。对于一种删除边方案,定义如下步骤以计算其分数:
- 分别获取三个组件 每个 组件中所有节点值的异或值。
- 最大 异或值和 最小 异或值的 差值 就是这一种删除边方案的分数。
- 例如,三个组件的节点值分别是:[4,5,7]、[1,9] 和 [3,3,3] 。三个异或值分别是 4 ^ 5 ^ 7 = 6、1 ^ 9 = 8 和 3 ^ 3 ^ 3 = 3 。最大异或值是 8 ,最小异或值是 3 ,分数是 8 - 3 = 5 。
返回在给定树上执行任意删除边方案可能的 最小 分数。
DFS.由于需要求出子树的异或和,不妨以 00 为根,DFS 这棵树,在求出时间戳的同时,求出每棵以 xx 为根的子树的异或和 \(\textit{xor}[x]\)。
由于 n 比较小,我们可以用 \(O(n^2)\)的时间枚举要删除的两条边 \(x_1\text{-}y_1\) 和 $ x_2\text{-}y_2$,并假设 x 是 y 的父节点,这会产生以下三种情况:
- 删除的两条边在同一颗子树内,且 \(y_1\) 是 \(x_2\) 的祖先节点(或重合)。这三个连通块的异或和分别为 \(\textit{xor}[y_2]\)、\(\textit{xor}[y_1]\oplus\textit{xor}[y_2]\) 和 \(\textit{xor}[0]\oplus\textit{xor}[y_1]\)(\(\oplus\) 表示异或运算)。
- 删除的两条边在同一颗子树内,且 \(y_2\) 是 \(x_1\) 的祖先节点(或重合)。
同上,这三个连通块的异或和分别为 \(\textit{xor}[y_1]\)、\(\textit{xor}[y_1]\oplus\textit{xor}[y_2]\) 和 \(\textit{xor}[0]\oplus\textit{xor}[y_2]\) - 删除的两条边分别属于两颗不相交的子树。
这三个连通块的异或和分别为 \(\textit{xor}[y_1]\)、\(\textit{xor}[0]\oplus\textit{xor}[y_1]\oplus\textit{xor}[y_2]\) 和 \(\textit{xor}[y_2]\)
此关键之处在于判断这两条边的关系,这里可以通过DFS来维护一个children的字典,也可以通过时间戳的方式.
代码实现时,由于不知道 \(\textit{edges}[i]\) 两个点的父子关系,枚举边的写法需要额外的判断。我们可以改为枚举不是根的两个点,删除这两个点及其父节点形成的边,这样代码更简洁,效率也略优于枚举边的写法。
class Solution {
HashMap<Integer, Integer> xor = new HashMap<>();
HashMap<Integer, HashSet<Integer>> graph = new HashMap<>();
HashMap<Integer, HashSet<Integer>> children = new HashMap<>();
int clock = 0;
int[] in;
int[] out;
public int minimumScore(int[] nums, int[][] edges) {
int n = nums.length;
in = new int[n];
out = new int[n];
int res = Integer.MAX_VALUE;
for(var edge:edges) {
if(!graph.containsKey(edge[0])) graph.put(edge[0], new HashSet<Integer>());
if(!graph.containsKey(edge[1])) graph.put(edge[1], new HashSet<Integer>());
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]);
}
dfs(0, -1, nums);
// dfs2(0, -1);
int a,b,c;
for(int i=1;i<n;++i) {
for(int j=i+1;j<n;++j) {
//if(children.get(i).contains(j)) {
if (in[i] < in[j] && out[j] <= out[i]) { // i 是 j 的祖先节点
a = xor.get(0) ^ xor.get(i);
b = xor.get(i) ^ xor.get(j);
c = xor.get(j);
}
//else if(children.get(j).contains(i)) {
else if (in[j] < in[i] && out[i] <= out[j]) { // i 是 j 的祖先节点
a = xor.get(0) ^ xor.get(j);
b = xor.get(j) ^ xor.get(i);
c = xor.get(i);
}
else {
a = xor.get(0) ^ xor.get(i) ^ xor.get(j);
b = xor.get(i);
c = xor.get(j);
}
res = Math.min(res, Math.max(c,Math.max(a,b)) - Math.min(c,Math.min(a,b)));
}
}
return res;
}
public int dfs(int node, int preNode, int[] nums) {
clock ++ ;
in[node] = clock;
xor.put(node, nums[node]);
for(var child:graph.get(node)) {
if(child!=preNode) xor.put(node, xor.get(node) ^ dfs(child, node, nums));
}
out[node] = clock;
return xor.get(node);
}
public HashSet<Integer> dfs2(int node, int preNode) {
children.put(node, new HashSet<Integer>());
for(var child:graph.get(node)) {
if(child!=preNode) {
children.get(node).add(child);
for(var childNode: dfs2(child, node)) {
children.get(node).add(childNode);
}
}
}
return children.get(node);
}
}