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

庆祝我的第一次AK,哟哟哟,以为可以挤进前100,但是大佬还是多呀。主要是第二题的map处理之后,不知道要怎么排序,所以卡了挺久的,可怜。

周赛排名:145/ 1648

第一题:模拟。

第二题:前缀 + 异或。

第三题:BFS + map + 排序。

第四题:动态规划 DP。

详细题解如下。


1. 解码字母到整数映射(Decrypt String From Alphabet to Integer Mapping)

           AC代码(C++)

2. 子数组异或查询(Xor Queries of A Subarray)

           AC代码(C++)

3.获取你好友已观看的视频(Get Watched Videos by Your Friends)

           AC代码(方法一   BFS + map + 对map枚举排序 C++)

           AC代码(方法二   BFS+map+重写sort排序  C++)

4.让字符串成为回文串的最少插入次数(Minimum Insertion Steps to Make A String Palindrome)

           AC代码(Java)


LeetCode第170场周赛地址:

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


1. 解码字母到整数映射(Decrypt String From Alphabet to Integer Mapping)

题目链接

https://leetcode-cn.com/problems/decrypt-string-from-alphabet-to-integer-mapping/

题意

给你一个字符串 s,它由数字('0' - '9')和 '#' 组成。我们希望按下述规则将 s 映射为一些小写英文字符:

  • 字符('a' - 'i')分别用('1' - '9')表示。
  • 字符('j' - 'z')分别用('10#' - '26#')表示。 

返回映射之后形成的新字符串。

题目数据保证映射始终唯一。

示例 1:

输入:s = "10#11#12"
输出:"jkab"
解释:"j" -> "10#" , "k" -> "11#" , "a" -> "1" , "b" -> "2".

示例 2:

输入:s = "1326#"
输出:"acz"

示例 3:

输入:s = "25#"
输出:"y"

示例 4:

输入:s = "12345678910#11#12#13#14#15#16#17#18#19#20#21#22#23#24#25#26#"
输出:"abcdefghijklmnopqrstuvwxyz"

提示:

  • 1 <= s.length <= 1000
  • s[i] 只包含数字('0'-'9')和 '#' 字符。
  • s 是映射始终存在的有效字符串。

解题思路

理解题目的意思,其实就是将 a - z 分别映射成了 1 - 26,但是由于有一位数和两位数,为了一一映射,将两位数的后面加上 #,这样子就互不相同。

现在是给出,映射之后字符串,让我们得出原来的包含 a - z 的字符串。

如果我们从前往后扫过去,判断会比较麻烦,比如我们遇到 1,那么要看 1 的后两位是不是要为 #,如果是的话,那就是 1 和 后面的那个数,变成两位数,再映射回 字母。

但是如果我们从后往前扫,只要一遇到 #,那么我们就知道 # 的前两位数,就是一起映射的,如果直接遇到的是 数字,那就就是一位数的映射。

(从前往后 也是可以做的,但是 从后往前 更加方便判断)

AC代码(C++)

class Solution {
public:
    string freqAlphabets(string s) {
        
        string ans = "";
        int temp = 0;
        for(int i = s.size()-1;i >= 0; --i)
        {
            // 如果遇到 #,说明是两位数
            if(s[i] == '#')
            {
                temp = (s[i-2] - '0')*10 + (s[i-1] - '0');
                i -= 2;
            }
            else // 否则直接是 一位数
            {
                temp = s[i] - '0';
            }
            // 得到了 1- 26,对应映射回 a-z,注意,因为是从后往前,所以新加进去的是在左边。
            ans = (char)((temp - 1) + 'a') + ans;
        }
        return ans;
    }
};

 


2. 子数组异或查询(Xor Queries of A Subarray)

题目链接

https://leetcode-cn.com/problems/xor-queries-of-a-subarray/

题意

有一个正整数数组 arr,现给你一个对应的查询数组 queries,其中 queries[i] = [Li, Ri]。

对于每个查询 i,请你计算从 Li 到 Ri 的 XOR 值(即 arr[Li] xor arr[Li+1] xor ... xor arr[Ri])作为本次查询的结果。

并返回一个包含给定查询 queries 所有结果的数组。

示例 1:

输入:arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]]
输出:[2,7,14,8] 
解释:
数组中元素的二进制表示形式是:
1 = 0001 
3 = 0011 
4 = 0100 
8 = 1000 
查询的 XOR 值为:
[0,1] = 1 xor 3 = 2 
[1,2] = 3 xor 4 = 7 
[0,3] = 1 xor 3 xor 4 xor 8 = 14 
[3,3] = 8

示例 2:

输入:arr = [4,8,2,10], queries = [[2,3],[1,3],[0,0],[0,3]]
输出:[8,0,4,4]

