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

这周的周赛,总体不难。

第一题,模拟,时间复杂度过得去。还有一种简化时间复杂度的方法,具体看详细题解。

第二题,其实就是利用条件去构建,然后返回来看构建后的结果符不符合要求即可,挺简单的。

第三题,DFS

第四题,根据数据的大小,可以选择利用枚举(二进制枚举所有选择情况),也可以利用 DFS。

详细题解如下。


1. 奇数值单元格的数目(Cells with Odd Values in A Matrix)

            AC代码(方法一 C++)

            AC代码(方法二 C++)

2. 重构 2 行二进制矩阵(Reconstruct A 2 Row Binary Matrix)

            AC代码(C++)

3.统计封闭岛屿的数目(Number of Closed Islands)

            AC代码(C++)

4.得分最高的单词集合(Maximum Score Words Formed by Letters)

            AC代码(方法一:枚举—利用二进制 C++)

            AC代码(方法二:DFS C++)


LeetCode第162场周赛地址:

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


1. 奇数值单元格的数目(Cells with Odd Values in A Matrix)

题目链接

https://leetcode-cn.com/problems/cells-with-odd-values-in-a-matrix/

题意

给你一个 n 行 m 列的矩阵,最开始的时候,每个单元格中的值都是 0。

另有一个索引数组 indices,indices[i] = [ri, ci] 中的 ri 和 ci 分别表示指定的行和列(从 0 开始编号)。

你需要将每对 [ri, ci] 指定的行和列上的所有单元格的值加 1。

请你在执行完所有 indices 指定的增量操作后,返回矩阵中 「奇数值单元格」 的数目。

示例 1:

输入:n = 2, m = 3, indices = [[0,1],[1,1]]
输出:6
解释:最开始的矩阵是 [[0,0,0],[0,0,0]]。
第一次增量操作后得到 [[1,2,1],[0,1,0]]。
最后的矩阵是 [[1,3,1],[1,3,1]],里面有 6 个奇数。

示例 2:

输入:n = 2, m = 2, indices = [[1,1],[0,0]]
输出:0
解释:最后的矩阵是 [[2,2],[2,2]],里面没有奇数。

提示:

  • 1 <= n <= 50
  • 1 <= m <= 50
  • 1 <= indices.length <= 100
  • 0 <= indices[i][0] < n
  • 0 <= indices[i][1] < m

 

解题思路

方法一:

根据题目,模拟即可,新建一个二维数组,用于模拟结果矩阵,一开始是全 0。

接着遍历所有的索引数组 indices情况,然后对需要操作的那一行,那一列全部加1。最后再遍历那个二位数组,判断奇数的个数即可。

这样子的时间复杂度是 O(L*(N+M)),空间复杂度是O(NM)。其中L是索引数组 indices的长度。

根据数据范围,该方法可以通过。

方法二:

我们可以利用两个一维数组,一个数组 r 表示的是行的操作次数,另一个数组 r 表示的是列的操作次数,这样子关于在(x,y)的点上数为 r[x] + c[y]。

如果接着简化,考虑到所有奇数的情况要求是,行为奇数且列为偶数,或者行为偶数且列为奇数

所以只要我们分别统计行的奇数个数 a,和列的奇数个数 b。

那么结果就是:a*(m-b) + (n-a)*b。

即,当行为奇数的时候,列为偶数情况下的个数。加上,行为偶数的时候,列为奇数情况下的个数。

时间复杂度是 O(L + N + M),空间复杂度是O(N+M)

相比于方法一,降低了时间复杂度和空间复杂度

AC代码(方法一 C++)

// 模拟整个数组的变化情况

class Solution {
public:
    int oddCells(int n, int m, vector<vector<int>>& indices) {
        int nums[55][55];
        memset(nums,0,sizeof(nums));
        int ans = 0;
        for(auto x : indices)
        {
            int r = x[0], c = x[1];
            for(int i = 0;i < m;i++)
                ++nums[r][i];
            for(int i = 0;i < n;i++)
                ++nums[i][c];
        }
        for(int i = 0;i < n;i++)
            for(int j = 0;j < m;j++)
            {
                if(nums[i][j] % 2 == 1)
                    ++ans;
            }

        return ans;
    }
};

AC代码(方法二 C++)

