本次周赛题目比较简单,比赛链接位于这里
No.1 检查是否每一行每一列都包含全部整数
对一个大小为 n x n 的矩阵而言,如果其每一行和每一列都包含从 1 到 n 的 全部 整数(含 1 和 n),则认为该矩阵是一个 有效 矩阵。
给你一个大小为 n x n 的整数矩阵 matrix ,请你判断矩阵是否为一个有效矩阵:如果是,返回 true ;否则,返回 false 。
示例 1:

输入:matrix = [[1,2,3],[3,1,2],[2,3,1]]
输出:true
解释:在此例中,n = 3 ,每一行和每一列都包含数字 1、2、3 。
因此,返回 true 。
解析
本题给定一个方阵,要求我们判断是否每一行、每一列都包含1-n全部数字。
这道题不需要什么技巧,按照题设判断即可。先逐行遍历,再逐列遍历,依次判断是否包含全部的数字即可。判断的方式,可以通过数组记录每个数字出现个数,或者通过集合记录出现的数字统计数量。
C++代码如下:
//No 1
bool checkValid(vector<vector<int>>& matrix) {
int n = matrix.size();
for (int i = 0; i < n; ++i) {
vector<int>m(n, 0);
for (int j = 0; j < n; ++j) {
if (m[matrix[i][j] - 1] > 0) return false;
m[matrix[i][j] - 1]++;
}
for (int j = 0; j < n; ++j) {
if (m[j] == 0) return false;
}
}
for (int i = 0; i < n; ++i) {
vector<int>m(n, 0);
for (int j = 0; j < n; ++j) {
if (m[matrix[j][i] - 1] > 0) return false;
m[matrix[j][i] - 1]++;
}
for (int j = 0; j < n; ++j) {
if (m[j] == 0) return false;
}
}
return true;
}
No.2 最少交换次数来组合所有的 1 II
交换 定义为选中一个数组中的两个 互不相同 的位置并交换二者的值。
环形 数组是一个数组,可以认为 第一个 元素和 最后一个 元素 相邻 。
给你一个 二进制环形 数组 nums ,返回在 任意位置 将数组中的所有 1 聚集在一起需要的最少交换次数。
示例 1:
输入:nums = [0,1,0,1,1,0,0]
输出:1
解释:这里列出一些能够将所有 1 聚集在一起的方案:
[0,0,1,1,1,0,0] 交换 1 次。
[0,1,1,1,0,0,0] 交换 1 次。
[1,1,0,0,0,0,1] 交换 2 次(利用数组的环形特性)。
无法在交换 0 次的情况下将数组中的所有 1 聚集在一起。
因此,需要的最少交换次数为 1 。
解析
本题给定一个仅有1和0构成的环形数组,首尾是相连的。我们可以交换任意2个元素,求把所有的1聚集在一起(相邻)需要的最小交换次数。
通过一次遍历我们可以知道数组一共有多少个1,显然,交换的最终目标就是得到这个长度的全1部分。因此我们可以通过滑动窗口,定义一个长度等于1的个数的窗口,这个窗口在整个数组上滑动,在窗口内实现全都是1.为了使得窗口内全部为1,就需要进行交换,将0换走,因次交换次数就是窗口内的0的个数。
考虑到以1开头的滑窗,其交换次数不多于0开头的(小于等于),一次我们可以简单一些,只讨论1开头的窗口位置,统计窗口内的0的个数。
但是,如果我们对于每个滑窗位置,都遍历其全部元素统计0的数量,复杂度会达到
O
(
N
2
)
O(N^2)
O(N2)超时。因此需要一些优化。
主要有两种方式:
- 我们只遍历1开头的窗口,由于窗口不是连续滑动,无法直接动态规划,考虑到数组只有0和1,求和等价于求1的个数,我们遍历一次计算前缀和,通过前缀和相减计算1的个数,那么总数减去1的个数就是0的个数。
- 遍历所有的窗口,此时窗口连续滑动,0的个数等于前一个位置的窗口0个数减去滑走的,加上新加入的,标准的动态规划。
这里将思路1的代码放在下面,思路2实现起来也比较容易。两个思路基本没什么大的区别。
//No 2
int minSwaps(vector<int>& nums) {
int num1 = 0,n=nums.size();
vector<int>sumIn(n + 1, 0);
for (int i = 0; i < n; ++i) {
sumIn[i + 1] = sumIn[i] + nums[i];
num1 += nums[i];
}
if (num1 == 0) return 0;
int ans = n;
for (int i = 0; i < n; ++i) {
if (nums[i] == 1) {
int t = i + num1 - 1,num0=n;
if (t < n) {
num0 = num1-(sumIn[t + 1] - sumIn[i]);
}
else {
num0 = num1-(sumIn[n] - sumIn[i] + sumIn[t - n + 1]);
}
ans = min(ans, num0);
}
}
return ans;
}
No.3 统计追加字母可以获得的单词数
给你两个下标从 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 = [“ant”,“act”,“tack”], targetWords = [“tack”,“act”,“acti”]
输出:2
解释:
- 为了形成 targetWords[0] = “tack” ,可以选用 startWords[1] = “act” ,追加字母 ‘k’ ,并重排 “actk” 为 “tack” 。
- startWords 中不存在可以用于获得 targetWords[1] = “act” 的字符串。
注意 “act” 确实存在于 startWords ,但是 必须 在重排前给这个字符串追加一个字母。 - 为了形成 targetWords[2] = “acti” ,可以选用 startWords[1] = “act” ,追加字母 ‘i’ ,并重排 “acti” 为 “acti” 自身。
1
<
=
s
t
a
r
t
W
o
r
d
s
.
l
e
n
g
t
h
,
t
a
r
g
e
t
W
o
r
d
s
.
l
e
n
g
t
h
<
=
5
∗
1
0
4
1 <= startWords.length, targetWords.length <= 5 * 10^4
1<=startWords.length,targetWords.length<=5∗104
1 <= startWords[i].length, targetWords[j].length <= 26
startWords 和 targetWords 中的每个字符串都仅由小写英文字母组成
在 startWords 或 targetWords 的任一字符串中,每个字母至多出现一次
解析
本题给定2组字符串 startWords 和 targetWords ,以及一种操作:即对一个字符串添加一个它本身没有的字母并重排,求targetWords 数组的字符串中有多少个能够通过startWords 中的某个字符串通过一次上述操作得到。
根据题意和提示我们发现,重排指的是任意排列,就是说只要组成的字母一样就视为能够重排得到;而操作第一步是添加一个原先不存在的字母,又知字符串数组的每个字符串中,每个字母至多出现一次,那么这题就可以转化为:targetWords中的字符串st,是否startWords存在一个字符串ss,st恰好比ss多一个字母,除了多出来的字母,剩下的组成一致(顺序无所谓)。由于字符串组成只有26个字母,而数组很长,因此,我们应当把遍历操作放在字母集,而查询放在数组上,因此修改为:对于targetWords中的字符串st,去掉其中一个字母,得到ss,这个ss是否在startWords中。
由于字符串顺序无所谓,为了进一步简化,我们将字母进行编码,每个字符对应一个二进制位,包含该字母这一位就是1,反之是0,这样通过一个32位整型数字,就能够表示一个原来的字符串。
那么单个字母就是只有1位是1的整型,即2的幂。我们将这些单个字母编码构成的整数放在数组中。将startWords的字符串编码后放在集合中,便于O(1)的查询。
接下来对于targetWords中的每个字符串,首先进行编码,然后对于其包含的每个字母,依次剔除,查询剔除后的编码是否在startWords编码集合中,只要找到一个满足的字母和对应的字符串,就表明能够通过一次操作得到;每个字母都尝试过均找不到,就说明这个字符串无法通过操作得到。记录能够得到的字符串综述返回即可。
这里要注意剔除字母一定要是字符串本身就存在的,对于编码后的整数,就是二者做按位与运算,结果等于单个字母的编码,否则会出错。例如编码4代表字母’c’,2代表 ‘b’,4-2=2,但显然’c’不能通过 'b’得到。
C++代码如下:
vector<int>alpha;
for (int i = 0; i < 26; ++i) {
alpha.push_back((1 << i));
}
unordered_set<int>start;
for (auto s : startWords) {
int tmp = 0;
for (auto c : s) {
tmp += (1 << (c - 'a'));
}
start.insert(tmp);
}
int count = 0;
for (auto s : targetWords) {
int tmp = 0;
for (auto c : s) {
tmp += (1 << (c - 'a'));
}
for (auto a : alpha) {
if ((tmp & a) == a && start.count(tmp ^ a)) {
++count;
break;
}
}
}
return count;
No.4 全部开花的最早一天
你有 n 枚花的种子。每枚种子必须先种下,才能开始生长、开花。播种需要时间,种子的生长也是如此。给你两个下标从 0 开始的整数数组 plantTime 和 growTime ,每个数组的长度都是 n :
plantTime[i] 是 播种 第 i 枚种子所需的 完整天数 。每天,你只能为播种某一枚种子而劳作。无须 连续几天都在种同一枚种子,但是种子播种必须在你工作的天数达到 plantTime[i] 之后才算完成。
growTime[i] 是第 i 枚种子完全种下后生长所需的 完整天数 。在它生长的最后一天 之后 ,将会开花并且永远 绽放 。
从第 0 开始,你可以按 任意 顺序播种种子。
返回所有种子都开花的 最早 一天是第几天。
示例 1:

