LeetCode 第206场周赛 题解

a.二进制矩阵中的特殊位置

a.题目

给你一个大小为 rows x cols 的矩阵 mat,其中 mat[i][j] 是 0 或 1,请返回 矩阵 mat 中特殊位置的数目 。

特殊位置 定义:如果 mat[i][j] == 1 并且第 i 行和第 j 列中的所有其他元素均为 0(行和列的下标均 从 0 开始 ),则位置 (i, j) 被称为特殊位置。

示例 1

输入:mat = [[1,0,0],
  [0,0,1],
  [1,0,0]]
输出:1
解释:(1,2) 是一个特殊位置,因为 mat[1][2] == 1 且所处的行和列上所有其他元素都是 0

示例 2

输入:mat = [[1,0,0],
  [0,1,0],
  [0,0,1]]
输出:3
解释:(0,0), (1,1) 和 (2,2) 都是特殊位置

示例 3

输入:mat = [[0,0,0,1],
  [1,0,0,0],
  [0,1,1,0],
  [0,0,0,0]]
输出:2

示例 4

输入:mat = [[0,0,0,0,0],
  [1,0,0,0,0],
  [0,1,0,0,0],
  [0,0,1,0,0],
  [0,0,0,1,1]]
输出:3

提示

  • rows == mat.length
  • cols == mat[i].length
  • 1 <= rows, cols <= 100
  • mat[i][j] 是 0 或 1

a.分析

直接暴力就行了 但是暴力之前都还是得计算一下复杂度
首先遍历整个矩阵是O(nm)的 然后每次都去检查一下行和列的话是O(n+m)的
因此总的复杂度是O(nm(n+m)) 完全可以过的
所以直接暴力就行了
看代码即可

a.参考代码

class Solution {
public:
    int n,m;
    vector<vector<int>> g;
    int numSpecial(vector<vector<int>>& mat) {
        g=mat;
        n=g.size();
        m=g[0].size();
        int ans=0;
        for(int i=0;i<n;i++)	//遍历整个矩阵
            for(int j=0;j<m;j++)
                if(g[i][j]&&check(i,j))ans++;	//对于每个都去暴力check一下
        return ans;
    }
    bool check(int x,int y){
        for(int i=0;i<n;i++){
            if(i==x)continue;
            if(g[i][y])return false;
        }
        for(int i=0;i<m;i++){
            if(i==y)continue;
            if(g[x][i])return false;
        }
        return true;
    }
};

b.统计不开心的朋友

b.题目

给你一份 n 位朋友的亲近程度列表,其中 n 总是 偶数 。

对每位朋友 i,preferences[i] 包含一份 按亲近程度从高到低排列 的朋友列表。换句话说,排在列表前面的朋友与 i 的亲近程度比排在列表后面的朋友更高。每个列表中的朋友均以 0 到 n-1 之间的整数表示。

所有的朋友被分成几对,配对情况以列表 pairs 给出,其中 pairs[i] = [xi, yi] 表示 xi 与 yi 配对,且 yi 与 xi 配对。

但是,这样的配对情况可能会是其中部分朋友感到不开心。在 x 与 y 配对且 u 与 v 配对的情况下,如果同时满足下述两个条件,x 就会不开心:

  • x 与 u 的亲近程度胜过 x 与 y,且
  • u 与 x 的亲近程度胜过 u 与 v

返回 不开心的朋友的数目 。

示例 1

输入:n = 4, preferences = [[1, 2, 3], [3, 2, 0], [3, 1, 0], [1, 2, 0]], pairs = [[0, 1], [2, 3]]
输出:2
解释:
朋友 1 不开心,因为:

  • 1 与 0 配对,但 1 与 3 的亲近程度比 1 与 0 高,且
  • 3 与 1 的亲近程度比 3 与 2 高。
    朋友 3 不开心,因为:
  • 3 与 2 配对,但 3 与 1 的亲近程度比 3 与 2 高,且
  • 1 与 3 的亲近程度比 1 与 0 高。
    朋友 0 和 2 都是开心的。

示例 2

输入:n = 2, preferences = [[1], [0]], pairs = [[1, 0]]
输出:0
解释:朋友 0 和 1 都开心。

示例 3

输入:n = 4, preferences = [[1, 3, 2], [2, 3, 0], [1, 3, 0], [0, 2, 1]], pairs = [[1, 3], [0, 2]]
输出:4

