LeetCode第275场周赛-2022/1/9
检查是否每一行每一列都包含全部整数
题目描述
检查是否每一行每一列都包含全部整数
对一个大小为 n x n 的矩阵而言,如果其每一行和每一列都包含从 1 到 n 的全部整数(含 1 和 n),则认为该矩阵是一个有效矩阵。
给你一个大小为 n x n 的整数矩阵 matrix ,请你判断矩阵是否为一个有效矩阵:如果是返回 true;否则返回 false。
- n == matrix.length == matrix[ i ].length
- 1 <= n <= 100
- 1 <= matrix[ i ][ j ] <= n
输入:matrix = [[1,2,3],[3,1,2],[2,3,1]]
输出:true
题目分析
数独检查,哈希。由于题目已经给定
1
<
=
m
a
t
r
i
x
[
i
]
[
j
]
<
=
n
1 <= matrix[ i ][ j ] <= n
1<=matrix[i][j]<=n,只需用
s
e
t
set
set 或
u
n
o
r
d
e
r
e
d
_
s
e
t
unordered \_set
unordered_set 容器判重即可,下给出无此限制的做法。用
s
e
t
set
set 或
u
n
o
r
d
e
r
e
d
_
s
e
t
unordered \_set
unordered_set 容器判重(元素总数是否为
n
n
n),后检查每行每列元素和是否为
n
(
n
+
1
)
2
\frac{n(n+1)}{2}
2n(n+1)。
class Solution {
public:
bool checkValid(vector<vector<int>>& matrix) {
int n = matrix.size();
set<int> list;
for (int i = 0; i < n; i++) {
list.clear();
int sum = 0;
for (int j = 0; j < n; j++) {
list.insert(matrix[i][j]);
sum += matrix[i][j];
}
if (list.size() != n || sum !=n * (n + 1) / 2) return false;
}
for (int i = 0; i < n; i++) {
list.clear();
int sum = 0;
for (int j = 0; j < n; j++) {
list.insert(matrix[j][i]);
sum += matrix[j][i];
}
if (list.size() != n || sum != n * (n + 1) / 2) return false;
}
return true;
}
};
最少交换次数来组合所有的 1 II
题目描述
最少交换次数来组合所有的 1 II
交换定义为选中一个数组中的两个互不相同的位置并交换二者的值。环形数组是一个数组,可以认为第一个元素和最后一个元素相邻 。
给你一个二进制环形数组 nums ,返回在任意位置将数组中的所有 1 聚集在一起需要的最少交换次数。
- 1 <= nums.length <= 1 0 5 10^5 105
- nums[ i ] 为 0 或者 1
输入:nums = [0,1,1,1,0,0,1,1,0]
输出:2
题目分析
延长数组处理环+滑动窗口+前缀和数组。考虑到最终所有的
1
1
1 全部并在一起,区别在于这段 “
1
1
1” 的位置不同,故可以设置一个长度为所有
1
1
1 个数和的滑动窗口,让滑动窗口从初始位置开始滑动,分别统计每次滑动窗口中
0
0
0 的个数,最小值即为最少交换次数,统计滑动窗口中
0
0
0 元素个数用前缀和数组最为方便(和825题做法类似,双指针/滑动窗口+前缀和)。另外由于数组为环形数组,倍长数组可化环为直。
class Solution {
public:
int minSwaps(vector<int>& nums) {
int len = accumulate(nums.begin(), nums.end(), 0), n = nums.size();
int res = 1000000, pre_zero[2 * n + 1];
for (int i = 0; i < n; i++) {
nums.push_back(nums[i]);
}
pre_zero[0] = 0;
for (int i = 0; i < 2 * n; i++) {
pre_zero[i + 1] = pre_zero[i] + 1 - nums[i];
}
for (int i = 1; i <= n; i++) {
res = min(res, pre_zero[i + len - 1] - pre_zero[i - 1]);
}
return res;
}
};
统计追加字母可以获得的单词数
题目描述
统计追加字母可以获得的单词数
给你两个下标从 0 开始的字符串数组 startWords 和 targetWords 。每个字符串都仅由小写英文字母组成。对于 targetWords 中的每个字符串,检查是否能够从 startWords 中选出一个字符串,执行一次转换操作,得到的结果与当前 targetWords 字符串相等。
转换操作如下面两步所述:
- 追加任何不存在于当前字符串的任一小写字母到当前字符串的末尾。
例如,如果字符串为 “abc” ,那么字母 ‘d’、‘e’ 或 ‘y’ 都可以加到该字符串末尾,但 ‘a’ 就不行。如果追加的是 ‘d’ ,那么结果字符串为 “abcd” 。 - 重排新字符串中的字母,可以按任意顺序重新排布字母。
例如,“abcd” 可以重排为 “acbd”、“bacd”、“cbda”,以此类推。注意,它也可以重排为 “abcd” 自身。
找出 targetWords 中有多少字符串能够由 startWords 中的任一字符串执行上述转换操作获得。返回 targetWords 中这类字符串的数目。
注意:你仅能验证 targetWords 中的字符串是否可以由 startWords 中的某个字符串经执行操作获得。startWords 中的字符串在这一过程中不发生实际变更。
- 1 <= startWords.length, targetWords.length <= 5 ∗ 1 0 4 5*10^4 5∗104
- 1 <= startWords[ i ].length, targetWords[ j ].length <= 26
- startWords 和 targetWords 中的每个字符串都仅由小写英文字母组成
- 在 startWords 或 targetWords 的任一字符串中,每个字母至多出现一次
输入:startWords = [“ant”,“act”,“tack”], targetWords = [“tack”,“act”,“acti”]
输出:2
题目分析
由于每个单词的长度不超过26,每个字母来源于
a
∼
z
a\sim z
a∼z, 且每个单词中字母不重复,所以每个单词可用一个至多 26
b
i
t
bit
bit 的二进制数表示(定义一个
t
r
a
n
s
f
o
r
m
transform
transform 函数),如
a
c
d
→
1
d
1
c
0
1
a
、
acd\to1_d1_c01_a、
acd→1d1c01a、
a
n
k
→
1
n
00
1
k
000000000
1
a
ank\to1_{n} 001_k0000000001_{a}
ank→1n001k0000000001a 将
s
t
a
r
t
W
a
r
d
s
startWards
startWards 和
t
a
r
g
e
t
W
a
r
d
s
targetWards
targetWards 中所有字符串都转换为这样的二进制数,对
s
t
a
r
t
W
a
r
d
s
startWards
startWards 转换后的每一个二进制数,任意一个
“
0
”
“0”
“0” 位变成
“
1
”
“1”
“1” 后就是题目要求的一种转换状态,如
a
c
d
→
a
c
d
g
:
1101
→
101101
acd\to acdg:1101\to101101
acd→acdg:1101→101101(高位省略部分都是
0
0
0),将
s
t
a
r
t
W
a
r
d
s
startWards
startWards 所有的可能转换状态存储在哈希表中,与
t
r
a
n
s
f
o
r
m
(
w
o
r
d
i
n
t
a
r
g
e
t
W
a
r
d
s
)
transform(word\; in \; targetWards)
transform(wordintargetWards) 逐一比较即可求出
r
e
s
res
res。
class Solution {
public:
int wordCount(vector<string>& startWords, vector<string>& targetWords) {
auto transform = [](const string& word) -> int {
int digit = 0;
for (char ch: word) {
digit |= 1 << (ch - 'a');
}
return digit;
};
unordered_set<int> targets;
for (const string& word: startWords) {
int digit = transform(word);
for (int i = 0; i < 26; i++) {
if(((digit >> i) & 1) == 0) targets.insert(digit | (1 << i));
}
}
int res = 0;
for (const string& word: targetWords) {
if (targets.count(transform(word))) res++;
}
return res;
}
};
全部开花的最早一天
题目描述
全部开花的最早一天
你有 n 枚花的种子。每枚种子必须先种下,才能开始生长、开花。播种需要时间,种子的生长也是如此。给你两个下标从 0 开始的整数数组 plantTime 和 growTime ,每个数组的长度都是 n :
- plantTime[i] 是播种第 i 枚种子所需的完整天数。每天,你只能为播种某一枚种子而劳作。无须连续几天都在种同一枚种子,但是种子播种必须在你工作的天数达到 plantTime[i] 之后才算完成。
- growTime[i] 是第 i 枚种子完全种下后生长所需的完整天数。在它生长的最后一天之后,将会开花并且永远绽放。
从第 0 开始,你可以按任意顺序播种种子。返回所有种子都开花的最早一天是第几天。
- n == plantTime.length == growTime.length
- 1 <= n <= 1 0 5 10^5 105
- 1 <= plantTime[ i ], growTime[ i ] <= 1 0 4 10^4 104
输入:plantTime = [1,2,3,2], growTime = [2,1,2,1]
输出:9
题目分析
宏观考虑,由题目要求知每天只能为播种一枚种子而劳作,即
p
l
a
n
t
T
i
m
e
plantTime
plantTime 是不能互相叠加冲突的,那么题目待求的最短时间
r
e
s
≥
∑
i
=
0
n
−
1
p
l
a
n
t
T
i
m
e
[
i
]
res\ge\sum_{i=0}^{n-1}plantTime[i]
res≥∑i=0n−1plantTime[i] 。
显然,在所有种子播种完成后最后一枚播种完成的种子还需要花费
g
r
o
w
T
i
m
e
[
i
]
growTime[i]
growTime[i] 的时间生长,于是通常来说应有
r
e
s
=
∑
i
=
0
n
−
1
p
l
a
n
t
T
i
m
e
[
i
]
+
m
i
n
{
g
r
o
w
T
i
m
e
[
i
]
}
res=\sum_{i=0}^{n-1}plantTime[i]+min\left \{ growTime[i] \right \}
res=∑i=0n−1plantTime[i]+min{growTime[i]}。
(如下图,每一行代表一枚种子,蓝色代表播种,黄色代表生长)
但可能出现的问题在于倒数第二行的黄色格子长度比最后一整行还长
则此时应有
r
e
s
=
∑
i
=
0
n
−
2
p
l
a
n
t
T
i
m
e
[
i
]
+
g
r
o
w
T
i
m
e
[
倒
数
第
二
行
的
种
子
]
res=\sum_{i=0}^{n-2}plantTime[i]+growTime[倒数第二行的种子]
res=∑i=0n−2plantTime[i]+growTime[倒数第二行的种子],为了让
r
e
s
res
res 最小,倒数第二行的
g
r
o
w
T
i
m
e
growTime
growTime 应该是所有
g
r
o
w
T
i
m
e
growTime
growTime 中倒数第二小的,且
r
e
s
=
m
a
x
(
∑
i
=
0
n
−
2
p
l
a
n
t
T
i
m
e
[
i
]
+
g
r
o
w
T
i
m
e
[
倒
数
第
二
行
的
种
子
]
,
∑
i
=
0
n
−
2
p
l
a
n
t
T
i
m
e
[
i
]
+
g
r
o
w
T
i
m
e
[
倒
数
第
二
行
的
种
子
]
)
res=max(\sum_{i=0}^{n-2}plantTime[i]+growTime[倒数第二行的种子],\newline \quad\quad\quad\qquad \sum_{i=0}^{n-2}plantTime[i]+growTime[倒数第二行的种子])
res=max(∑i=0n−2plantTime[i]+growTime[倒数第二行的种子],∑i=0n−2plantTime[i]+growTime[倒数第二行的种子])。
…依此类推,倒数第三行的
g
r
o
w
T
i
m
e
growTime
growTime 应该是所有
g
r
o
w
T
i
m
e
growTime
growTime 中倒数第三小的,
r
e
s
=
m
a
x
(
∑
i
=
0
n
−
2
p
l
a
n
t
T
i
m
e
[
i
]
+
g
r
o
w
T
i
m
e
[
倒
数
第
二
行
的
种
子
]
,
∑
i
=
0
n
−
2
p
l
a
n
t
T
i
m
e
[
i
]
+
g
r
o
w
T
i
m
e
[
倒
数
第
二
行
的
种
子
]
,
∑
i
=
0
n
−
3
p
l
a
n
t
T
i
m
e
[
i
]
+
g
r
o
w
T
i
m
e
[
倒
数
第
三
行
的
种
子
]
)
res=max(\sum_{i=0}^{n-2}plantTime[i]+growTime[倒数第二行的种子],\newline \quad\quad\quad\qquad \sum_{i=0}^{n-2}plantTime[i]+growTime[倒数第二行的种子],\newline \quad\quad\quad\qquad \sum_{i=0}^{n-3}plantTime[i]+growTime[倒数第三行的种子])
res=max(∑i=0n−2plantTime[i]+growTime[倒数第二行的种子],∑i=0n−2plantTime[i]+growTime[倒数第二行的种子],∑i=0n−3plantTime[i]+growTime[倒数第三行的种子])。
那么代码就很简单了,设置一个保存每颗种子信息的结构体数组 s e e d [ n ] seed[n] seed[n],对数组按结构体中的 g r o w T i m e growTime growTime 从大到小排序,可用前缀和 p r e = ∑ p l a n t T i m e pre=\sum plantTime pre=∑plantTime,求出 m a x ( p r e + g r o w T i m e ) max(pre+growTime) max(pre+growTime) 即可。
class Solution {
public:
int earliestFullBloom(vector<int>& plantTime, vector<int>& growTime) {
int n = plantTime.size();
int pre = 0, temp = 0, res = 0;
struct Seed
{
int index;
int plant;
int grow;
}seed[n];
for (int i = 0; i < n; i++) {
seed[i].index = i;
seed[i].plant = plantTime[i];
seed[i].grow = growTime[i];
}
auto cmp = [](const Seed& x, const Seed& y) -> bool {
return x.grow > y.grow;
};
sort(seed, seed + n, cmp);
for (int i = 0; i < n; i++) {
pre += seed[i].plant;
temp = pre + seed[i].grow;
res = max(res, temp);
}
return res;
}
};