LeetCode 第27场夜喵双周赛 题解

文章目录

  • a.通过翻转子数组使两个数组相等
    • a.题目
    • a.分析
    • a.参考代码
  • b.检查一个字符串是否包含所有长度为 K 的二进制子串
    • b.题目
    • b.分析
    • b.参考代码
  • c.课程安排 IV
    • c.题目
    • c.分析
    • c.参考代码
      • BFS判断可达
      • 最短路
  • d.摘樱桃 II
    • d.题目
    • d.分析
    • d.参考代码

a.通过翻转子数组使两个数组相等

a.题目

给你两个长度相同的整数数组 targetarr
每一步中,你可以选择 arr 的任意 非空子数组 并将它翻转。你可以执行此过程任意次。
如果你能让 arr 变得与 target 相同,返回 True;否则,返回 False 。

示例 1

输入:target = [1,2,3,4], arr = [2,4,1,3]
输出:true
解释:你可以按照如下步骤使 arr 变成 target:
1- 翻转子数组 [2,4,1] ,arr 变成 [1,4,2,3]
2- 翻转子数组 [4,2] ,arr 变成 [1,2,4,3]
3- 翻转子数组 [4,3] ,arr 变成 [1,2,3,4]
上述方法并不是唯一的,还存在多种将 arr 变成 target 的方法。

示例 2

输入:target = [7], arr = [7]
输出:true
解释:arr 不需要做任何翻转已经与 target 相等。

示例 3

输入:target = [1,12], arr = [12,1]
输出:true

示例 4

输入:target = [3,7,9], arr = [3,7,11]
输出:false
解释:arr 没有数字 9 ,所以无论如何也无法变成 target 。

示例 5

输入:target = [1,1,1,1,1], arr = [1,1,1,1,1]
输出:true

提示

  • target.length == arr.length
  • 1 <= target.length <= 1000
  • 1 <= target[i] <= 1000
  • 1 <= arr[i] <= 1000

a.分析

拿到第一题其实我有点懵的 因为假如用模拟翻转的话这题将会变得相当复杂
然后想了下这里是有这样一个结论的:

  • 只要两者元素相同 那么总能通过翻转来变成一样

然后我猜出来之后当然要简单证明下 不然就会吃一个WA不值得

  • 对于target[0] 我们在arr中找到相同的元素arr[i] 那么把(0,i)区间翻转 就能把他放到第0位上去
  • 对于target[1]同样也能这样操作 而且由于target[0]此时位置已经翻转好了 所以就无所谓了

总的复杂度是遍历两个的复杂度 O(n)
其实贪快的话直接构造函数塞进multiset然后判断下是否相等就好了

a.参考代码

class Solution {
public:
    bool canBeEqual(vector<int>& target, vector<int>& arr) {
        int n=target.size();
        map<int,int> m1,m2; //我这里没用multiset 用的计数
        for(int i=0;i<n;i++)
        {
            m1[target[i]]++;
            m2[arr[i]]++;
        }
        for(auto i:m1)
            if(m2[i.first]!=i.second)return false;
        return true;
    }
};

b.检查一个字符串是否包含所有长度为 K 的二进制子串

b.题目

给你一个二进制字符串 s 和一个整数 k

如果所有长度为 k 的二进制字符串都是 s 的子串,请返回 True ,否则请返回 False 。

示例 1

输入:s = “00110110”, k = 2
输出:true
解释:长度为 2 的二进制串包括 “00”,“01”,“10” 和 “11”。它们分别是 s 中下标为 0,1,3,2 开始的长度为 2 的子串。

示例 2

输入:s = “00110”, k = 2
输出:true

示例 3

输入:s = “0110”, k = 1
输出:true
解释:长度为 1 的二进制串包括 “0” 和 “1”,显然它们都是 s 的子串。

示例 4

输入:s = “0110”, k = 2
输出:false
解释:长度为 2 的二进制串 “00” 没有出现在 s 中。

示例 5

输入:s = “0000000001011100”, k = 4
输出:false

提示

  • 1 <= s.length <= 5 * 10^5
  • s 中只含 0 和 1 。
  • 1 <= k <= 20

b.分析

这里我们先分析下题目
题目需要我们判断子串中是否有全部k以内的数字 那么你肯定就得把子串都弄出来才能判断
因此 上来先把s中全部长度为k的子串存起来 用substr就好了 一次复杂度是O(k)的
那么用什么存起来呢 这里我们想要知道他是否存在 那么直接就用set保存就行了
然后就是判断部分了 我们可以选择从0开始的全部字符串形式的数字都弄出来 然后再去看一下它在不在里面 如果有不在的话就直接返回false就行了

我再考虑了一下(赛后) 其实完全没有必要把字符串造出来 因为set的大小最大就是2的k次方 而且如果想要全部数字匹配的话 那么set只能是最大那个大小 所以直接判断下set的大小是不是为2的k次方就行了

b.参考代码

