LeetCode第184场周赛(Weekly Contest 184)解题报告

这周的周赛,出乎意料的容易,特别是前三题,几乎都是easy题目了(特别第三题,如果会 python,超简单,但是我不会,所以还是手动写了匹配)。手速场,手速场,然鹅我脑子转得慢,而且敲代码还慢,可怜。

第一题:字符串查找。

第二题:模拟。

第三题:字符串替换。

第四题:DP + 状压。

详细题解如下。


1.数组中的字符串匹配(String Matching in An Array)

           AC代码(C++)

2. 查询带键的排列(Queries on A Permutation with Key)

           AC代码(C++)

3.HTML 实体解析器(Html Entity Parser)

           AC代码(C++)

4.给 N x 3 网格图涂色的方案数(Number of Ways to Paint N X 3 Grid)

           AC代码(C++)


LeetCode第184场周赛地址:

https://leetcode-cn.com/contest/weekly-contest-184


1.数组中的字符串匹配(String Matching in An Array)

题目链接

https://leetcode-cn.com/problems/string-matching-in-an-array/

题意

给你一个字符串数组 words ,数组中的每个字符串都可以看作是一个单词。请你按 任意 顺序返回 words 中是其他单词的子字符串的所有单词。

如果你可以删除 words[j] 最左侧和/或最右侧的若干字符得到 word[i] ,那么字符串 words[i] 就是 words[j] 的一个子字符串。

示例 1:

输入:words = ["mass","as","hero","superhero"]
输出:["as","hero"]
解释:"as" 是 "mass" 的子字符串,"hero" 是 "superhero" 的子字符串。
["hero","as"] 也是有效的答案。

提示:

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 30
  • words[i] 仅包含小写英文字母。
  • 题目数据 保证 每个 words[i] 都是独一无二的。

 

解题思路

根据题意,我们就是要判断每一个字符串,是不是其他字符串的子串。

那么一开始的想法就是,对每一个字符串,都枚举其他字符串,那么就是 O(N ^ 2),同时,得到两个字符串后,要看,其中一个字符串是不是另一个的字符串,字符串匹配,那么简单的就是 O(len * len),所以,暴力枚举的时间复杂度是 O(N ^ 2 * len * len),那么根据数据范围,不会超时。

其中,字符串匹配的,可以用 string 的函数 find,str.find(s),即在 str 中,是否有 s。

AC代码(C++)

class Solution {
public:
    vector<string> stringMatching(vector<string>& words) {
        vector<string> ans;
        int n = words.size();
        for(int i = 0;i < n; ++i)
        {
            bool flag = false;
            for(int j = 0;j < n; ++j)
            {
                // 判断 words[i] 是不是其中一个的子串
                if(i == j) continue;
                if(words[i].size() > words[j].size()) continue;
                
                if(words[j].find(words[i]) != -1)
                {
                    flag = true;
                    break;
                } 
            }
            if(flag) ans.push_back(words[i]);
        }
        return ans;
    }
};

 


2. 查询带键的排列(Queries on A Permutation with Key)

题目链接

https://leetcode-cn.com/problems/queries-on-a-permutation-with-key/

题意

给你一个待查数组 queries ,数组中的元素为 1 到 m 之间的正整数。 请你根据以下规则处理所有待查项 queries[i](从 i=0 到 i=queries.length-1):

  • 一开始,排列 P=[1,2,3,...,m]。
  • 对于当前的 i ,请你找出待查项 queries[i] 在排列 P 中的位置(下标从 0 开始),然后将其从原位置移动到排列 P 的起始位置(即下标为 0 处)。注意, queries[i] 在 P 中的位置就是 queries[i] 的查询结果。

请你以数组形式返回待查数组  queries 的查询结果。

示例 1:

输入:queries = [3,1,2,1], m = 5
输出:[2,1,2,1] 
解释:待查数组 queries 处理如下:
对于 i=0: queries[i]=3, P=[1,2,3,4,5], 3 在 P 中的位置是 2,接着我们把 3 移动到 P 的起始位置,得到 P=[3,1,2,4,5] 。
对于 i=1: queries[i]=1, P=[3,1,2,4,5], 1 在 P 中的位置是 1,接着我们把 1 移动到 P 的起始位置,得到 P=[1,3,2,4,5] 。 
对于 i=2: queries[i]=2, P=[1,3,2,4,5], 2 在 P 中的位置是 2,接着我们把 2 移动到 P 的起始位置,得到 P=[2,1,3,4,5] 。
对于 i=3: queries[i]=1, P=[2,1,3,4,5], 1 在 P 中的位置是 1,接着我们把 1 移动到 P 的起始位置,得到 P=[1,2,3,4,5] 。 
因此,返回的结果数组为 [2,1,2,1] 。 

示例 2:

输入:queries = [4,1,2,2], m = 4
输出:[3,1,2,0]

提示:

  • 1 <= m <= 10^3
  • 1 <= queries.length <= m
  • 1 <= queries[i] <= m

