【力扣周赛】第345场周赛

6430: 找出转圈游戏输家

题目描述

描述:n 个朋友在玩游戏。这些朋友坐成一个圈,按 顺时针方向 从 1 到 n 编号。从第 i 个朋友的位置开始顺时针移动 1 步会到达第 (i + 1) 个朋友的位置(1 <= i < n),而从第 n 个朋友的位置开始顺时针移动 1 步会回到第 1 个朋友的位置。

游戏规则如下:

第 1 个朋友接球。

接着,第 1 个朋友将球传给距离他顺时针方向 k 步的朋友。
然后,接球的朋友应该把球传给距离他顺时针方向 2 * k 步的朋友。
接着,接球的朋友应该把球传给距离他顺时针方向 3 * k 步的朋友,以此类推。
换句话说,在第 i 轮中持有球的那位朋友需要将球传递给距离他顺时针方向 i * k 步的朋友。

当某个朋友第 2 次接到球时,游戏结束。

在整场游戏中没有接到过球的朋友是 输家 。

给你参与游戏的朋友数量 n 和一个整数 k ,请按升序排列返回包含所有输家编号的数组 answer 作为答案。

输入:n = 5, k = 2
输出:[4,5]
解释:以下为游戏进行情况:
1)第 1 个朋友接球,第 1 个朋友将球传给距离他顺时针方向 2 步的玩家 —— 第 3 个朋友。
2)第 3 个朋友将球传给距离他顺时针方向 4 步的玩家 —— 第 2 个朋友。
3)第 2 个朋友将球传给距离他顺时针方向 6 步的玩家 —— 第 3 个朋友。
4)第 3 个朋友接到两次球,游戏结束。
输入:n = 4, k = 4
输出:[2,3,4]
解释:以下为游戏进行情况:
1)第 1 个朋友接球,第 1 个朋友将球传给距离他顺时针方向 4 步的玩家 —— 第 1 个朋友。
2)第 1 个朋友接到两次球,游戏结束。

解题思路

思路:最直观的想法是,约瑟夫环的变种。使用一个长度为n+1的visited数组存储元素是否接到过球(其中下标为0不使用),初始化均为false,表示初始均未接到过球,然后将下标为1设为true,表示第一个小朋友接到过球;使用i表示当前是第几个k,初始化为1,表示从1*k开始;使用cur表示当前小朋友编号,初始化为1,表示当前是第一个小朋友;使用nextcur表示下一个小朋友编号,初始化为(cur+i*k)%n==0?n:(cur+i*k)%n,注意此处小朋友编号从1开始而不是从0开始,故当其通过计算公式(cur+i*k)%n得到的结果是0时,其对应的应该为n(如果小朋友编号从0开始,那么对应的应该为0);题目描述的结束条件是当某个小朋友第2次接到球,对应的代码循环结束条件即为当下一个小朋友未接到球则继续循环,在每一轮循环中,首先置访问标志,然后更新cur、i以及nextcur,循环结束后遍历visited数组,将未接到过球的小朋友编号均加入到res数组中,并返回res即可。注意,本题特殊条件如下,一是小朋友个数n=1时没有输家,二是小朋友个数n等于步长k时除了第一个小朋友均是输家。

vector<int> circularGameLosers(int n, int k) {
        vector<int> res;
        if(n==1)  //没有输家
            return res;
        if(n==k)  //除了第一个均是输家
        {
            for(int i=2;i<=n;i++)
                res.push_back(i);
            return res;
        }
        vector<bool> visited(n+1,false); //访问数组 下标为0不使用
        visited[1]=true; //第一个访问
        int i=1; //记录是第几个k
        int cur=1; //记录当前下标
        int nextcur=(cur+i*k)%n==0?n:(cur+i*k)%n; //记录下一个下标  ==0 n eg:2 1
        while(!visited[nextcur])  //下一个待访问的下标未被访问则继续
        {
            visited[nextcur]=true;
            cur=nextcur;
            i++;
            nextcur=(cur+i*k)%n==0?n:(cur+i*k)%n;
        }
        for(int i=1;i<=n;i++)
        {
            if(!visited[i])
                res.push_back(i);
        }
        return res;
    }

6431: 相邻值的按位异或

题目描述

描述:下标从 0 开始、长度为 n 的数组 derived 是由同样长度为 n 的原始 二进制数组 original 通过计算相邻值的 按位异或(⊕)派生而来。

特别地,对于范围 [0, n - 1] 内的每个下标 i :

如果 i = n - 1 ,那么 derived[i] = original[i] ⊕ original[0]
否则 derived[i] = original[i] ⊕ original[i + 1]
给你一个数组 derived ,请判断是否存在一个能够派生得到 derived 的 有效原始二进制数组 original 。

如果存在满足要求的原始二进制数组,返回 true ;否则,返回 false 。

二进制数组是仅由 0 和 1 组成的数组。