提示:

  • 1 <= arr.length <= 3 * 10^4
  • 1 <= arr[i] <= 10^9
  • 1 <= queries.length <= 3 * 10^4
  • queries[i].length == 2
  • 0 <= queries[i][0] <= queries[i][1] < arr.length

解题思路

拿到题目,第一想法,暴力,那么时间复杂度是 O(Q * A),其中Q是queries的长度,A是arr的长度。这样子的话,根据数据范围,会超时。

因此需要考虑到其他方法,题目问的是,异或,那么要想到异或的性质

  • 相同的数,异或 = 0
  • 任何数 和 0 异或还是其本身

如果我们能在每一个queries[i] = [Li, Ri] 中,能够快速的得出答案,即 O(1)的话,那么就不会超时,也就是空间换时间,提前处理。

  • 我们可以用一个前缀和 记录前 n 个数的异或值
  • 0,1,2....L-1 个数的异或值为 A
  • 0,1,2,...R 个数的异或值为 B
  • 那么问 [L, R] 区间中的异或为 A^B

因为我们的前 R个的异或值中,有 前 L - 1 数的异或值,那么我们将这两个值再进行异或,那就是,相当于把 前 R个中去掉了前 L - 1 个,那么剩下的异或值,就是 [L, R] 区间中数的异或值。

这就利用了,相同数异或 = 0的性质。

AC代码(C++)

class Solution {
public:
    vector<int> xorQueries(vector<int>& arr, vector<vector<int>>& queries) {
        int n = arr.size();
    
        vector<int> ans;
        // 因为对于 L-1 可能会小于0,所以我们对前缀和数组,增加一位,使得不会越界
        vector<int> yihuo(n + 1, 0);
        int cnt = 0;
        
        for(int i = 0;i < n;i++)
        {
            cnt ^= arr[i];
            yihuo[i + 1] = cnt;
        }
        
        for(auto q : queries)
        {
            // 因为增加了一位,所以 R 变成 R+1, L-1变成了L
            int temp = yihuo[q[1]+1] ^ yihuo[q[0]];
            ans.push_back(temp);
        }
        
        return ans;
        
    }
};

 


3.获取你好友已观看的视频(Get Watched Videos by Your Friends)

题目链接

https://leetcode-cn.com/problems/get-watched-videos-by-your-friends/

题意

有 n 个人,每个人都有一个  0 到 n-1 的唯一 id 。

给你数组 watchedVideos  和 friends ,其中 watchedVideos[i]  和 friends[i] 分别表示 id = i 的人观看过的视频列表和他的好友列表。

Level 1 的视频包含所有你好友观看过的视频,level 2 的视频包含所有你好友的好友观看过的视频,以此类推。一般的,Level 为 k 的视频包含所有从你出发,最短距离为 k 的好友观看过的视频。

给定你的 id  和一个 level 值,请你找出所有指定 level 的视频,并将它们按观看频率升序返回。如果有频率相同的视频,请将它们按名字字典序从小到大排列。

示例 1:

输入:watchedVideos = [["A","B"],["C"],["B","C"],["D"]], friends = [[1,2],[0,3],[0,3],[1,2]], id = 0, level = 1
输出:["B","C"] 
解释:
你的 id 为 0 ,你的朋友包括:
id 为 1 -> watchedVideos = ["C"] 
id 为 2 -> watchedVideos = ["B","C"] 
你朋友观看过视频的频率为:
B -> 1 
C -> 2

示例 2:

输入:watchedVideos = [["A","B"],["C"],["B","C"],["D"]], friends = [[1,2],[0,3],[0,3],[1,2]], id = 0, level = 2
输出:["D"]
解释:
你的 id 为 0 ,你朋友的朋友只有一个人,他的 id 为 3 。

提示:

  • n == watchedVideos.length == friends.length
  • 2 <= n <= 100
  • 1 <= watchedVideos[i].length <= 100
  • 1 <= watchedVideos[i][j].length <= 8
  • 0 <= friends[i].length < n
  • 0 <= friends[i][j] < n
  • 0 <= id < n
  • 1 <= level < n
  • 如果 friends[i] 包含 j ,那么 friends[j] 包含 i

解题分析

根据题目,我们要先找到处于要求的 level 上的所有人,然后再去统计这些人的观看视频(包括看了啥视频,这些视频分别看了多少次),最后根据要求进行排序输出看了啥视频(先按观看频率升序排序,如果频率相同,就按视频名的字典序排序)。

因此这道题总共就是三步走。

1、其中第一个,其实就是构造一个树,然后以 id 为根节点,找到深度为 level 上的所有节点(即对应的人)。

所以第一想到就是利用BFS,找出这些人,存放在一个数组中。所以第一步就是简单的BFS

2、找到了所有的人之后,我们就统计对应这些人观看的视频,要统计看了哪些视频和每个视频对应看了几次。