解题思路

根据题意,其实就是一个模拟,也就是我们不断的找到 p 中某个数的位置后,然后对 p 再进行处理。

那么如果暴力枚举,也就是枚举 q 中的每一个数,然后去找到这个数在 p 中的位置后。再将 p 处理(也就是在对应位置以及前面位置,相当于数后移动一位,然后第一位数变成 q 中的这个数)。

也就是,第一层循环 q,然后循环 p 找到 位置后,再循环 p 进行处理。

那么时间复杂度就是 O(q * p) = O(N ^ 2),根据数据范围,不会超时。

AC代码(C++)

class Solution {
public:
    vector<int> processQueries(vector<int>& q, int m) {
        int n = q.size();
        vector<int> ans(n, 0);

        vector<int> p(m, 0);
        for(int i = 0;i < m; ++i) p[i] = i + 1;  // 弄到 p
        
        for(int i = 0;i < n; ++i)
        {
            for(int j = 0;j < m; ++j)  // 先找到 位置,那么这个就是 ans 的答案。
                if(q[i] == p[j])
                {
                    ans[i] = j;
                    break;
                }
            // 然后对 p 处理,此时位置是 ans[i],数都往后移动一位
            for(int j = ans[i];j >= 1; --j)
            {
                p[j] = p[j - 1];
            }
            p[0] = q[i];   // 最后第一个数,是此时要找的 q[i] 这个数。
        }
        return ans;
    }
};

3.HTML 实体解析器(Html Entity Parser)

题目链接

https://leetcode-cn.com/problems/html-entity-parser/

题意

「HTML 实体解析器」 是一种特殊的解析器,它将 HTML 代码作为输入,并用字符本身替换掉所有这些特殊的字符实体。

HTML 里这些特殊字符和它们对应的字符实体包括:

  • 双引号:字符实体为 &quot; ,对应的字符是 " 。
  • 单引号:字符实体为 &apos; ,对应的字符是 ' 。
  • 与符号:字符实体为 &amp; ,对应对的字符是 & 。
  • 大于号:字符实体为 &gt; ,对应的字符是 > 。
  • 小于号:字符实体为 &lt; ,对应的字符是 < 。
  • 斜线号:字符实体为 &frasl; ,对应的字符是 / 。

给你输入字符串 text ,请你实现一个 HTML 实体解析器,返回解析器解析后的结果。

示例 1:

输入:text = "&amp; is an HTML entity but &ambassador; is not."
输出:"& is an HTML entity but &ambassador; is not."
解释:解析器把字符实体 &amp; 用 & 替换

示例 2:

输入:text = "and I quote: &quot;...&quot;"
输出:"and I quote: \"...\""

提示:

  • 1 <= text.length <= 10^5
  • 字符串可能包含 256 个ASCII 字符中的任意字符。

解题分析

其实是一道,字符串替换问题。即在源字符串中,如果存在对应的子串,那么就替换成其他的。

所以这道题,我们可以遍历字符串,如果发现是 & 开头的,那就一直找到 ; 结束的这部分子串,如果这个字符串存在替换列表中,就替换掉。

关于找到子串后,判断在不在,我们可以用哈希表 map 来存储,快速判断这个子串是否存在,并且为了快速得到 替换结果,我们可以 map 的value 是下标,这个下标对应的字符数组,就是这个子串要替换的字符。

而在字符串查找的时候,要特别小心,如果有一个字符串是,刚好开头 &,结尾才 ;那么此时时间复杂度就变为了 O(N ^ 2),就会超时。

因此我们要优化一下,我们发现,要替换的子串,最大长度为 7,所以我们找到 &,之后,最后查找长度为 7,如果此时还没到 ;那就说明,这个子串不是可以替换的。这样子,时间复杂度是 O(N * 7),就不会超时。

AC代码(C++)

class Solution {
public:
    unordered_map<string, int> mp;
    char c[6] = {'\"', '\'', '&', '>', '<', '/'};  // 替换列表
    string entityParser(string text) {
        string res = "";
        // 初始化
        mp.clear();
        mp["&quot;"] = 1;
        mp["&apos;"] = 2;
        mp["&amp;"] = 3;
        mp["&gt;"] = 4;
        mp["&lt;"] = 5;
        mp["&frasl;"] = 6;
        
        int n = text.size();
        
        for(int i = 0;i < n; ++i)
        {
            if(text[i] == '&')  //发现是 &,那么就去找到 ;
            {
                int j = i + 1;
                while(j < n && j - i + 1 <= 7 && text[j] != ';') ++j;  // 因为最大长度是 7
                
                string tep = text.substr(i, j - i + 1);  // 把这部分可能替换的子串取下来,用于判断是不是我们要替换的
                if(mp[tep])
                {
                    res += c[mp[tep] - 1];
                    i = j;
                }
                else
                    res += text[i];   // 不是要替换的,说明从 & 的这部分,是要原来不变的。
                
            }
            else res += text[i];
        }
        return res;
    }
};

 