输入:derived = [1,1,0]
输出:true
解释:能够派生得到 [1,1,0] 的有效原始二进制数组是 [0,1,0] :
derived[0] = original[0] ⊕ original[1] = 0 ⊕ 1 = 1 
derived[1] = original[1] ⊕ original[2] = 1 ⊕ 0 = 1
derived[2] = original[2] ⊕ original[0] = 0 ⊕ 0 = 0
输入:derived = [1,1]
输出:true
解释:能够派生得到 [1,1] 的有效原始二进制数组是 [0,1] :
derived[0] = original[0] ⊕ original[1] = 1
derived[1] = original[1] ⊕ original[0] = 1
输入:derived = [1,0]
输出:false
解释:不存在能够派生得到 [1,0] 的有效原始二进制数组。

解题思路

难度:中等。

思路:最直观的想法是,将原始二进制数组original各元素分类,即使用0和1来进行区分(此处假设同号使用0,不同号使用1),使用一个umap表示下标与符号对。遍历derived数组,如果当前元素为0,则表示原始的original[i]和original[i+1]同号,此时将umap[i+1]设置为与umap[i]相同,反之则表示原始的original[i]和original[i+1]异号,此时将umap[i+1]设置为与umap[i]相反。最后需要根据derived[n-1]判断umap[n-1]和umap[0]的关系:即如果derived[n-1]=0,则表示原始的original[n-1]和original[0]同号,即umap[n-1]应该等于umap[0],如果不满足则不存在;同理即如果derived[n-1]=1,则表示原始的original[n-1]和original[0]异号,即umap[n-1]应该不等于umap[0],如果不满足则不存在。(虽然可能效率不高,但是我想到了我就太厉害啦!)

bool doesValidArrayExist(vector<int>& derived) 
{
   int n=derived.size();
   // 力扣超时就去掉了这部分
   // if(n==1)  //同样长度 如果为1为false
   // {
   //     if(derived[0]==0)
   //         return true;
   //     else
   //         return false;
   // }
   // if(n==2)
   // {
   //     if(derived[0]==derived[1])
   //         return true;
   //     else
   //         return false;
   // }
   unordered_map<int,int> umap;  
   // <下标,对应分类(0/1代表符号不同)>  假设默认同号设为0 不同号设为1
   for(int i=0;i+1<n;i++) //遍历数组
   {
      if(derived[i]==0) //origin[i]和origin[i+1]同号
      {
         if(i==0) //特殊处理第一个
         {
             umap[i]=0;
             umap[i+1]=0;
         }
         else //只用处理下一个与当前的关系
         {
             umap[i+1]=umap[i];
         }
      }
      else
      {
         if(i==0) //特殊处理第一个
         {
             umap[i]=0;
             umap[i+1]=1;
         }
         else //只用处理下一个与当前的关系
         {
             umap[i+1]=(umap[i]==0)?1:0;
         }
        }
     }
     // 力扣超时就去掉了这部分
     // for(auto u:umap)
     //     cout<<u.first<<" "<<u.second<<endl;
     //判断最后一个
     if((derived[n-1]==0&&umap[n-1]==umap[0])||(derived[n-1]==1&&umap[n-1]!=umap[0]))
        return true;
     return false;
}

6433: 矩阵中移动的最大次数

题目描述

描述:给你一个下标从 0 开始、大小为 m x n 的矩阵 grid ,矩阵由若干 正 整数组成。

你可以从矩阵第一列中的 任一 单元格出发,按以下方式遍历 grid :

从单元格 (row, col) 可以移动到 (row - 1, col + 1)、(row, col + 1) 和 (row + 1, col + 1) 三个单元格中任一满足值 严格 大于当前单元格的单元格。
返回你在矩阵中能够 移动 的 最大 次数。

示例 1:
在这里插入图片描述

输入:grid = [[2,4,3,5],[5,4,9,3],[3,4,2,11],[10,9,13,15]]
输出:3
解释:可以从单元格 (0, 0) 开始并且按下面的路径移动:
- (0, 0) -> (0, 1).
- (0, 1) -> (1, 2).
- (1, 2) -> (2, 3).
可以证明这是能够移动的最大次数。

示例 2:

在这里插入图片描述

输入:grid = [[3,2,4],[2,1,9],[1,1,7]]
输出:0
解释:从第一列的任一单元格开始都无法移动。

解题思路

难度:中等。