所以我们想到用 map<string, int>,key表示视频名,value表示这个视频看了几次

3、按照要求进行排序

本来要对map进行排序的,但是map不能排序。而且用的是map,本身是红黑树,已经是给了 key 自动排序了。但是我们不仅仅要对视频名的字典序进行排序,还要按照观看频率,即map的value排序。

1)所以我自己做的时候,想的是暴力排序,hhh,就是我们之前统计了视频不同的数量,然后枚举这些数量,最后对每一次都遍历所有map中的视频和对应的次数,(因为是先按观看次数升序),所以找到最小的次数,同时,由于相同观看次数的时候,字典序小的肯定是在前面,我们统计最小数的时候,记录下,第一个出现最小数的视频名。这样子这个视频名就是排序后的前一个,存放在答案中 vector<string> ans。

接着我们把对应这个视频出现次数设为最大值,表示用过了。接着下一次遍历。

虽然这样子时间复杂度变大,但是不会超时。

2)后面看了大佬们的做法,也同样先用map统计了每种视频对应的观看次数。并且先把所有出现的视频名都放到 vector<string> ans 中。

然后重写排序 sort 中的cmp,cmp的规则是,先按 对应视频名统计出现的次数 mp[视频名],来排序。如果两个次数都相同,就对字符串进行字典序排序。

这样子时间复杂度会降低。

 

AC代码(方法一   BFS + map + 对map枚举排序 C++)

const int MAXN = 100 + 10;
#define INF 0x3f3f3f3f

class Solution {
public:
    
    // 用于 BFS的,记录点 x和对应的深度d
    struct Node{
        int x;
        int d;
        Node(int x_, int d_): x(x_), d(d_) {}
        
    };
    
    map<string, int> mp;
   
    vector<int> f;
    int vis[MAXN];
    
    // BFS
    void bfs(vector<vector<int>>& friends, int id, int level)
    {
        memset(vis, 0, sizeof(vis));
        queue<Node> q;
        q.push(Node(id, 0));
        vis[id] = 1;
        
        while(!q.empty())
        {
            Node cur = q.front();
            q.pop();
            
            if(cur.d > level) break;   //如果出现深度 > 要求的,那么说明对应的level深度已经找完了,就可以提前结束BFS了
            if(cur.d == level) f.push_back(cur.x);  // 等于深度,就存放起来
            
            for(int i = 0;i < friends[cur.x].size(); ++i)
            {
                if(vis[friends[cur.x][i]] == 0)
                {
                    q.push(Node(friends[cur.x][i], cur.d + 1));
                    vis[friends[cur.x][i]] = 1;
                }
            }
            
            
        }
        return;
    }
    
    
    vector<string> watchedVideosByFriends(vector<vector<string>>& watchedVideos, vector<vector<int>>& friends, int id, int level) {
        
        vector<string> ans;
        // 1、先BFS
        bfs(friends, id, level);

        // 2、用map统计出现的次数
        int numVide = 1;
        for(int i = 0;i < f.size();i++)
        {
            for(int j = 0;j < watchedVideos[f[i]].size(); ++j)
            {
                if(mp[watchedVideos[f[i]][j]] == 0)
                {
                    mp[watchedVideos[f[i]][j]] = 1;
                    numVide++;
                }
                else
                    mp[watchedVideos[f[i]][j]]++;
            }
        }
        
        // 3、暴力对map排序
        while(--numVide)
        {
            int minVal = INF;  // 找的是最小值
            string res;
            for(map<string,int>::iterator it=mp.begin();it!=mp.end();++it)
            {
                if((it->second) < minVal)
                {
                    minVal = it->second;  // 记录出现的最小值
                }
                
            }
            for(map<string,int>::iterator it=mp.begin();it!=mp.end();++it)
            {
                if((it->second) == minVal)  // 从前往后,第一个对应最小值,同时也是字典序最小
                {
                    res = it->first;
                    it->second = INF;  // 这个已经输出了,所以变为最大值,下次不会搜索到
                    break;
                }
                
            }
            ans.push_back(res);
        }
        
        
        
        return ans;
    }
};

AC代码(方法二   BFS+map+重写sort排序  C++)

const int MAXN = 100 + 10;
map<string, int> mp;

// 重写cmp
inline bool cmp(const string &a, const string &b)
{
    if(mp[a] != mp[b]) return mp[a] < mp[b];  //先按频率排序
    //频率相同,就按字符串排序
    return a < b;
    
}

class Solution {
public:
    
    struct Node{
        int x;
        int d;
        Node(int x_, int d_): x(x_), d(d_) {}
    };

    vector<int> f;
    int vis[MAXN];

