力扣周赛 | 力扣第136场夜喵双周赛

本文将对力扣第136场夜喵双周赛的T3和T4进行讲解:最少翻转次数使二进制矩阵回文II、标记所有节点需要的时间,涉及分类讨论、动态规划、思维、换根DP等知识点。

T3 - 最少翻转次数使二进制矩阵回文 II

题目链接:100385. 最少翻转次数使二进制矩阵回文 II

题目描述

给你一个 m × n m × n m×n 的二进制矩阵 g r i d grid grid

如果矩阵中一行或者一列从前往后与从后往前读是一样的,那么我们称这一行或者这一列是 回文 的。

你可以将 g r i d grid grid 中任意格子的值 翻转 ,也就是将格子里的值从 0 0 0 变成 1 1 1 ,或者从 1 1 1 变成 0 0 0

请你返回 最少 翻转次数,使得矩阵中 所有 行和列都是 回文的 ,且矩阵中 1 1 1 的数目可以被 4 4 4 整除

数据范围
  • m = = g r i d . l e n g t h m == grid.length m==grid.length
  • n = = g r i d [ i ] . l e n g t h n == grid[i].length n==grid[i].length
  • 1 < = m ∗ n < = 2 ∗ 105 1 <= m * n <= 2 * 105 1<=mn<=2105
  • 0 < = g r i d [ i ] [ j ] < = 1 0 <= grid[i][j] <= 1 0<=grid[i][j]<=1
解题思路

分情况讨论。

m , n m,n m,n 均为偶数时,我们发现,如果确定一个左上区域的格子,那么其对应的左下、右上、右下的格子也将被确定,因此, “ 1 1 1 的数目可以被 4 4 4 整除” 这个条件自动满足,故只需求出“使得矩阵中所有行和列都是回文的 ”最少操作数即可。对于一组(左上、左下、右上、右下)四个格子, 0 0 0 的数量少则将 0 0 0 修改为 1 1 1,否则将 1 1 1 修改为 0 0 0,这样所需操作数最少。

m , n m,n m,n 存在奇数时,定义变量 k , c k,c k,c

  • m m m 为奇数,对于最中间那行, g r i d [ i ] [ j ] ! = g r i d [ i ] [ n − 1 − j ] grid[i][j] != grid[i][n - 1 - j] grid[i][j]!=grid[i][n1j] 的位置是需要操作的,操作次数计入 k k k,操作不妨均为 1 − > 0 1->0 1>0 1 1 1 的数目计入 c c c
  • n n n 为奇数,对于最中间那列, g r i d [ i ] [ j ] ! = g r i d [ m − i − 1 ] [ j ] grid[i][j] != grid[m-i-1][j] grid[i][j]!=grid[mi1][j] 的位置是需要操作的,操作次数计入 k k k,操作不妨均为 1 − > 0 1->0 1>0 1 1 1 的数目计入 c c c

c c c 4 4 4 取模的值要么是 0 0 0,要么是 2 2 2。若为 2 2 2

  • k k k 不为 0 0 0,则可将一个 1 − > 0 1->0 1>0 改为 0 − > 1 0->1 0>1,使得取模的值为 0 0 0,操作次数不变;
  • k k k 0 0 0,则可翻转一对 0 0 0 或者 1 1 1,操作次数 + 2 +2 +2,即为 k + 2 k+2 k+2,也即 2 2 2

对于非最中间列和非最中间行,按 m , n m,n m,n 均为偶数时的情况处理。

特别的,当 m , n m,n m,n 均为奇数时,矩阵中心必须为 0 0 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 ( m n ) O(mn) O(mn)

空间复杂度: O ( 1 ) O(1) O(1)

T4 - 标记所有节点需要的时间

题目链接:100392. 标记所有节点需要的时间

题目描述

给你一棵 无向 树,树中节点从 0 0 0 n − 1 n - 1 n1 编号。同时给你一个长度为 n − 1 n - 1 n1 的二维整数数组 e d g e s edges edges ,其中 e d g e s [ i ] = [ u i , v i ] edges[i] = [ui, vi] edges[i]=[ui,vi] 表示节点 u i ui ui v i vi vi 在树中有一条边。