class Solution {
public:
    int oddCells(int n, int m, vector<vector<int>>& indices) {
        int cntOddRow = 0;
        int cntOddCol = 0;
        int r[55];
        int c[55];
        memset(r,0,sizeof(r));
        memset(c,0,sizeof(c));

        for(auto x : indices)
        {
            r[x[0]]++;
            c[x[1]]++;
        }
        for(int i = 0;i < n;i++)
        {
            if(r[i] %2 == 1)
                cntOddRow++;
        }
        for(int i = 0;i < m;i++)
        {
            if(c[i] %2 == 1)
                cntOddCol++;
        }
        int ans = cntOddRow * (m-cntOddCol) + (n-cntOddRow) * cntOddCol;
        return ans;
    }
};

2. 重构 2 行二进制矩阵(Reconstruct A 2 Row Binary Matrix)

题目链接

https://leetcode-cn.com/problems/reconstruct-a-2-row-binary-matrix/

题意

给你一个 2 行 n 列的二进制数组:

  • 矩阵是一个二进制矩阵,这意味着矩阵中的每个元素不是 0 就是 1。
  • 第 0 行的元素之和为 upper。
  • 第 1 行的元素之和为 lower。
  • 第 i 列(从 0 开始编号)的元素之和为 colsum[i],colsum 是一个长度为 n 的整数数组。

你需要利用 upper,lower 和 colsum 来重构这个矩阵,并以二维整数数组的形式返回它。

如果有多个不同的答案,那么任意一个都可以通过本题。

如果不存在符合要求的答案,就请返回一个空的二维数组。

示例 1:

输入:upper = 2, lower = 1, colsum = [1,1,1]
输出:[[1,1,0],[0,0,1]]
解释:[[1,0,1],[0,1,0]] 和 [[0,1,1],[1,0,0]] 也是正确答案。

示例 2:

输入:upper = 2, lower = 3, colsum = [2,2,1,1]
输出:[]

示例 3:

输入:upper = 5, lower = 5, colsum = [2,1,2,0,1,0,1,2,0,1]
输出:[[1,1,1,0,1,0,0,1,0,0],[1,0,1,0,0,0,1,1,0,1]]

提示:

  • 1 <= colsum.length <= 10^5
  • 0 <= upper, lower <= colsum.length
  • 0 <= colsum[i] <= 2

 

解题思路

贪心,遍历一维数组colsum,对应有3种情况:

1)遇到是2的话,那么就是两行该列上都是1,然后upper,lower都减1,然后判断upper,lower有一个小于0了,说明没有这个矩阵的存在,那就返回空数组。如果都大于0,那么往下。

2)遇到是1的话,选择是贪心,也就是先把第一行填完,使得upper等于0。如果第一行的已经upper为0,说明第一行不能再填了。那我们再遇到是1的,就全部填到第二行种。最后判断,如果upper,lower有一个不为0,那就说明不存在。如果存在这个矩阵,说明填完之后,upper和lower应该都刚好是0.

3)遇到是0的话,就两行该列上都是0,所以这个不用考虑

我用的方法是两次遍历,但其实一次遍历也是可以,只是两次遍历的思路清晰好理解。

时间复杂度是 O(2N),其中N是一维数组colsum的长度。

AC代码(C++)

class Solution {
public:
    vector<vector<int>> reconstructMatrix(int upper, int lower, vector<int>& colsum) {
        vector<vector<int>> ans;
        // init
        for(int i = 0;i < 2;i++)
        {
            vector<int> zeros;
            for(int j = 0; j < colsum.size();j++) 
                zeros.push_back(0);
            ans.push_back(zeros);
        }

        // 先找出 2 的,这个时候,两个都是1
        for(int i = 0; i < colsum.size();i++)
        {
            if(colsum[i] == 2)
            {
                ans[0][i] = 1;
                ans[1][i] = 1;
                --upper;
                --lower;
            }
        }
        if(upper < 0 || lower < 0)
            return {};

        // 找是1的时候,贪心全部往第一行填,直到upper等于0后,再全部往第二行填
        for(int i = 0; i < colsum.size();i++)
        {
            if(colsum[i] == 1)
            {
                if(upper > 0)
                {
                    --upper;
                    ans[0][i] = 1;
                    continue;
                }

                --lower;
                ans[1][i] = 1;
            }
        }
        if(upper != 0 || lower != 0)
            return {};

        return ans;
    }
};

3.统计封闭岛屿的数目(Number of Closed Islands)

题目链接

https://leetcode-cn.com/problems/number-of-closed-islands/

题意

有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 )。

我们从一块陆地出发,每次可以往上下左右 4 个方向相邻区域走,能走到的所有陆地区域,我们将其称为一座「岛屿」。