4.给 N x 3 网格图涂色的方案数(Number of Ways to Paint N X 3 Grid)

题目链接

https://leetcode-cn.com/problems/number-of-ways-to-paint-n-x-3-grid/

题意

你有一个 n x 3 的网格图 grid ,你需要用 红,黄,绿 三种颜色之一给每一个格子上色,且确保相邻格子颜色不同(也就是有相同水平边或者垂直边的格子颜色不同)。

给你网格图的行数 n 。

请你返回给 grid 涂色的方案数。由于答案可能会非常大,请你返回答案对 10^9 + 7 取余的结果。

示例 1:

示例有图,具体看链接

输入:n = 1
输出:12
解释:总共有 12 种可行的方法:

示例 2:

输入:n = 5000
输出:30228214

提示:

  • n == grid.length
  • grid[i].length == 3
  • 1 <= n <= 5000

解题分析

这道题看起来就像是 DP,也就是,当前行进行涂色,会受到上一行涂色的影响。

1)设状态

我们假设 dp[ i ][ j ] 表示,涂 第 i 行,j 状态的 方案数。

那么关于 j,我们发现,一行有三个状态,我们把涂色变为数字 0 1 2 表示三种颜色,那么一行的三个状态就是 0 1 2的组合,也就是可以看成一个  3 位 的 三进制数,那么变为 十进制表示,就是 j。(因此,j 最大数字位 3^3 - 1 = 26)

因此,我们对每一行枚举 j,此时判断在上一行 dp[ i - 1][ k ] 状态下,是不是可以成功,如果可以成功,说明此时 dp[ i ][ j ] 的数量就在 dp[ i - 1][ k ] 方案数上累加(因为可以是其他 k 状态转移来的,所以是累加)。

所以总共三层循环,i,j,k,时间复杂度是 O(N * 30 * 30) 不会超时。

2)状态转移

也就是对于每一行 i,判断其所有可能状态 j,看从上一行的 所有 k 状态是否可以进行涂色。(因为某一行的涂色,只和本行和上一行有关而已)

那么我们要想能转移,就要判断 j 状态是不是满足条件(和上一行不冲突,同时自己这一行的状态不冲突)

  • 首先是自己这一行状态不冲突,也就是从 j 十进制,变回三进制数中,相邻的不能相等(我们可以事先计算所有同一行不冲突的 j 状态,然后用 vis 记录)
  • 与上一行的状态不冲突,那么也就是将 j,与 k ,计算相同位上的 三进制数,不相同。

3)初始化

一开始,所有的 dp = 0,(方案数为 0 )

然后,计算所有第一行 dp[ 1 ][ all j ] 的值,也就是,如果 j 状态成立,那么对应的方案数 = 1,不成立方案数 = 0。

4)结果

最后输出的结果,应该是 dp[ n ][ all ] 的累加,因为涂到最后一行,任何状态都是可以的,所以是累加。

AC代码(C++)

const int MAXN = 5005;
const int MOD = 1e9 + 7;
class Solution {
public:
    int dp[MAXN][27];
    int vis[27];
    
    bool check(int num)  // 同一行状态冲突不冲突
    {
        int last = num % 3;
        num /= 3;
        for(int i = 0;i < 2; ++i)
        {
            if(num % 3 == last) return false;
            last = num % 3;
            num /= 3;
        }
        return true;
    }
    
    bool check(int a, int b)  // 两行的状态不能冲突
    {
        for(int i = 0;i < 3; ++i)
        {
            if(a % 3 == b % 3) return false;
            a /= 3;
            b /= 3;
        }
        return true;
    }
    
    
    int numOfWays(int n) {
        
        memset(vis, 0, sizeof(vis));
        memset(dp, 0, sizeof(dp));
        
        for(int i = 0;i < 27; ++i)  // 先记录下,所有同一行不冲突的状态
        {
            if(check(i)) vis[i] = 1;
        }
        
        for(int i = 0;i < 27; ++i)   // 初始化
        {
            if(vis[i]) dp[1][i] = 1;
        }
        
        for(int i = 2;i <= n; ++i)
        {
            for(int j = 0;j < 27; ++j)  // 当前状态 j
            {
                if(vis[j] == 0) continue;  // 如果本身自己状态就冲突,那就下一个状态
                for(int k = 0;k < 27; ++k)  // 上一行的状态 k
                {
                    if(vis[k] == 0) continue;  // 如果自己行的状态就不成立,就下一个状态
                    
                    if(!check(j, k)) continue;  // 两行的状态冲突,那就考虑下一个状态
                    
                    dp[i][j] = (dp[i][j] + dp[i - 1][k]) % MOD;
                    
                }
            }
        }

        int ans = 0;  // 结果
        for(int i = 0;i < 27; ++i)  // 最后一行的,所有状态情况的涂色方案累加
        {
            if(vis[i]) ans = (ans + dp[n][i]) % MOD;
        }
        
        return ans;
    }
};

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值