一开始,所有 节点都 未标记 。对于节点 i i i

  • i i i 是奇数时,如果时刻 x − 1 x - 1 x1 该节点有 至少 一个相邻节点已经被标记了,那么节点 i i i 会在时刻 x x x 被标记。
  • i i i 是偶数时,如果时刻 x − 2 x - 2 x2 该节点有 至少 一个相邻节点已经被标记了,那么节点 i i i 会在时刻 x x x 被标记。

请你返回一个数组 t i m e s times times ,表示如果你在时刻 t = 0 t = 0 t=0 标记节点 i i i ,那么时刻 t i m e s [ i ] times[i] times[i] 时,树中所有节点都会被标记。

请注意,每个 t i m e s [ i ] times[i] times[i] 的答案都是独立的,即当你标记节点 i i i 时,所有其他节点都未标记。

数据范围
  • 2 < = n < = 105 2 <= n <= 105 2<=n<=105
  • e d g e s . l e n g t h = = n − 1 edges.length == n - 1 edges.length==n1
  • e d g e s [ i ] . l e n g t h = = 2 edges[i].length == 2 edges[i].length==2
  • 0 < = e d g e s [ i ] [ 0 ] , e d g e s [ i ] [ 1 ] < = n − 1 0 <= edges[i][0], edges[i][1] <= n - 1 0<=edges[i][0],edges[i][1]<=n1
  • 输入保证 e d g e s edges edges 表示一棵合法的树。
解题思路

不妨先考虑根节点为 0 0 0 的情况,令 f [ x ] f[x] f[x] = 标记以 x x x 为根节点的子树所需最长时间, f [ x ] f[x] f[x] 通过一遍 d f s dfs dfs 可以得到。思考以其他节点作为树的根节点时,标记所有节点所需最长时间能否由 f [ x ] f[x] f[x] 得到。

在以 0 0 0 为根节点的树中,选取任意节点 k k k,设其父节点为 p p p,则,对于以 k k k 为根节点的树,其根节点到子节点的路径,对应在以 0 0 0 为根节点的树中,要么仍然由 k k k 往下走,要么由 k k k 往上走。

f 1 [ x ] f1[x] f1[x] = 标记以 x x x 为根节点的子树所需最长时间, f 2 [ x ] f2[x] f2[x] = 标记以 x x x 为根节点的子树所需次长时间,注意,最长时间和次长时间可能相等。

g [ x ] g[x] g[x] = 由 x x x 往上走所需最长时间。

k k k 往上走,走到其父节点 p p p,接下来可继续往上走,也可往下走,但不能走回 k k k

  • p p p 继续往上走所需最长时间为 g [ p ] g[p] g[p]
  • p p p 继续往下走所需最长时间为 t t t
    • f 1 [ x ] f1[x] f1[x] 不是由 k k k 得到,则 t = f 1 [ x ] t=f1[x] t=f1[x]
    • f 1 [ x ] f1[x] f1[x] 是由 k k k 得到,则 t = f 2 [ x ] t=f2[x] t=f2[x]

综上, g [ k ] = m a x ( g [ p ] , t ) + ( p % 2 = = 1 ? 1 : 2 ) g[k]= max( g[p], t ) + (p\%2==1?1:2) g[k]=max(g[p],t)+(p%2==1?1:2)

故,标记以 x x x 为根节点的树所需最长时间为 m a x ( f [ x ] , g [ x ] ) max( f[x], g[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) O(n)

空间复杂度: O ( n ) O(n) O(n)

END

以上就是本文的全部内容,如果觉得有一点点帮助,欢迎点赞、转发加关注,如果有任何问题,欢迎在评论区交流和讨论,咱们下期见!

文章声明:题目来源 力扣 平台,如有侵权,请联系删除!

题目来源:力扣第407场双周赛

文章文档:公众号 字节幺零二四 回复关键字可获取本文文档。

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值