class Solution {
public:
    bool hasAllCodes(string s, int k) {
        if(s.size()<k)return false;	//题目没说k必须比s长度小
        unordered_set<string> Set;
        for(int i=0;i<s.size()-k+1;i++)Set.insert(s.substr(i,k));
        return Set.size()==(1<<k);
    }
};

c.课程安排 IV

c.题目

你总共需要上 n 门课,课程编号依次为 0 到 n-1 。

有的课会有直接的先修课程,比如如果想上课程 0 ,你必须先上课程 1 ,那么会以 [1,0] 数对的形式给出先修课程数对。

给你课程总数 n 和一个直接先修课程数对列表 prerequisite 和一个查询对列表 `queries 。

对于每个查询对 queries[i] ,请判断 queries[i][0] 是否是 queries[i][1] 的先修课程。

请返回一个布尔值列表,列表中每个元素依次分别对应 queries 每个查询对的判断结果。

注意:如果课程 a 是课程 b 的先修课程且课程 b 是课程 c 的先修课程,那么课程 a 也是课程 c 的先修课程。

示例 1

输入:n = 2, prerequisites = [[1,0]], queries = [[0,1],[1,0]]
输出:[false,true]
解释:课程 0 不是课程 1 的先修课程,但课程 1 是课程 0 的先修课程。

示例 2

输入:n = 2, prerequisites = [], queries = [[1,0],[0,1]]
输出:[false,false]
解释:没有先修课程对,所以每门课程之间是独立的。

示例 3


输入:n = 3, prerequisites = [[1,2],[1,0],[2,0]], queries = [[1,0],[1,2]]
输出:[true,true]

示例 4

输入:n = 3, prerequisites = [[1,0],[2,0]], queries = [[0,1],[2,0]]
输出:[false,true]

示例 5

输入:n = 5, prerequisites = [[0,1],[1,2],[2,3],[3,4]], queries = [[0,4],[4,0],[1,3],[3,0]]
输出:[true,false,true,false]

提示

  • 2 <= n <= 100
  • 0 <= prerequisite.length <= (n * (n - 1) / 2)
  • 0 <= prerequisite[i][0], prerequisite[i][1] < n
  • prerequisite[i][0] != prerequisite[i][1]
  • 先修课程图中没有环。
  • 先修课程图中没有重复的边。
  • 1 <= queries.length <= 10^4
  • queries[i][0] != queries[i][1]

c.分析

我一开始看到题目想的是拓扑排序 就是判断一下询问前者是不是在拓扑序上的前者 但是后来发现这题图不一定是连通的 也就是会分割成多个图的 那么这种情况就需要对每个图分别进行拓扑序 然后还要判断是不是在同一个图中 这样就有点麻烦

我再看了下复杂度 最多有100个节点 也就是说最多有1e4个两两关系
那么假如我对这1e4个两两关系进行一次连通判断 那么跑完整个图也只需要1e6而已 这个复杂度完全可以接受

所以总的复杂度是O(n^3) 与询问的size无关 询问最多就是边的数量n^2
这题有点特殊 就特殊在询问的次数受边数限制 可以对于每条边都去跑一次图 不然的话很显然是要预处理关系的

最短路预处理
赛后想了下 直接用floyd跑下多源最短路就行了
复杂度还是O(n^3)

c.参考代码

BFS判断可达

class Solution {
public:
    vector<unordered_set<int>> G; //这里如果直接用二维数组存图可能在bfs时候复杂度上升
    vector<bool> checkIfPrerequisite(int n, vector<vector<int>>& p, vector<vector<int>>& Q) {
        G.resize(n);
        for(auto i:p)	//邻接矩阵存图
            G[i[0]].insert(i[1]);
        vector<bool> ans;
        for(auto i:Q)
            if(bfs(i[0],i[1]))ans.push_back(true);
            else ans.push_back(false);
        return ans;
    }
    bool bfs(int a,int b)	//判断是否可达
    {
        queue<int> q;
        q.push(a);
        int vis[105]={0};
        vis[a]=true;
        while(q.size())
        {
            int x=q.front();
            q.pop();
            for(auto i:G[x])
                if(i==b)return true;
                else if(!vis[i]){
                    vis[i]=true;
                    q.push(i);
                }
        }
        return false;
    }
};`

最短路

class Solution {
public:
    bool d[105][105];
    vector<bool> ans;
    vector<bool> checkIfPrerequisite(int n, vector<vector<int>>& prerequisites, vector<vector<int>>& queries) {
        int i,j,k;
        memset(d,0,sizeof(d));
        for(i=0;i<n;i++)d[i][i]=1;
        for(auto e:prerequisites)d[e[0]][e[1]]=1;
        for(k=0;k<n;k++)for(i=0;i<n;i++)for(j=0;j<n;j++)d[i][j]=d[i][j]||d[i][k]&&d[k][j];
        ans.clear();
        for(auto q:queries)ans.push_back(d[q[0]][q[1]]);
        return ans;
    }
};