如果一座岛屿 完全 由水域包围,即陆地边缘上下左右所有相邻区域都是水域,那么我们将其称为 「封闭岛屿」。

请返回封闭岛屿的数目。

示例中有图的显示,具体可点击链接查看。

提示:

  • 1 <= grid.length, grid[0].length <= 100
  • 0 <= grid[i][j] <=1

解题分析

根据题意,就是找到连通的岛屿个数,并且判断这个岛屿的上下左右是不是都是水。

所以只要我们得到一个岛屿有一个陆地在边界,那就说明这个岛屿不是封闭岛屿。

利用DFS,从某个路地点出发深搜,找出这一陆地所属的岛屿上的所有陆地,判断有没有陆地是在边界上的。如果有的话,说明这个岛屿不是封闭岛屿。

AC代码(C++)

class Solution {
public:
    int vis[105][105];   // 记录这个地方是否搜过
    int dx[4] = {1,0,-1,0};
    int dy[4] = {0,1,0,-1};

    bool dfs(int x, int y, vector<vector<int>>& grid)
    {
        vis[x][y] = 1;
        int n = grid.size(), m = grid[0].size();
        bool flag = true;

        for(int i = 0;i < 4;i++)
        {
            int xx = x+dx[i], yy = y+dy[i];
            if(xx < 0 || xx>=n || yy<0 || yy>=m) // 到达边缘,说明不是封闭
                flag = false;
            else
            {
                if(grid[xx][yy]==0 && vis[xx][yy]==0)  // 还要判断即使不是边缘,能不能往下DFS的条件
                {
                    bool ret = dfs(xx, yy, grid);
                    flag = flag && ret;
                }
            }
        }
        return flag;  // 等这个岛屿的所有陆地走完,再判断
    }

    int closedIsland(vector<vector<int>>& grid) {
        int ans = 0;
        memset(vis,0,sizeof(vis)); // 0表示没走过

        for(int i = 0;i < grid.size();i++)
        {
            for(int j = 0;j < grid[0].size();j++)
            {
                if(grid[i][j]==0 && vis[i][j]==0)  // 可以DFS的条件,陆地且没走过的
                {
                    bool flag = dfs(i, j, grid);
                    if(flag)
                        ++ans;
                }
            }
        }
        return ans;
    }
};

 


4.得分最高的单词集合(Maximum Score Words Formed by Letters)

题目链接

https://leetcode-cn.com/problems/maximum-score-words-formed-by-letters/

题意

你将会得到一份单词表 words,一个字母表 letters (可能会有重复字母),以及每个字母对应的得分情况表 score。

请你帮忙计算玩家在单词拼写游戏中所能获得的「最高得分」:能够由 letters 里的字母拼写出的 任意 属于 words 单词子集中,分数最高的单词集合的得分。

单词拼写游戏的规则概述如下:

  • 玩家需要用字母表 letters 里的字母来拼写单词表 words 中的单词。
  • 可以只使用字母表 letters 中的部分字母,但是每个字母最多被使用一次。
  • 单词表 words 中每个单词只能计分(使用)一次。
  • 根据字母得分情况表score,字母 'a', 'b', 'c', ... , 'z' 对应的得分分别为 score[0], score[1], ..., score[25]。
  • 本场游戏的「得分」是指:玩家所拼写出的单词集合里包含的所有字母的得分之和。

示例 1:

输入:words = ["dog","cat","dad","good"], letters = ["a","a","c","d","d","d","g","o","o"], score = [1,0,9,5,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0]
输出:23
解释:
字母得分为  a=1, c=9, d=5, g=3, o=2
使用给定的字母表 letters,我们可以拼写单词 "dad" (5+1+5)和 "good" (3+2+2+5),得分为 23 。
而单词 "dad" 和 "dog" 只能得到 21 分。

示例 2:

输入:words = ["xxxz","ax","bx","cx"], letters = ["z","a","b","c","x","x","x"], score = [4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,10]
输出:27
解释:
字母得分为  a=4, b=4, c=4, x=5, z=10
使用给定的字母表 letters,我们可以组成单词 "ax" (4+5), "bx" (4+5) 和 "cx" (4+5) ,总得分为 27 。
单词 "xxxz" 的得分仅为 25 。

示例 3:

输入:words = ["leetcode"], letters = ["l","e","t","c","o","d"], score = [0,0,1,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0]
输出:0
解释:
字母 "e" 在字母表 letters 中只出现了一次,所以无法组成单词表 words 中的单词。