提示

  • 2 <= n <= 500
  • n 是偶数
  • preferences.length == n
  • preferences[i].length == n - 1
  • 0 <= preferences[i][j] <= n - 1
  • preferences[i] 不包含 i
  • preferences[i] 中的所有值都是独一无二的
  • pairs.length == n/2
  • pairs[i].length == 2
  • xi != yi
  • 0 <= xi, yi <= n - 1
  • 每位朋友都 恰好 被包含在一对中

b.分析

看上去还是暴力模拟(做题尽量无脑 只要能过) 但是和上面题目一样 都是得算一下时间复杂度

我们可以先预处理一下快乐的程度 方便我们直接比较
根据题目 预处理出来score[i][j]为i对j的亲近程度 越高就越亲近

对于n组人 一共有n^2种配对模式 (先不管对和对之间的顺序)
比如当前匹配到的(x,y)和(u,v) 那么就直接按照它的规则进行处理是否开心即可
score[x][u]<=score[x][y]score[u][x]<=score[u][v]都是不可行的

那么这时候就要把x标记为不开心的
这里要注意一下 当x为不开心的时候 其实还是有其他配对让他不开心
所以要类似visited数组差不多 不能重复

然后还有个需要注意的是 每个配对的pair是会产生多种排列组合的

也就是谁当x谁当u 等等等等
那么总共就有2×2×2共8种组合 (pair不能改)
(在代码中我是靠两个for重复排列来4×2)
(当然你也可以在check中把8个都做了)

总的时间复杂度是O(n^2)的

b.参考代码

int score[505][505];
class Solution {
public:
    int unhappyFriends(int n, vector<vector<int>>& preferences, vector<vector<int>>& pairs) {
        memset(score,0,sizeof score);
        for(int i=0;i<preferences.size();i++)	//预处理出分数
            for(int j=0;j<preferences[i].size();j++){
                score[i][preferences[i][j]]=666-j;
            }
        int ans=0;
        bool vis[505]={0};	//处理过的要记录下
        for(int i=0;i<pairs.size();i++)		//去对两两对看下是否不快乐
            for(int j=0;j<pairs.size();j++){
                if(i==j)continue;	//同对不处理
                int a=pairs[i][0],b=pairs[i][1],c=pairs[j][0],d=pairs[j][1];
                if(!vis[a]&&(check(a,b,c,d)||check(a,b,d,c))){
                    ans++;
                    vis[a]=true;
                }
                if(!vis[b]&&(check(b,a,c,d)||check(b,a,d,c))){
                    ans++;
                    vis[b]=true;
                }
            }
        return ans;
    }
    bool check(int x,int y,int u,int v){	//简单的check
        if(score[x][u]<=score[x][y])return false;
        if(score[u][x]<=score[u][v])return false;
        return true;
    }
};

c.连接所有点的最小费用

c.题目

给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi] 。

连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。

请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。

示例 1

输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
输出:20
解释:

我们可以按照上图所示连接所有点得到最小总费用,总费用为 20 。
注意到任意两个点之间只有唯一一条路径互相到达。

示例 2

输入:points = [[3,12],[-2,5],[-4,1]]
输出:18

示例 3

输入:points = [[0,0],[1,1],[1,0],[-1,1]]
输出:4

示例 4

输入:points = [[-1000000,-1000000],[1000000,1000000]]
输出:4000000

示例 5

输入:points = [[0,0]]
输出:0

提示

  • 1 <= points.length <= 1000
  • -10^6 <= xi, yi <= 10^6
  • 所有点 (xi, yi) 两两不同。

c.分析

最小生成树模板题
只是权值为曼哈顿距离
因此只需要把曼哈顿距离作为图预处理出来

用prim或者kruscal都可以

具体复杂度取决于用哪个算法跑最小生成树 我直接用的是返回总权值的模板函数

c.参考代码