    void bfs(vector<vector<int>>& friends, int id, int level)
    {
        memset(vis, 0, sizeof(vis));
        queue<Node> q;
        q.push(Node(id, 0));
        vis[id] = 1;
        
        while(!q.empty())
        {
            Node cur = q.front();
            q.pop();
            
            if(cur.d > level) break;
            if(cur.d == level) f.push_back(cur.x);
            
            for(int i = 0;i < friends[cur.x].size(); ++i)
            {
                if(vis[friends[cur.x][i]] == 0)
                {
                    q.push(Node(friends[cur.x][i], cur.d + 1));
                    vis[friends[cur.x][i]] = 1;
                }
            }
        }
        return;
    }
    

    vector<string> watchedVideosByFriends(vector<vector<string>>& watchedVideos, vector<vector<int>>& friends, int id, int level) {
        
        vector<string> ans;
        // 1、BFS
        bfs(friends, id, level);

        // 2、用map统计次数
        mp.clear();  // 需要把 mp 清除内存,安全
        for(int i = 0;i < f.size();i++)
        {
            for(int j = 0;j < watchedVideos[f[i]].size(); ++j)
            {
                if(mp[watchedVideos[f[i]][j]] == 0)
                {
                    ans.push_back(watchedVideos[f[i]][j]); //出现新的视频,放入ans
                    mp[watchedVideos[f[i]][j]] = 1;
                }
                else
                    mp[watchedVideos[f[i]][j]]++;
            }
        }

        sort(ans.begin(), ans.end(), cmp); // 按照要求排序,重写cmp

        return ans;
    }
};

4.让字符串成为回文串的最少插入次数(Minimum Insertion Steps to Make A String Palindrome)

题目链接

https://leetcode-cn.com/problems/minimum-insertion-steps-to-make-a-string-palindrome/

题意

给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。

请你返回让 s 成为回文串的 最少操作次数 。

「回文串」是正读和反读都相同的字符串。

示例 1:

输入:s = "zzazz"
输出:0
解释:字符串 "zzazz" 已经是回文串了,所以不需要做任何插入操作。

示例 2:

输入:s = "mbadm"
输出:2
解释:字符串可变为 "mbdadbm" 或者 "mdbabdm" 。

示例 3:

输入:s = "leetcode"
输出:5
解释:插入 5 个字符后字符串变为 "leetcodocteel" 。

示例 4:

输入:s = "g"
输出:0

示例 5:

输入:s = "no"
输出:1

提示:

  • 1 <= s.length <= 500
  • s 中所有字符都是小写字母。

解题分析

最小操作次数,所以考虑一下,能不能使用动态规划DP

1)设变量,因为字符串是不是回文串,那就是从某一位置到另一位置,因此要用二维的DP。所以设 dp[ i ][ j ] 表示,从 i 到 j 字符串要是回文串的最小操作次数。

2)状态转移当我们判断,从 i 到 j 的时候,如果 s[ i ] == s[ j ],那就说明这两个字符相同,不会增加操作次数,那么 dp[ i ][ j ] = dp[ i+1 ][ j-1 ],也就是由内部的操作次数决定。如果 s[ i ] != s[ j ],说明这两个字符相同,那么需要增加(其实也可以看成是删除)一个的时候。那么我们可以选择在左边增加,或者右边增加,那么因为是最小操作,所以取最小值 min(dp[ i+1 ][ j], dp[ i][ j-1 ] ) + 1 ,在最小值的基础上,因为不同,所以要操作一次,因为操作数是 min + 1。

3)我们判断字符串,肯定是从 (0, n-1),往内部看的,所以用到递归,那么就会有重复的值,因此用记忆化搜索,即如果已经计算过的值,我们就直接用。因此我们设 dp 的初始值为 -1,只要需要不是 -1(因为处理过的,操作数至少会是0),那么就说明计算过的值,直接返回。

同时边界条件是,当 (i, j) 表示的字符串长度为 1或者0的时候,相当于本身就是回文串,那么操作数为0。

 

AC代码(Java)

import java.util.Arrays;

class Solution {
    int[][] dp = new int[510][510];

    public int dfs(int l, int r, String s){
        if(l >= r) return 0;

        if(dp[l][r] != -1) return dp[l][r];
        
        // 递归
        if(s.charAt(l) == s.charAt(r))
            dp[l][r] = dfs(l+1, r-1, s);  // 由下一个dfs算出来的值就是 dp[l+1][r-1],然后再给回现在的 dp[l][r]
        else
            dp[l][r] = Math.min(dfs(l+1, r, s), dfs(l, r-1, s)) + 1;
        
        return dp[l][r];
    }
    
    public int minInsertions(String s) {
        for(int[] x : dp)
            Arrays.fill(x, -1);  // Arrays.fill只能给一维数组赋值

        int n = s.length();
        return dfs(0, n-1, s);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值