第216场LeetCode周赛
No 1. 检查两个字符串数组是否相等
给你两个字符串数组 word1 和 word2 。如果两个数组表示的字符串相同,返回 true ;否则,返回 false 。
数组表示的字符串 是由数组中的所有元素 按顺序 连接形成的字符串。
示例 1:
输入:word1 = [“ab”, “c”], word2 = [“a”, “bc”]
输出:true
解释:
word1 表示的字符串为 “ab” + “c” -> “abc”
word2 表示的字符串为 “a” + “bc” -> “abc”
两个字符串相同,返回 true
示例 2:
输入:word1 = [“a”, “cb”], word2 = [“ab”, “c”]
输出:false
提示:
1 <= word1.length, word2.length <= 10^3
1 <= word1[i].length, word2[i].length <= 10^3
1 <= sum(word1[i].length), sum(word2[i].length) <= 10^3
word1[i] 和 word2[i] 由小写字母组成
解析
本题例行签到题,要求我们比较两个字符串数组是否相等,也即比较两个数组中的字符串按顺序连成的整个字符串是否相等。本题数据范围较小,直接暴力求解即可,将两个数组中的字符串分别拼接起来,比较二者是否相等。
bool arrayStringsAreEqual(vector<string>& word1, vector<string>& word2) {
string s1 = "", s2 = "";
for (auto s : word1) s1 += s;
for (auto s : word2) s2 += s;
return s1 == s2;
}
No 2. 具有给定数值的最小字符串
小写字符 的 数值 是它在字母表中的位置(从 1 开始),因此 a 的数值为 1 ,b 的数值为 2 ,c 的数值为 3 ,以此类推。
字符串由若干小写字符组成,字符串的数值 为各字符的数值之和。例如,字符串 “abe” 的数值等于 1 + 2 + 5 = 8 。
给你两个整数 n 和 k 。返回 长度 等于 n 且 数值 等于 k 的 字典序最小 的字符串。
注意,如果字符串 x 在字典排序中位于 y 之前,就认为 x 字典序比 y 小,有以下两种情况:
x 是 y 的一个前缀;
如果 i 是 x[i] != y[i] 的第一个位置,且 x[i] 在字母表中的位置比 y[i] 靠前。
示例 1:
输入:n = 3, k = 27
输出:“aay”
解释:字符串的数值为 1 + 1 + 25 = 27,它是数值满足要求且长度等于 3 字典序最小的字符串。
示例 2:
输入:n = 5, k = 73
输出:“aaszz”
提示:
1 <= n <= 10^5
n <= k <= 26 * n
解析
本题给定了字符以及字符串数值的定义,要求我们按照给定的字符串长度与数值,返回满足条件的字典序最小的字符串。
本题要求字典序最小,那么肯定是尽量以a开头,z结尾,而且只有一个字符允许不是a或z。换言之,需要前面的尽可能小,直到最后再通过z来满足数值。例如n=2,k=30,肯定是选择dz而不是ey或其他,因为字典序是从前向后比较的,所以尽可能使前面的字符小。
不妨设目标字符串满足:
m个a,待定字符,l个z 的形式。
我们记这个待定字符的数值为x(不是字母x,代表未知的待定字符)。如 “aaaaXzzz”。
那么显然有:
m
+
1
+
l
=
n
m+1+l=n
m+1+l=n
m
∗
1
+
x
+
l
∗
26
=
k
m*1+x+l*26=k
m∗1+x+l∗26=k
上两式相减:
x
−
1
+
25
∗
l
=
k
−
n
x-1+25*l=k-n
x−1+25∗l=k−n
显然待定字母的数值x一定在1~26之间,那么x-1一定在0-25之间。因此可以将k-n看做是一个整数乘25再加一个小于25的数,那这两个数一定是:
l
=
(
k
−
n
)
/
25
l=(k-n)/25
l=(k−n)/25
x
−
1
=
(
k
−
n
)
m
o
d
25
x-1 = (k-n)mod 25
x−1=(k−n)mod25
这里可以发现对25取余结果在0-24之间,不含25,那么有没有可能x-1=25呢,显然此时x代表的字母是z,这跟后面加字母z是一样的,所以不用担心。
根据上式,我们求解出l与x,再带回去求出m。然后将m个a,待定字母,l个z,拼接起来即可。
string getSmallestString(int n, int k) {
string ans;
int m = 0, l = 0, x = 0;
int tmp = k - n;
l = tmp / 25;
x = tmp % 25 + 1;
m = n - l - 1;
ans = string(m, 'a');
ans.push_back('a' + x - 1);
ans += string(l, 'z');
return ans;
}
No 3.生成平衡数组的方案数
给你一个整数数组 nums 。你需要选择 恰好 一个下标(下标从 0 开始)并删除对应的元素。请注意剩下元素的下标可能会因为删除操作而发生改变。
比方说,如果 nums = [6,1,7,4,1] ,那么:
选择删除下标 1 ,剩下的数组为 nums = [6,7,4,1] 。
选择删除下标 2 ,剩下的数组为 nums = [6,1,4,1] 。
选择删除下标 4 ,剩下的数组为 nums = [6,1,7,4] 。
如果一个数组满足奇数下标元素的和与偶数下标元素的和相等,该数组就是一个 平衡数组 。
请你返回删除操作后,剩下的数组 nums 是 平衡数组 的 方案数 。
示例 1:
输入:nums = [2,1,6,4]
输出:1
解释:
删除下标 0 :[1,6,4] -> 偶数元素下标为:1 + 4 = 5 。奇数元素下标为:6 。不平衡。
删除下标 1 :[2,6,4] -> 偶数元素下标为:2 + 4 = 6 。奇数元素下标为:6 。平衡。
删除下标 2 :[2,1,4] -> 偶数元素下标为:2 + 4 = 6 。奇数元素下标为:1 。不平衡。
删除下标 3 :[2,1,6] -> 偶数元素下标为:2 + 6 = 8 。奇数元素下标为:1 。不平衡。
只有一种让剩余数组成为平衡数组的方案。
示例 2:
输入:nums = [1,1,1]
输出:3
解释:你可以删除任意元素,剩余数组都是平衡数组。
提示:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^4
解析
本题给定一个数组,我们可以任意删去其中一个,求剩下的数组属于平衡数组的删除方法有多少种。平衡数组是指奇数下标与偶数下标的元素和相等。
这里要注意,删掉一个元素后,它后面的元素下标发生了变化,都减小了1,那么后面元素下标的奇偶性相当于去了反。换言之:
删掉一个元素后的奇数下标元素,等于该元素之前的奇数下标加之后的偶数下表元素;而删掉它后的偶数下标元素,相当于它之前的偶数与之后的奇数。
本题暴力解法当然是尝试删掉每个元素,求删完的数组是否平衡,复杂度 O ( N 2 ) O(N^2) O(N2)。显然这样做涉及到大量重复计算,根据上述分析,我们只要遍历2次,分别得到每个元素的前缀以及后缀奇数和、偶数和,再按照“奇加偶”、“偶加奇”,合成两个"删后奇偶和数组",这两个数组相同位置上的值如果相等,就说明删掉原数组这个位置后数组平衡,记录这样的数量就行了。
生成“每个元素的前缀以及后缀奇数和、偶数和”数组也不必每次遍历,根据前一个点的值求解即可(动规)
一个奇数下标的元素,其前缀奇数和就等于前一个位置的前缀奇数和,因为自己是奇数下标但不计入前缀,所以没有变化;而前缀偶数和等于前一个位置前缀偶数和加上前一个位置元素数值,因为前一个数是偶数下标,他没算自己,现在要把他加上。偶数下标元素也是类似的。
后缀和也是同理,只不过变成考虑后一个位置元素了。
C++代码如下:
int waysToMakeFair(vector<int>& nums) {
int n = nums.size();
int ans = 0;
vector<int>preOdd(n, 0), preEven(n, 0), postOdd(n, 0), postEven(n, 0);
for (int i = 1; i < n; ++i) {
if (i %2== 0) {
preEven[i] = preEven[i - 1];
preOdd[i] = preOdd[i - 1] + nums[i - 1];
}
else {
preOdd[i] = preOdd[i - 1];
preEven[i] = preEven[i - 1] + nums[i - 1];
}
}
for (int i = n - 2; i >= 0; --i) {
if (i % 2 == 0) {
postEven[i] = postEven[i + 1];
postOdd[i] = postOdd[i + 1] + nums[i + 1];
}
else {
postOdd[i] = postOdd[i + 1];
postEven[i] = postEven[i + 1] + nums[i + 1];
}
}
for (int i = 0; i < n; ++i) {
int tmp1 = preOdd[i] + postEven[i];
int tmp2 = preEven[i] + postOdd[i];
if (tmp1 == tmp2)++ans;
}
return ans;
}
这里的preOdd, preEven, postOdd, postEven就是每个元素前后的奇数下标与偶数下标和。
遍历每个下标,判断“奇加偶”、“偶加奇”是否相等,相等则结果加一。
No 4. 完成所有任务的最少初始能量
给你一个任务数组 tasks ,其中 tasks[i] = [actuali, minimumi] :
actuali 是完成第 i 个任务 需要耗费 的实际能量。
minimumi 是开始第 i 个任务前需要达到的最低能量。
比方说,如果任务为 [10, 12] 且你当前的能量为 11 ,那么你不能开始这个任务。如果你当前的能量为 13 ,你可以完成这个任务,且完成它后剩余能量为 3 。
你可以按照 任意顺序 完成任务。
请你返回完成所有任务的 最少 初始能量。
示例 1:
输入:tasks = [[1,2],[2,4],[4,8]]
输出:8
解释:
一开始有 8 能量,我们按照如下顺序完成任务:
- 完成第 3 个任务,剩余能量为 8 - 4 = 4 。
- 完成第 2 个任务,剩余能量为 4 - 2 = 2 。
- 完成第 1 个任务,剩余能量为 2 - 1 = 1 。
注意到尽管我们有能量剩余,但是如果一开始只有 7 能量是不能完成所有任务的,因为我们无法开始第 3 个任务。
示例 2:
输入:tasks = [[1,3],[2,4],[10,11],[10,12],[8,9]]
输出:32
解释:
一开始有 32 能量,我们按照如下顺序完成任务:
- 完成第 1 个任务,剩余能量为 32 - 1 = 31 。
- 完成第 2 个任务,剩余能量为 31 - 2 = 29 。
- 完成第 3 个任务,剩余能量为 29 - 10 = 19 。
- 完成第 4 个任务,剩余能量为 19 - 10 = 9 。
- 完成第 5 个任务,剩余能量为 9 - 8 = 1 。
提示:
1 <= tasks.length <= 10^5
1 <= actuali <= minimumi <= 10^4
解析
本题给了一系列的任务,每个任务有两个参数,分别是消耗能量a与到达能量m,也就是执行这个任务前至少要有m的能量,执行任务消耗a。我们可以任意顺序完成所有任务,求完成所有任务所需的最小初始能量。这个初始能量,要保证执行每个任务前都满足其最小到达能量,同时能够执行完所有的任务。
本题直觉上讲,应该是存在一个排序机制,按照每个任务a和m执行,这样是最小的。
我当时做的时候,是这么想的,但是一般常用的按a或者按m排都不对,通过对示例找了规律,发现是按二者差值排序的。也就是应该先进行m-a大的任务,所需的初始能量比较小。
static bool comp(pair<int, int>&p1, pair<int, int>&p2) {
int t1 = p1.second - p1.first, t2 = p2.second - p2.first;
return t1 > t2 || (t1 == t2 && p1.second > p2.second) || (t1 == t2 && p1.second == p2.second&&p1.first < p2.first);
}
int minimumEffort(vector<vector<int>>& tasks) {
vector<pair<int, int>>vp;
int n = tasks.size(), ans = 0;
for (auto t : tasks) {
vp.push_back({ t[0],t[1] });
}
sort(vp.begin(), vp.end(), comp);
for (int i = n - 1; i >= 0; --i) {
ans = max(ans + vp[i].first, vp[i].second);
}
return ans;
}
如上述代码,自定义了排序规则,按每个元素的二者插值排序。(虽然我这里考虑差值相等时,优先m较大、a较小的,但是根据大佬们论证似乎不需要、无所谓)
零神大佬对此做了证明,参考这里
我本人当时只是凭直觉这么做的。
大致原理如下:
加入按照先0,1,2…n的顺序去做任务,初始值value满足:
v
a
l
u
e
>
=
m
0
value>=m_0
value>=m0
v
a
l
u
e
−
a
0
>
=
m
1
value-a_0>=m_1
value−a0>=m1
v
a
l
u
e
−
a
0
−
a
1
>
=
m
2
value-a_0-a_1>=m_2
value−a0−a1>=m2
v
a
l
u
e
−
s
u
m
(
a
0
,
a
1
,
.
.
.
a
n
−
1
)
>
=
m
n
value-sum(a_0,a_1,...a_{n-1})>=m_n
value−sum(a0,a1,...an−1)>=mn
v
a
l
u
e
>
=
s
u
m
(
a
0
,
a
1
,
.
.
.
a
n
−
1
)
+
m
n
value>= sum(a_0,a_1,...a_{n-1})+m_n
value>=sum(a0,a1,...an−1)+mn
换言之,最小初始能量大于等于最后一个任务的到达能量加上其他任务的消耗能量。那计入我们调换顺序,使得第i个任务为最后任务,那么:
v
a
l
u
e
2
>
=
s
u
m
(
a
0
,
a
1
,
.
.
a
i
−
1
,
a
i
+
1
,
.
.
.
a
n
−
1
,
a
n
)
+
m
i
value2>= sum(a_0,a_1,..a_{i-1},a_{i+1},...a_{n-1},a_n)+m_i
value2>=sum(a0,a1,..ai−1,ai+1,...an−1,an)+mi
要想让value2更小,需要满足:
v
a
l
u
e
−
v
a
l
u
e
2
>
0
value-value2>0
value−value2>0
约去相同的值得:
a
i
+
m
n
>
a
n
+
m
i
a_i+m_n>a_n+m_i
ai+mn>an+mi
m
n
−
a
n
>
m
i
−
a
i
m_n-a_n>m_i-a_i
mn−an>mi−ai
也就是m-a大的任务先做。