输入:plantTime = [1,4,3], growTime = [2,3,1]
输出:9
解释:灰色的花盆表示播种的日子,彩色的花盆表示生长的日子,花朵表示开花的日子。
一种最优方案是:
第 0 天,播种第 0 枚种子,种子生长 2 整天。并在第 3 天开花。
第 1、2、3、4 天,播种第 1 枚种子。种子生长 3 整天,并在第 8 天开花。
第 5、6、7 天,播种第 2 枚种子。种子生长 1 整天,并在第 9 天开花。
因此,在第 9 天,所有种子都开花。
解析
本题给定了若干花,我们需要先播种,消耗一定的时间,播种成功后的话会自己生长,生长结束会开花。播种过程是“串行”的,同一时间只能有一朵花在播种,但生长过程可以同时进行。我们可以自定义播种的顺序,求最短的全部开花时间。
本题作为最后一题,加上题目的描述,本以为是一道很难的题,但实际上可以通过贪心解决。一种朴素的思想是,既然生长过程可以并行,那我们应该先把生长时间较长的花种下去,这样它自己慢慢生长,与此同时我们种植其他花,是不是比较快呢?
我们以两朵花为例,花1:种植时间,生长时间=a, b, 花2:种植时间,生长时间=c, d,不妨设b>d
如果先1后2,则总时间是
m
a
x
(
a
+
c
+
d
,
a
+
b
)
max(a+c+d,a+b)
max(a+c+d,a+b);反之则是
m
a
x
(
c
+
a
+
b
,
c
+
d
)
max(c+a+b,c+d)
max(c+a+b,c+d),由于b>d,则
c
+
a
+
b
>
c
+
b
>
c
+
d
c+a+b>c+b>c+d
c+a+b>c+b>c+d,所以先2后1的时间一定是c+a+b。而c+a+b一定大于a+c+d与a+b,所以无论ac如何,无论具体数字,先1后2更快,也就是先种生长时间长的更快。
综上,我们按照生长时间从大到小对植物排序(种植时间无所谓),然后依次种植即可
C++代码如下:
int earliestFullBloom(vector<int>& plantTime, vector<int>& growTime) {
vector<pair<int, int>>flowers;
int n = plantTime.size();
for (int i = 0; i < n; ++i) {
flowers.push_back({ plantTime[i],growTime[i] });
}
sort(flowers.begin(), flowers.end(), [](pair<int, int>& p1, pair<int, int>p2) {return p1.second > p2.second || (p1.second == p2.second) && (p1.first < p2.first); });
int day = 0,s=0;
for (int i = 0; i < n; ++i) {
int dayi = flowers[i].first + flowers[i].second + s;
s += flowers[i].first;
day = max(day, dayi);
}
return day;
}

296

被折叠的 条评论
为什么被折叠?