d.摘樱桃 II

d.题目

给你一个 rows x cols 的矩阵 grid 来表示一块樱桃地。 grid 中每个格子的数字表示你能获得的樱桃数目。
你有两个机器人帮你收集樱桃,机器人 1 从左上角格子 (0,0) 出发,机器人 2 从右上角格子 (0, cols-1) 出发。
请你按照如下规则,返回两个机器人能收集的最多樱桃数目:

  • 从格子 (i,j) 出发,机器人可以移动到格子 (i+1, j-1),(i+1, j) 或者 (i+1, j+1) 。
  • 当一个机器人经过某个格子时,它会把该格子内所有的樱桃都摘走,然后这个位置会变成空格子,即没有樱桃的格子。
  • 当两个机器人同时到达同一个格子时,它们中只有一个可以摘到樱桃。
  • 两个机器人在任意时刻都不能移动到 grid 外面。
  • 两个机器人最后都要到达 grid 最底下一行。

示例 1

输入:grid = [[3,1,1],[2,5,1],[1,5,5],[2,1,1]]
输出:24
解释:机器人 1 和机器人 2 的路径在上图中分别用绿色和蓝色表示。
机器人 1 摘的樱桃数目为 (3 + 2 + 5 + 2) = 12 。
机器人 2 摘的樱桃数目为 (1 + 5 + 5 + 1) = 12 。
樱桃总数为: 12 + 12 = 24 。

示例 2

输入:grid = [[1,0,0,0,0,0,1],[2,0,0,0,0,3,0],[2,0,9,0,0,0,0],[0,3,0,5,4,0,0],[1,0,2,3,0,0,6]]
输出:28
解释:机器人 1 和机器人 2 的路径在上图中分别用绿色和蓝色表示。
机器人 1 摘的樱桃数目为 (1 + 9 + 5 + 2) = 17 。
机器人 2 摘的樱桃数目为 (1 + 3 + 4 + 3) = 11 。
樱桃总数为: 17 + 11 = 28 。

示例 3

输入:grid = [[1,0,0,3],[0,0,0,3],[0,0,3,3],[9,0,3,3]]
输出:22

示例 4

输入:grid = [[1,1],[1,1]]
输出:4

提示

  • rows == grid.length
  • cols == grid[i].length
  • 2 <= rows, cols <= 70
  • 0 <= grid[i][j] <= 100

d.分析

如果只有一个机器人的话 那么这个问题就是经典的三角数字dp问题
我们来看下如果只有一个是怎样的
定义状态dp[i][j] 为选到第i行第j列的时候 后面总共的最大值
那么我们站在第i行第j列的时候 我们就只需要考虑三个的大小问题而已:
dp[i+1][j-1] dp[i+1][j] dp[i+1][j+1]
那么直到最后一行就不用考虑了

那么一个机器人很明显 那么两个呢
我的第一个想法是能否先跑一次一个机器人 然后把途中的樱桃数量都变为0 然后再去跑第二个 但是后来发现这个是不可行的 因为两次都是按照最优的去选择 并不会出现相遇时候去选择次优的
(主要是因为两个机器人能选取的范围不同 假如第一个机器人实际上要选的是较左的次优 那么右边机器人第二次跑显然就选不到了)

既然两个机器人的状态互相有影响 那么我们直接多定义一个机器人的状态就好了

定义dp[i][j1][j2]为在第i行的时候两个机器人分别在j1和j2列
那么移动的时候就有3 * 3种可能了 在9种可能里面选最大的就行了

然后呢 用记忆化搜索将会使移动更加直观

总的复杂度是状态的填充O(n^3)

d.参考代码

int dy[]={-1,0,1};	//移动的方向
class Solution {
public:
    vector<vector<int>> G;
    int mem[75][75][75];	//记忆数组
    int n,m;
    int cherryPickup(vector<vector<int>>& grid) {
        G=grid;
        n=G.size();
        m=G[0].size();
        memset(mem,-1,sizeof(mem));
        return ms(0,0,m-1);	//搜索初始状态就是答案
    }
    int ms(int i,int j1,int j2)
    {
        if(j1<0||j2<0||j1>=m||j2>=m)return 0;
        if(i==n-1){	//最后一行
            if(j1!=j2)return G[i][j1]+G[i][j2];
            return G[i][j1];
        }
        int &now=mem[i][j1][j2];	//简化书写
        if(now!=-1)return now;
        if(j1!=j2)now=G[i][j1]+G[i][j2];
        else now=G[i][j1];
        int Max=0;
        for(int ii=0;ii<3;ii++)		//多种移动选最优的
            for(int jj=0;jj<3;jj++)
                Max=max(Max,ms(i+1,j1+dy[ii],j2+dy[jj]));
        now+=Max;
        return now;
    }
};

你可能感兴趣的:(leetcode,周赛,数据结构,算法,动态规划,leetcode)