这周的周赛,总体不难。
第一题,模拟,时间复杂度过得去。还有一种简化时间复杂度的方法,具体看详细题解。
第二题,其实就是利用条件去构建,然后返回来看构建后的结果符不符合要求即可,挺简单的。
第三题,DFS
第四题,根据数据的大小,可以选择利用枚举(二进制枚举所有选择情况),也可以利用 DFS。
详细题解如下。
1. 奇数值单元格的数目(Cells with Odd Values in A Matrix)
2. 重构 2 行二进制矩阵(Reconstruct A 2 Row Binary Matrix)
3.统计封闭岛屿的数目(Number of Closed Islands)
4.得分最高的单词集合(Maximum Score Words Formed by Letters)
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;
}
};