思路:最直观的想法是,记忆化搜索(dfs+dp)。使用m表示矩阵行数,使用n表示矩阵列数,使用res表示结果,使用row表示当前行下标,使用col表示当前列下标,使用dp[i][j]表示从下标(i,j)出发可以移动的最大次数,使用dfs(row,col,m,n,grid,dp)表示从下标(row,col)出发可以移动的最大次数。由于是可以从第一列的任一单元格出发,故遍历行数,使用dfs获取从(i,0)出发可以移动的最大次数step,然后使用step更新res,最后返回res-1即可(因为最后一次也算作了一步)。dfs具体实现过程如下:首先判断下标row和col是否合法,如果不合法则表示无法移动,返回0即可;其次判断dp[row][col]是否已经被计算过,如果是则直接返回计算后的结果;然后使用step1、step2、step3分别收集从下标(row-1,col+1)、(row,col+1)、(row+1,col+1)出发移动的最大次数(注意因为此处需要严格比较数组元素,故需要在每次判断时对下标进行合法性判断,看起来冗余,但是非常必要),然后递推公式为dp[row][col] = max({step1,step2,step3})+1,注意,不是每次step1、step2和step3均有值,故需要初始化,最后返回dp[row][col]即可。

int dfs(int row,int col,int m,int n,vector<vector<int>>& grid,vector<vector<int>>& dp) //从(row,col)出发能移动的最大数
{
   if(row<0||col<0||row>=m||col>=n) //无法移动
     return 0;
   if(dp[row][col]!=-1)
     return dp[row][col];
   int step1=0,step2=0,step3=0;  //有的情况下step1或者step2或者step3没有
   if(grid[row-1][col+1]>grid[row][col])
       step1=dfs(row-1,col+1,m,n,grid,dp);
   if(grid[row][col+1]>grid[row][col])
       step2=dfs(row,col+1,m,n,grid,dp);
   if(grid[row+1][col+1]>grid[row][col])
       step3=dfs(row+1,col+1,m,n,grid,dp);
   dp[row][col]=max({step1,step2,step3})+1;  
   return dp[row][col];
}
int maxMoves(vector<vector<int>>& grid) 
{
    int m=grid.size();  //行
    int n=grid[0].size();  //列
    int res=0;
    vector<vector<int>> dp(m,vector<int>(n,-1));
    for(int i=0;i<m;i++)
    {
        int step=dfs(i,0,m,n,grid,dp);
        res=max(res,step);
    }
    return res-1; //最后一个算做了一次
}

6432: 统计完全连通分量的数量

题目描述

描述:给你一个整数 n 。现有一个包含 n 个顶点的 无向 图,顶点按从 0 到 n - 1 编号。给你一个二维整数数组 edges 其中 edges[i] = [ai, bi] 表示顶点 ai 和 bi 之间存在一条 无向 边。

返回图中 完全连通分量 的数量。

如果在子图中任意两个顶点之间都存在路径,并且子图中没有任何一个顶点与子图外部的顶点共享边,则称其为 连通分量 。

如果连通分量中每对节点之间都存在一条边,则称其为 完全连通分量 。

示例 1:

在这里插入图片描述

输入:n = 6, edges = [[0,1],[0,2],[1,2],[3,4]]
输出:3
解释:如上图所示,可以看到此图所有分量都是完全连通分量。

示例 2:

在这里插入图片描述

输入:n = 6, edges = [[0,1],[0,2],[1,2],[3,4],[3,5]]
输出:1
解释:包含节点 0、1 和 2 的分量是完全连通分量,因为每对节点之间都存在一条边。
包含节点 3 、4 和 5 的分量不是完全连通分量,因为节点 4 和 5 之间不存在边。
因此,在图中完全连接分量的数量是 1 。

解题思路

难度:中等。

统计完全连通分量的数量:最直观的想法是,使用一个vector嵌套set的数组存储每一个点以及其对应的邻接点集合(包含自己),首先遍历结点,将结点自身加入邻接点集合中,然后遍历边,将邻接点加入邻接点集合中;接着使用一个map存储一个邻接点集合与对应的个数对,再次遍历结点,并且使用umap[m[i]]++统计邻接集合的个数;最后使用res表示完全连通分量的个数,遍历umap,取出a,b表示其pair对,其中a为set邻接点集合,b为集合个数,如果b等于a的大小,则表示是一个完全连通分量,此时将res加一,最后返回res即可。

int countCompleteComponents(int n, vector<vector<int>>& edges) 
{
	//存储每一个点以及其对应的邻接点(包含自己)
    vector<set<int>> m(n,set<int>());  
    for(int i=0;i<n;i++)
       m[i].insert(i);  //插入自己
    for(auto e:edges)
    {
       int x=e[0];
       int y=e[1];
       m[x].insert(y);  //插入邻接点
       m[y].insert(x);  //插入邻接点
    }
    //map存set unordered_map好像无法set
    map<set<int>,int> umap;   //存储一个邻接集合对应的点个数
    for(int i=0;i<n;i++)
        umap[m[i]]++;  //统计邻接集合的个数
    int res=0; //统计完全连通分量个数
    for(auto u:umap)
    {
        auto a=u.first;
        int b=u.second;
        if(a.size()==b)
          res++;
    }
    return res;
}

最后:力扣超时不一定是算法的问题,有时候有可能只是操作!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值