LeetCode 第273场周赛 解析
这次周赛应该是2021年度的最后一场周赛了,下一场就到2022年了。
这篇博客对本次周赛的题目做一个解析。题目链接位于这里
No 1.反转两次的数字
反转 一个整数意味着倒置它的所有位。
例如,反转 2021 得到 1202 。反转 12300 得到 321 ,不保留前导零 。
给你一个整数 num ,反转 num 得到 reversed1 ,接着反转 reversed1 得到 reversed2 。如果 reversed2 等于 num ,返回 true ;否则,返回 false 。
示例 1:
输入:num = 526
输出:true
解释:反转 num 得到 625 ,接着反转 625 得到 526 ,等于 num 。
示例 2:
输入:num = 1800
输出:false
解释:反转 num 得到 81 ,接着反转 81 得到 18 ,不等于 num 。
示例 3:
输入:num = 0
输出:true
解释:反转 num 得到 0 ,接着反转 0 得到 0 ,等于 num 。
提示:
0 < = n u m < = 1 0 6 0 <= num <= 10^6 0<=num<=106
解析
本题是对一个输入的整数,判断对它按数位反转两次后是否跟原数一致。实际上如果只是单纯的翻转,也即按数位翻过来,反转两次肯定和原数一样,但这里的问题是,每次翻转要去除前导零。比如50反转就是5而不是05.
本题最简单的思路就是模拟这一过程,如:
//No 1
bool isSameAfterReversals(int num) {
string n1 = to_string(num);
int i = 0;
while (i < n1.size() && n1[i] == '0')++i;
if (i == n1.size())--i;
n1 = n1.substr(i);
string n2 = n1;
reverse(n2.begin(), n2.end());
i = 0;
while (i < n2.size() && n2[i] == '0')++i;
if (i == n2.size())--i;
n2 = n2.substr(i);
string n3 = n2;
reverse(n3.begin(), n3.end());
return n1 == n3;
}
按照题意先转为字符串,记为原数。然后反转字符串并去除前导零,进行2次操作,记为变换后的数,比较和原数是否相等即可。
但思考一下我们就会发现,其实不用这样做。我们知道造成反转前后的差异的就是两次反转带来的先导0. 这里明确两点:
- 2次反转后的“首”=1次反转后的“尾”=反转前的“首”,反转前的原数是个正常数字不可能由先导0,所以第二次反转根本不存在先导0去除的问题;
- 2次反转后的“尾”=1次反转后的“首”=反转前的“尾”,反转前的数如果末尾有0,第一次反转会跑到前面而被去掉,2次反转后末尾的0就不见了,跟原数不相等。
因此,造成反转前后不相等的情况只能是上述的情况2,就是原数末尾有0,即它是10的倍数。有一个特例的0,因为只有一位所以不能被去除。
所以,如果一个数不等于0而又是10的整数倍,2次反转必然不相等;反之,如果是0或者不是10的倍数,那反转前后不变。
因此本题只需要:
bool isSameAfterReversals(int num) {
return (num==0)||(num%10!=0);
}
即可。
No 2.执行所有后缀指令
现有一个 n x n 大小的网格,左上角单元格坐标 (0, 0) ,右下角单元格坐标 (n - 1, n - 1) 。给你整数 n 和一个整数数组 startPos ,其中 startPos = [startrow, startcol] 表示机器人最开始在坐标为 (startrow, startcol) 的单元格上。
另给你一个长度为 m 、下标从 0 开始的字符串 s ,其中 s[i] 是对机器人的第 i 条指令:‘L’(向左移动),‘R’(向右移动),‘U’(向上移动)和 ‘D’(向下移动)。
机器人可以从 s 中的任一第 i 条指令开始执行。它将会逐条执行指令直到 s 的末尾,但在满足下述条件之一时,机器人将会停止:
下一条指令将会导致机器人移动到网格外。
没有指令可以执行。
返回一个长度为 m 的数组 answer ,其中 answer[i] 是机器人从第 i 条指令 开始 ,可以执行的 指令数目 。
示例 1:
输入:n = 3, startPos = [0,1], s = “RRDDLU”
输出:[1,5,4,3,1,0]
解释:机器人从 startPos 出发,并从第 i 条指令开始执行:
- 0: “RRDDLU” 在移动到网格外之前,只能执行一条 “R” 指令。
- 1: “RDDLU” 可以执行全部五条指令,机器人仍在网格内,最终到达 (0, 0) 。
- 2: “DDLU” 可以执行全部四条指令,机器人仍在网格内,最终到达 (0, 0) 。
- 3: “DLU” 可以执行全部三条指令,机器人仍在网格内,最终到达 (0, 0) 。
- 4: “LU” 在移动到网格外之前,只能执行一条 “L” 指令。
- 5: “U” 如果向上移动,将会移动到网格外。
解析
本题给定了一个二维网格和初始位置,以及一个长度m的字符串构成的指令。我们需要对指令每个位置开始,向后遍历,每个字符代表一个机器人的移动,从起始位置和起始指令开始,逐个字符执行,直到所有指令执行完或机器人会被移出网格,记录能移动的指令数量。返回从 0 0 0到 m − 1 m-1 m−1每个下标作为起始指令能执行的数量。
本题数据范围不大,直接模拟过程即可。对于m条质量,每个作为开始向后遍历,时间复杂度为 O ( N 2 ) O(N^2) O(N2)。
每条指令执行前,先判断执行当前指令会不会导致机器人出界,如果会,就直接退出,表明无法执行当前以及后续指令了。否则,就根据指令内容,改变机器人位置,增加计数器。
C++代码如下:
//No 2
int numE(int n, int sr, int sc, string e, int si) {
int count = 0;
int r = sr, c = sc,m=e.size();
while (si < m) {
if ((e[si] == 'L' && sc == 0) || (e[si] == 'R' && sc == n - 1) || (e[si] == 'U' && sr == 0) || (e[si] == 'D' && sr == n - 1)) break;
if (e[si] == 'L')--sc;
else if (e[si] == 'R')++sc;
else if (e[si] == 'U')--sr;
else ++sr;
++count;
++si;
}
return count;
}
vector<int> executeInstructions(int n, vector<int>& startPos, string s) {
int m = s.size();
vector<int>ans(m, 0);
for (int i = 0; i < m; ++i) {
ans[i] = numE(n, startPos[0], startPos[1], s, i);
}
return ans;
}
No 3. 相同元素的间隔之和
给你一个下标从 0 开始、由 n 个整数组成的数组 arr 。
arr 中两个元素的 间隔 定义为它们下标之间的 绝对差 。更正式地,arr[i] 和 arr[j] 之间的间隔是 |i - j| 。
返回一个长度为 n 的数组 intervals ,其中 intervals[i] 是 arr[i] 和 arr 中每个相同元素(与 arr[i] 的值相同)的 间隔之和 。
注意:|x| 是 x 的绝对值。
示例 1:
输入:arr = [2,1,3,1,2,3,3]
输出:[4,2,7,2,4,4,5]
解释:
- 下标 0 :另一个 2 在下标 4 ,|0 - 4| = 4
- 下标 1 :另一个 1 在下标 3 ,|1 - 3| = 2
- 下标 2 :另两个 3 在下标 5 和 6 ,|2 - 5| + |2 - 6| = 7
- 下标 3 :另一个 1 在下标 1 ,|3 - 1| = 2
- 下标 4 :另一个 2 在下标 0 ,|4 - 0| = 4
- 下标 5 :另两个 3 在下标 2 和 6 ,|5 - 2| + |5 - 6| = 4
- 下标 6 :另两个 3 在下标 2 和 5 ,|6 - 2| + |6 - 5| = 5
解析
本题要求我们对给定的数组,求每个位置上的元素与整个数组其他与它值一样的元素的间隔(即下标差的绝对值)之和。
显然,每个下标的间隔之和都只跟相同元素有关,所以先通过哈希表将相同元素的下标分离出来,得到多个下表数组
I
d
Id
Id。每个下表数组
I
d
Id
Id都记录了一个值在原数组出现的所有位置,然后对于每个下表数组,分别求每个位置的间隔之和即可。
例如对上述实例,共有1,2,3三个不同元素,下表数组分别是
[
1
,
3
]
,
[
0
,
4
]
,
[
2
,
5
,
6
]
[1,3],[0,4],[2,5,6]
[1,3],[0,4],[2,5,6]对下表数组[1,3]两个位置间隔都是
∣
1
−
3
∣
=
2
|1-3|=2
∣1−3∣=2所以将计算后的结果2填回到最终返回数组对应的下标1,3位置即可.
但这里要注意,数组长度也就是下标的数量最大是 1 0 5 10^5 105。所以对于每个下表数组 I d Id Id,不能用 O ( N 2 ) O(N^2) O(N2)的暴力方法,也即对每个 I d Id Id的位置,都遍历一遍 I d Id Id计算差的绝对值之和。否则会超时。
这里可以对
I
d
Id
Id排个序,当然,如果是按照数组顺序生成的元素下标数组,本身就是有序的,从小到大。
那么对于
I
d
Id
Id中的第i个位置,我们考虑和前一个位置i-1的关系。求第i个元素
I
d
[
i
]
Id[i]
Id[i]的间隔和,记作
s
u
m
I
n
t
e
r
[
i
]
sumInter[i]
sumInter[i],可以将数组分为三段:
- 对 于 j < i − 1 : s u m I n t e r [ i ] 1 = ∑ j = 0 i − 2 I d [ i ] − I d [ j ] = ∑ j = 0 i − 2 ( I d [ i − 1 ] − I d [ j ] ) + ( i − 1 ) ∗ ( I d [ i ] − I d [ i − 1 ] ) 对于 {j < i-1}: sumInter[i]_1=\sum_{j=0}^{i-2}{Id[i]-Id[j]}=\sum_{j=0}^{i-2}{(Id[i-1]-Id[j])}+(i-1)*(Id[i]-Id[i-1]) 对于j<i−1:sumInter[i]1=∑j=0i−2Id[i]−Id[j]=∑j=0i−2(Id[i−1]−Id[j])+(i−1)∗(Id[i]−Id[i−1]) 这部分的数均小于 I d [ i ] 和 I d [ i − 1 ] Id[i]和Id[i-1] Id[i]和Id[i−1],因此绝对差是 I d [ i ] Id[i] Id[i]减去这些数,相比于 s u m I n t e r [ i − 1 ] 1 sumInter[i-1]_1 sumInter[i−1]1,减数从 I d [ i − 1 ] Id[i-1] Id[i−1]增加到了 I d [ i ] Id[i] Id[i],每一个差都增加了一个二者的差值。因此 s u m I n t e r [ i ] 1 = s u m I n t e r [ i − 1 ] 1 + ( i − 1 ) ∗ ( I d [ i ] − I d [ i − 1 ] ) sumInter[i]_1=sumInter[i-1]_1+(i-1)*(Id[i]-Id[i-1]) sumInter[i]1=sumInter[i−1]1+(i−1)∗(Id[i]−Id[i−1])
- 对 于 j = i − 1 或 i : s u m I n t e r [ i ] 2 = a b s ( I d [ i ] − I d [ i − 1 ] ) + a b s ( I d [ i ] − I d [ i ] ) = I d [ i ] − I d [ i − 1 ] ; s u m I n t e r [ i − 1 ] 2 = a b s ( I d [ i − 1 ] − I d [ i − 1 ] ) + a b s ( I d [ i − 1 ] − I d [ i ] ) = I d [ i ] − I d [ i − 1 ] 对于j=i-1或i: sumInter[i]_2=abs(Id[i]-Id[i-1])+abs(Id[i]-Id[i]) = Id[i]-Id[i-1]; sumInter[i-1]_2=abs(Id[i-1]-Id[i-1])+abs(Id[i-1]-Id[i]) = Id[i]-Id[i-1] 对于j=i−1或i:sumInter[i]2=abs(Id[i]−Id[i−1])+abs(Id[i]−Id[i])=Id[i]−Id[i−1];sumInter[i−1]2=abs(Id[i−1]−Id[i−1])+abs(Id[i−1]−Id[i])=Id[i]−Id[i−1],也就是说对于j等于i-1或i, s u m I n t e r [ i ] 2 = s u m I n t e r [ i − 1 ] 2 sumInter[i]_2= sumInter[i-1]_2 sumInter[i]2=sumInter[i−1]2
-
对
于
j
>
i
:
s
u
m
I
n
t
e
r
[
i
]
3
=
∑
j
=
i
+
1
m
−
1
I
d
[
j
]
−
I
d
[
i
]
=
∑
j
=
i
+
1
m
−
1
(
I
d
[
j
]
−
I
d
[
i
−
1
]
)
−
(
m
−
i
−
1
)
∗
(
I
d
[
i
]
−
I
d
[
i
−
1
]
)
对于j>i: sumInter[i]_3=\sum_{j=i+1}^{m-1}{Id[j]-Id[i]}=\sum_{j=i+1}^{m-1}{(Id[j]-Id[i-1])}-(m-i-1)*(Id[i]-Id[i-1])
对于j>i:sumInter[i]3=∑j=i+1m−1Id[j]−Id[i]=∑j=i+1m−1(Id[j]−Id[i−1])−(m−i−1)∗(Id[i]−Id[i−1]) 这些数大于
I
d
[
i
]
和
I
d
[
i
−
1
]
Id[i]和Id[i-1]
Id[i]和Id[i−1],绝对差是这些数减去
I
d
[
i
]
Id[i]
Id[i],相比于
s
u
m
I
n
t
e
r
[
i
−
1
]
3
sumInter[i-1]_3
sumInter[i−1]3,被减数从
I
d
[
i
−
1
]
Id[i-1]
Id[i−1]增加到了
I
d
[
i
]
Id[i]
Id[i],每一个差都减小了了一个二者的差值。因此
s
u
m
I
n
t
e
r
[
i
]
3
=
s
u
m
I
n
t
e
r
[
i
−
1
]
3
+
(
m
−
i
−
1
)
∗
(
I
d
[
i
]
−
I
d
[
i
−
1
]
)
sumInter[i]_3=sumInter[i-1]_3+(m-i-1)*(Id[i]-Id[i-1])
sumInter[i]3=sumInter[i−1]3+(m−i−1)∗(Id[i]−Id[i−1])
综上:
s u m I n t e r [ i ] = s u m I n t e r [ i ] 1 + s u m I n t e r [ i ] 2 + s u m I n t e r [ i ] 3 = s u m I n t e r [ i − 1 ] 1 + ( i − 1 ) ∗ ( I d [ i ] − I d [ i − 1 ] ) + s u m I n t e r [ i − 1 ] 2 + s u m I n t e r [ i − 1 ] 3 + ( m − i − 1 ) ∗ ( I d [ i ] − I d [ i − 1 ] ) = s u m I n t e r [ i − 1 ] + [ ( i − 1 ) − ( m − i − 1 ) ] ∗ ( I d [ i ] − I d [ i − 1 ] ) sumInter[i]= sumInter[i]_1+ sumInter[i]_2+ sumInter[i]_3= sumInter[i-1]_1+(i-1)*(Id[i]-Id[i-1])+sumInter[i-1]_2+sumInter[i-1]_3+(m-i-1)*(Id[i]-Id[i-1])= sumInter[i-1]+[(i-1)-(m-i-1)]*(Id[i]-Id[i-1]) sumInter[i]=sumInter[i]1+sumInter[i]2+sumInter[i]3=sumInter[i−1]1+(i−1)∗(Id[i]−Id[i−1])+sumInter[i−1]2+sumInter[i−1]3+(m−i−1)∗(Id[i]−Id[i−1])=sumInter[i−1]+[(i−1)−(m−i−1)]∗(Id[i]−Id[i−1])
得到了同一个值的坐标数组,在增序下的递推公式。 s u m I n t e r [ i ] = s u m I n t e r [ i − 1 ] + [ ( i − 1 ) − ( m − i − 1 ) ] ∗ ( I d [ i ] − I d [ i − 1 ] ) sumInter[i]= sumInter[i-1]+[(i-1)-(m-i-1)]*(Id[i]-Id[i-1]) sumInter[i]=sumInter[i−1]+[(i−1)−(m−i−1)]∗(Id[i]−Id[i−1])
每个间隔和是前一个坐标间隔和的基础上,变动二者差值的若干倍,变动的倍数取决于前一个坐标之前和当前坐标之后各自有多少坐标。
这样就可以在 O ( N ) O(N) O(N)的复杂度计算出一个值对应的一组下标的间隔和,遍历所有值的下表数组,就得到了所求的全部数据。
C++代码如下:
//No 3
vector<long long> getDistances(vector<int>& arr) {
unordered_map<int, vector<int>>ids;
int n = arr.size();
for (int i = 0; i < n; ++i) {
ids[arr[i]].push_back(i);
}
vector<long long>ans(n, 0);
for (auto id : ids) {
vector<int>v = id.second;
long long sumall = 0;
int m = v.size();
for (int i = 0; i < m; ++i) {
sumall += (long long)v[i];
}
long long t = sumall - m * (long long)v[0];
ans[v[0]] = t;
for (int i = 1; i < m; ++i) {
int pos = v[i];
long long tmp = t + (long long)(v[i] - v[i-1]) * ((i-1)-(m - i - 1));
ans[pos] = tmp;
t = tmp;
}
}
return ans;
}
No 4. 还原原数组
Alice 有一个下标从 0 开始的数组 arr ,由 n 个正整数组成。她会选择一个任意的 正整数 k 并按下述方式创建两个下标从 0 开始的新整数数组 lower 和 higher :
对每个满足 0 <= i < n 的下标 i ,lower[i] = arr[i] - k
对每个满足 0 <= i < n 的下标 i ,higher[i] = arr[i] + k
不幸地是,Alice 丢失了全部三个数组。但是,她记住了在数组 lower 和 higher 中出现的整数,但不知道每个整数属于哪个数组。请你帮助 Alice 还原原数组。
给你一个由 2n 个整数组成的整数数组 nums ,其中 恰好 n 个整数出现在 lower ,剩下的出现在 higher ,还原并返回 原数组 arr 。如果出现答案不唯一的情况,返回 任一 有效数组。
注意:生成的测试用例保证存在 至少一个 有效数组 arr 。
示例 1:
输入:nums = [2,10,6,4,8,12]
输出:[3,7,11]
解释:
如果 arr = [3,7,11] 且 k = 1 ,那么 lower = [2,6,10] 且 higher = [4,8,12] 。
组合 lower 和 higher 得到 [2,6,10,4,8,12] ,这是 nums 的一个排列。
另一个有效的数组是 arr = [5,7,9] 且 k = 3 。在这种情况下,lower = [2,4,6] 且 higher = [8,10,12] 。
提示:
2
∗
n
=
=
n
u
m
s
.
l
e
n
g
t
h
2 * n == nums.length
2∗n==nums.length
1
<
=
n
<
=
1000
1 <= n <= 1000
1<=n<=1000
1
<
=
n
u
m
s
[
i
]
<
=
1
0
9
1 <= nums[i] <= 10^9
1<=nums[i]<=109
生成的测试用例保证存在 至少一个 有效数组 arr
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/recover-the-original-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解析
本题题意简述为,有一个长度为n的正整数数组arr,以及一个正整数k,对其中每个元素都加k,减k,得到了2n个数。现在已知这2n个数,即输入变量nums,求一个可能的原数组。
可能的原数组,即找到一个k,使得数组2n个数能够分为两组,每组n个数,每组对应的数之间差2k,k是正整数。本题可以通过适当的枚举方式找到一个解。
实际上恢复原数组的核心是这个正整数k值,对于每个k值我们可以从小到大尝试恢复原数组,也就是看是不是存在两两对应差2k的情况,如果能找出来就得到了问题的解,否则就查看下一个k。
由于nums中元素值域很大,不可能从这个角度枚举k值。我们可以换一个思路。
首先不妨设原数组n个元素为
{
a
0
,
a
1
,
.
.
.
,
a
n
−
1
}
\{a_0,a_1,...,a_{n-1}\}
{a0,a1,...,an−1},且满足递增顺序,即
{
a
0
≤
a
1
≤
,
.
.
.
,
≤
a
n
−
1
}
\{a_0\leq a_1 \leq,...,\leq a_{n-1}\}
{a0≤a1≤,...,≤an−1}那么题目输入的数组实际上就是由
{
a
i
−
k
,
a
i
+
k
∣
0
≤
i
≤
n
−
1
}
\{a_i-k,a_i+k | 0\leq i \leq n-1\}
{ai−k,ai+k∣0≤i≤n−1}组成的。虽然我们无法直接得到数组nums的顺序,但不难看出,
a
0
−
k
a_0-k
a0−k与
a
n
−
1
+
k
a_{n-1}+k
an−1+k就是nums中最小和最大的数。
由于数组元素的个数并不多,我们可以根据已知的数组最小元素
a
0
−
k
a_0-k
a0−k,枚举对应的
a
0
+
k
a_0+k
a0+k,二者之差即为2k,从而得到k值,这样的枚举方式显然不会超时。
另外,由于我们有 { a 0 ≤ a 1 ≤ , . . . , ≤ a n − 1 } \{a_0\leq a_1 \leq,...,\leq a_{n-1}\} {a0≤a1≤,...,≤an−1},则一定有 { a 0 + k ≤ a 1 + k ≤ , . . . , ≤ a n − 1 + k } \{a_0+k\leq a_1+k \leq,...,\leq a_{n-1}+k\} {a0+k≤a1+k≤,...,≤an−1+k},所以不难得到,数组nums至少存在 n − 1 n-1 n−1个元素不小于 a 0 + k a_0+k a0+k。因此我们首先将nums排序,其中最小的就是 a 0 − k a_0-k a0−k,而接下来从第二小(排序后下标1)到排序后下标n就是可能的 a 0 + k a_0+k a0+k,而从 n + 1 n+1 n+1到 2 n − 1 2n-1 2n−1这 n − 1 n-1 n−1个数不可能是 a 0 + k a_0+k a0+k。
对于枚举的 a 0 + k a_0+k a0+k,与 a 0 − k a_0-k a0−k相减,结果应当是正整数k的二倍,所以不可能是0或任意奇数,如果遇到说明这不可能是 a 0 + k a_0+k a0+k,直接跳过即可。
差是大于0的偶数时,说明这是可能的解。若此时枚举的 a 0 + k a_0+k a0+k在排序后的nums中下标为i,由于 { a 0 + k ≤ a 1 + k ≤ , . . . , ≤ a n − 1 + k } \{a_0+k\leq a_1+k \leq,...,\leq a_{n-1}+k\} {a0+k≤a1+k≤,...,≤an−1+k},则对于 a j , 1 ≤ j ≤ n − 1 a_j,1 \leq j \leq n-1 aj,1≤j≤n−1,一定有 a j − k a_j-k aj−k在i之前,而 a j + k a_j+k aj+k在i之后。可以采用双指针,从1和i+1的下标分别找对应的-k与+k数,定义一个bool数组记录已经确定的数对避免重复,利用数组有序的性质,可以找到全部的相差与枚举的值一致的数对,根据数对求出原数组的对应值并保存。
当遍历结束,可以通过找出来的数对数量是不是n判断当前解是否可行。可行解能够完美将nums分成两部分,从而得到的原数组的值也恰好为n。可行解直接返回即为所求。否则查找下一个可能的 a 0 + k a_0+k a0+k
C++代码如下:
//No 4
vector<int> recoverArray(vector<int>& nums) {
int n = nums.size() / 2;
sort(nums.begin(), nums.end());
for (int i = 0; i <= n; ++i) {
int d = nums[i] - nums[0];
if (d == 0 || d % 2 == 1) continue;
vector<bool>vis(2 * n);
vector<int>ans;
ans.push_back(nums[0] + d / 2);
vis[0] = true;
vis[i] = true;
int l = 1, r = i + 1;
while (r < 2 * n) {
while (l < 2 * n && vis[l])++l;
while (r < 2 * n && nums[r] - nums[l] < d)++r;
if (r<2 * n && nums[r] - nums[l]>d) break;
vis[l] = true;
vis[r] = true;
ans.push_back(nums[l] + d / 2);
++l;
++r;
}
if (ans.size() == n) return ans;
}
return {};
}