long long G[1005][1005];
class Solution {
public:
    int n;
    int minCostConnectPoints(vector<vector<int>>& points) {
        memset(G,0,sizeof(G));
        for(int i=0;i<points.size()-1;i++)
            for(int j=i+1;j<points.size();j++){
                int x1=points[i][0],x2=points[j][0],y1=points[i][1],y2=points[j][1];
                G[i][j]=abs(x1-x2)+abs(y1-y2);
                G[j][i]=G[i][j];
            }
        n=points.size();
        return prim();
    }
    int prim()		//如果图不连通 返回无穷大 否则返回最小生成树的总权值
    {
        vector<long long> dis(n,0x3f3f3f3f);	//未加入MST的节点与MST的距离
        vector<bool> MST(n,false);	//每个节点是否在生成树当中
        int w=0;	//权值和
        for(int i=0;i<n;i++)
        {
            int node=-1;
            for(int j=0;j<n;j++)
                if(!MST[j]&&(node==-1||dis[node]>dis[j]))
                    node=j;		//找到离生成树最小距离的一个点
            if(i&&dis[node]==0x3f3f3f3f)return 0x3f3f3f3f;	//不连通
            if(i)w+=dis[node];	//更新总权值
            MST[node]=true;	//加入节点到最小生成树
            for(int j=0;j<n;j++)
                dis[j]=min(dis[j],G[node][j]);	//更新距离 对于新加入的节点 更新未加入节点的边
        }
        return w;
    }
};

d.检查字符串是否可以通过排序子字符串得到另一个字符串

d.题目

给你两个字符串 s 和 t ,请你通过若干次以下操作将字符串 s 转化成字符串 t :

  • 选择 s 中一个 非空 子字符串并将它包含的字符就地 升序 排序。

比方说,对下划线所示的子字符串进行操作可以由 “14234” 得到 “12344” 。

如果可以将字符串 s 变成 t ,返回 true 。否则,返回 false 。

一个 子字符串 定义为一个字符串中连续的若干字符。

示例 1

输入:s = “84532”, t = “34852”
输出:true
解释:你可以按以下操作将 s 转变为 t :
“84532” (从下标 2 到下标 3)-> “84352”
“84352” (从下标 0 到下标 2) -> “34852”

示例 2

输入:s = “34521”, t = “23415”
输出:true
解释:你可以按以下操作将 s 转变为 t :
“34521” -> “23451”
“23451” -> “23415”

示例 3

输入:s = “12345”, t = “12435”
输出:false

示例 4

输入:s = “1”, t = “2”
输出:false

提示

  • s.length == t.length
  • 1 <= s.length <= 105
  • s 和 t 都只包含数字字符,即 ‘0’ 到 ‘9’ 。

d.分析

这题的关键在于要思考出如何对于特定位置i
去思考s[i]是否可以变成t[i]
因为最终的结果必须每个位置都要归位

其实真不太好想 但是要大胆想

比如有这样的样例 s为5xxxx3 t为3xxxxx
如何把最后面的3移动到第一位呢
小小思考之后不难得出 3必须要在这个区间为最小的才能通过排序才能够通过某种排序来把这个3从后面移到前面 不然中间有比它小的数的话 那么必定排序后肯定是小的数会被排序到前面

那么排序完之后是35xxxx或者3x5xxx/3xx5xx/3xxx5x/3xxxx5
这几种之中 显然是35xxxx比较好 这个比较重点
当把某个数移动到正确的位置时候 我们希望后面区间的顺序不要被改变
因为这样子就能有更多的空间来调整位置 因为要知道排序是不可逆的 我们希望尽可能的少去做不需要的调整(等需要的时候再去调整)

那么这样的排序是否存在的呢? 答案是肯定的 我们可以通过不断地两两交换位置即可把最后那个移动到最前面

那么我们可以知道如何移动是最优的 但是暴力去实现这个模拟移动的话复杂度是O(n^2)的 我们要O(n)往后去找到目标数字 然后用O(n)去判断最后的目标数字是否是区间最小 最后用O(n)去把整个数组往后推 但是这样的O(n^2)是肯定不可行的

其实不难解决 我们可以把每个数字的位置用hash来保存起来 而且是只会用到最前面的 因此可以用队列来模拟这个过程 然后检查的话也是可以检查比当前数字小最前索引是否比当前索引要大

最终总的时间复杂度就能够优化为O(n)

d.参考代码

class Solution {
public:
    bool isTransformable(string s, string t) {
        int n = s.size();
        vector<queue<int>> pos(10);	//hash table对idx的映射
        for (int i = 0; i < n; ++i) {
            pos[s[i] - '0'].push(i);
        }
        for (int i = 0; i < n; ++i) {
            int digit = t[i] - '0';
            if (pos[digit].empty()) {
                return false;
            }
            for (int j = 0; j < digit; ++j) {
                if (!pos[j].empty() && pos[j].front() < pos[digit].front()) {
                    return false;
                }
            }
            pos[digit].pop();
        }
        return true;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值