本文将对力扣第136场夜喵双周赛的T3和T4进行讲解:最少翻转次数使二进制矩阵回文II、标记所有节点需要的时间,涉及分类讨论、动态规划、思维、换根DP等知识点。
T3 - 最少翻转次数使二进制矩阵回文 II
题目链接:100385. 最少翻转次数使二进制矩阵回文 II
题目描述
给你一个 m × n
的二进制矩阵 grid
。
如果矩阵中一行或者一列从前往后与从后往前读是一样的,那么我们称这一行或者这一列是 回文 的。
你可以将 grid
中任意格子的值 翻转 ,也就是将格子里的值从 0
变成 1
,或者从 1
变成 0
。
请你返回 最少 翻转次数,使得矩阵中 所有 行和列都是 回文的 ,且矩阵中 1
的数目可以被 4
整除 。
数据范围
m == grid.length
n == grid[i].length
1 <= m * n <= 2 * 105
0 <= grid[i][j] <= 1
解题思路
分情况讨论。
当 m,n
均为偶数时,我们发现,如果确定一个左上区域的格子,那么其对应的左下、右上、右下的格子也将被确定,因此, “ 1
的数目可以被 4
整除” 这个条件自动满足,故只需求出“使得矩阵中所有行和列都是回文的 ”最少操作数即可。对于一组(左上、左下、右上、右下)四个格子,0
的数量少则将 0
修改为 1
,否则将 1
修改为 0
,这样所需操作数最少。
当 m,n
存在奇数时,定义变量 k,c
- 若
m
为奇数,对于最中间那行,grid[i][j] != grid[i][n - 1 - j]
的位置是需要操作的,操作次数计入k
,操作不妨均为1->0
,1
的数目计入c
。 - 若
n
为奇数,对于最中间那列,grid[i][j] != grid[m-i-1][j]
的位置是需要操作的,操作次数计入k
,操作不妨均为1->0
,1
的数目计入c
。
c
对 4
取模的值要么是 0
,要么是 2
。若为 2
- 若
k
不为0
,则可将一个1->0
改为0->1
,使得取模的值为0
,操作次数不变; - 若
k
为0
,则可翻转一对0
或者1
,操作次数+2
,即为k+2
,也即2
。
对于非最中间列和非最中间行,按 m,n
均为偶数时的情况处理。
特别的,当 m,n
均为奇数时,矩阵中心必须为 0
,可能需要一次操作。
代码实现
int minFlips(vector<vector<int>> &grid) {
auto &g = grid;
int m = g.size(), n = g[0].size(), res = 0;
// 情况一
for (int x = 0, y = m - 1, t; x < y; x++, y--)
for (int u = 0, v = n - 1; u < v; u++, v--) {
t = g[x][u] + g[x][v] + g[y][u] + g[y][v];
res += min(t, 4 - t);
}
// 情况二
int k = 0, c1 = 0;
if (m & 1) {
for (int x = m / 2, i = 0, j = n - 1; i < j; i++, j--) {
if (g[x][i] != g[x][j])k++;
else if (g[x][i])c1++;
}
}
if (n & 1) {
for (int y = n / 2, i = 0, j = m - 1; i < j; i++, j--) {
if (g[i][y] != g[j][y])k++;
else if (g[i][y])c1++;
}
}
res += (c1 % 2 == 0 || k) ? k : 2;
// 情况三
if (m & 1 && n & 1 && g[m / 2][n / 2])res++;
// result
return res;
}
时间复杂度:O(mn)
。
空间复杂度:O(1)
。
T4 - 标记所有节点需要的时间
题目链接:100392. 标记所有节点需要的时间
题目描述
给你一棵 无向 树,树中节点从 0
到 n - 1
编号。同时给你一个长度为 n - 1
的二维整数数组 edges
,其中 edges[i] = [ui, vi]
表示节点 ui
和 vi
在树中有一条边。
一开始,所有 节点都 未标记 。对于节点 i
:
- 当
i
是奇数时,如果时刻x - 1
该节点有 至少 一个相邻节点已经被标记了,那么节点i
会在时刻x
被标记。 - 当
i
是偶数时,如果时刻x - 2
该节点有 至少 一个相邻节点已经被标记了,那么节点i
会在时刻x
被标记。
请你返回一个数组 times
,表示如果你在时刻 t = 0
标记节点 i
,那么时刻 times[i]
时,树中所有节点都会被标记。
请注意,每个 times[i]
的答案都是独立的,即当你标记节点 i
时,所有其他节点都未标记。
数据范围
2 <= n <= 105
edges.length == n - 1
edges[i].length == 2
0 <= edges[i][0], edges[i][1] <= n - 1
- 输入保证
edges
表示一棵合法的树。
解题思路
不妨先考虑根节点为 0
的情况,令 f[x]
= 标记以 x
为根节点的子树所需最长时间,f[x]
通过一遍 dfs
可以得到。思考以其他节点作为树的根节点时,标记所有节点所需最长时间能否由 f[x]
得到。
在以 0
为根节点的树中,选取任意节点 k
,设其父节点为 p
,则,对于以 k
为根节点的树,其根节点到子节点的路径,对应在以 0
为根节点的树中,要么仍然由 k
往下走,要么由 k
往上走。
令 f1[x]
= 标记以 x
为根节点的子树所需最长时间,f2[x]
= 标记以 x
为根节点的子树所需次长时间,注意,最长时间和次长时间可能相等。
令 g[x]
= 由 x
往上走所需最长时间。
由 k
往上走,走到其父节点 p
,接下来可继续往上走,也可往下走,但不能走回 k
。
- 由
p
继续往上走所需最长时间为g[p]
; 由
p
继续往下走所需最长时间为t
- 若
f1[x]
不是由k
得到,则t=f1[x]
; - 若
f1[x]
是由k
得到,则t=f2[x]
。
- 若
综上,g[k]= max( g[p], t ) + (p%2==1?1:2)
。
故,标记以 x
为根节点的树所需最长时间为 max( f[x], g[x])
。
不难发现,这是一道比较传统的换根 DP 题。
代码实现
vector<int> timeTaken(vector<vector<int>> &edges) {
int n = edges.size() + 1;
// 建图-邻接表
int h[n], e[n << 1], ne[n << 1], idx = 0;
memset(h, -1, sizeof h);
auto add = [&](int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
};
for (auto &v: edges)
add(v[0], v[1]), add(v[1], v[0]);
// f1[x]=标记以x为根节点的子树所需最长时间
// f2[x]=标记以x为根节点的子树所需次长时间
int f1[n], f2[n], nf[n];
memset(f1, 0, sizeof f1);
memset(f2, 0, sizeof f2);
// x 当前节点 p x的父节点
function<void(int, int)> dfs1 = [&](int x, int p) {
for (int i = h[x]; ~i; i = ne[i]) {
if (e[i] == p)continue;
// 先求 f1[e[i]] 和 f2[e[i]]
dfs1(e[i], x);
int t = f1[e[i]] + (e[i] & 1 ? 1 : 2);
// 最长时间
if (t >= f1[x])
f2[x] = f1[x], f1[x] = t, nf[x] = e[i];
// 次长时间
else if (t > f2[x])
f2[x] = t;
}
};
dfs1(0, -1);
// g[x]=由x往上走所需最长时间
int g[n];
memset(g, 0, sizeof g);
// x 当前节点 p x的父节点
function<void(int, int)> dfs2 = [&](int x, int p) {
for (int i = h[x]; ~i; i = ne[i]) {
int j = e[i];
if (j == p)continue;
g[j] = max(g[x], nf[x] != j ? f1[x] : f2[x]) + (x & 1 ? 1 : 2);
dfs2(j, x);
}
};
dfs2(0, -1);
// r[x]=标记以x为根节点的树所需最长时间
vector<int> r(n);
for (int i = 0; i < n; i++)
r[i] = max(f1[i], g[i]);
return r;
}
时间复杂度:O(n)
。
空间复杂度:O(n)
。
END
以上就是本文的全部内容,如果觉得有一点点帮助,欢迎点赞、转发加关注,如果有任何问题,欢迎在评论区交流和讨论,咱们下期见!
文章声明:题目来源 力扣 平台,如有侵权,请联系删除!
题目来源:力扣第407场双周赛。
文章文档:公众号 字节幺零二四
回复关键字可获取本文文档。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。