提示:

  • 1 <= words.length <= 14
  • 1 <= words[i].length <= 15
  • 1 <= letters.length <= 100
  • letters[i].length == 1
  • score.length == 26
  • 0 <= score[i] <= 10
  • words[i] 和 letters[i] 只包含小写的英文字母。

 

解题分析

中等难度的困难题目,有两个思路,都是根据数据大小来进行解答的。

方法一:

我们根据单词表的数据,长度最多为14,每一个单词有两种情况,选或不选。所以单词表中枚举的所有的情况就是有 2^14=16384。因此我们可以利用枚举,即用0到16383,然后某个数的二进制表示中,1表示这个单词选中,0表示不选。

这样子枚举后,统计当前选择情况下的字母使用情况,从而判断这种选择是否满足“玩家需要用字母表 letters 里的字母来拼写单词表 words 中的单词。”条件,如果满足,那就统计此种情况的得分,取最高分记录即可。

方法二:

利用DFS,对于每一个单词,有选或者不选两种情况。

因此DFS的时候,我们可以也对应两种情况:这个单词选中,这个单词不选中,从而进行深搜

如果认为这个单词选择时,我们还要判断,是不是满足剩余的字母表中的字母够拼写,不够拼写也无法选择这个单词。

因为认为选中的时候,字母表记录的字母数会变化,但认为不选中的时候,要把原始的字母数备份,然后恢复。

AC代码(方法一:枚举—利用二进制 C++)

class Solution {
public:
    int maxScoreWords(vector<string>& words, vector<char>& letters, vector<int>& score) {
        // 枚举贪心,因为单词表长度为14,每一个单词选择情况2种:选或不选
        // 所以总共枚举次数:2^14,这样子判断所有情况后,计划出这种情况下,能否得分
        // 如果得分的话,就计算看是不是最高分
        
        // 统计字母表中的字母情况
        int cntOri[26];
        memset(cntOri, 0, sizeof(cntOri));
        for(auto x: letters)
            ++cntOri[x-'a'];
        
        int ans = 0;
        int cur = 0;
        int n = words.size();
        int lim = 1<<n;  // 枚举所有情况
        for(int i = 0;i < lim;i++)
        {
            cur = 0;
            int cnt[26];   //记录这种选择情况下的单词的字母数需要情况
            memset(cnt,0,sizeof(cnt));

            for(int j = 0; j < n;j++)
            {
                if((i>>j)&1)  // 检查这个单词二进制对应是1还是0,如果是1,表示这个单词选中
                {
                    for(auto x : words[j]) ++cnt[x-'a'];
                }
            }
            // 判断这种选择能不能拼写出来,能得话就同时记录分数,不能说明这种选择情况得分为0
            for(int j = 0;j < 26;j++)
            {
                if(cnt[j] <= cntOri[j])  
                {
                    cur += cnt[j]*score[j];
                }
                else
                {
                    cur = 0;
                    break;
                }
            }
            
            // 保留最高分
            if(cur > ans)
                ans = cur;

        }
        return ans;

    }
};

AC代码(方法二:DFS C++)

class Solution {
public:
    vector<int> cnt;
    int ans = 0;
    
    // 判断这个单词如果要选中,能不能拼写成功,能拼写成功就同时返回这个单词得分
    int judg(string& word, vector<int>& score)
    {
        int sum = 0;
        for(int i = 0;i < word.size();i++)
        {
            if(cnt[word[i]-'a'] == 0)
                return -1;
            else
            {
                --cnt[word[i]-'a'];
                sum += score[word[i]-'a'];
            }
        }
        return sum;
    }

    void dfs(int x, int cur, vector<string>& words, vector<char>& letters, vector<int>& score)
    {
        // DFS结束条件,当结束得时候,同时保留最高分
        if(x == words.size())
        {
            ans = max(ans, cur);
            return;
        }

        // 拼写这个单词
        vector<int> ori = cnt; // 备份回溯
        int getScore = judg(words[x], score);
        if(getScore != -1)
        {
            dfs(x+1, cur+getScore, words, letters, score);
        }

        // 不拼写这个单词(回溯)
        cnt = ori;
        dfs(x+1, cur, words, letters, score);

    }


    int maxScoreWords(vector<string>& words, vector<char>& letters, vector<int>& score) {
        // 统计单词表
        //memset(cnt, 0, sizeof(cnt));
        cnt = vector<int>(26,0);
        for(auto x: letters)
            ++cnt[x-'a'];

        dfs(0, 0, words, letters, score);   // 开始DFS,初始位置,0,且得分为0
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值