1. 前言
很难受,题2wa了很多发,一直卡一个case,想破脑瓜没想明白,还是太菜。然后题3比题2还简单,题4模拟细节巨多,补题的时候看到灵神的c++代码大为震撼。
2. 赛题
2.1 题1
2.1.1 题意
数组中相对位置不同的两个数,如果位置前的数的最高位和位置处于后的最低位互质,那么这是一个美丽数对。求数组中美丽数对的数目。
2.1.2 分析
数据范围限制最高位最低为都是[1,9] 时间复杂度上限可以到O( n 3 n^3 n3)
2 <= nums.length <= 100
1 <= nums[i] <= 9999
nums[i] % 10 != 0
先遍历一遍数组把每个位置的最高位最低为求出记录下。
然后枚举数组位置i, j,
判断对应两个数的高位、低位最大公因数是否为1(这里可以直接枚举,因为两个数范围都是[1,9],是常量级,也可以先做个表,把所有情况记录)
2.1.3 我的解法
class Solution {
public:
int gcd(int x, int y){
// 1 2 3 4 5 6 7 8 9
// 1 2 3 4 5 6 7 8 9
for(int i=9; i>=2; i--){
if((x%i==0) && (y%i==0)){
return i;
}
}
// 最大公因数为1
return 1;
}
int countBeautifulPairs(vector<int>& nums) {
int n = nums.size();
vector<int> b(n, 0);
vector<int> e(n, 0);
// 求出最高位和最低位
for(int i=0; i<n; i++){
// 最低为直接取模
e[i] = nums[i]%10;
int tmp = nums[i];
// 最高位需要不断除10
while(tmp>=10){
tmp/=10;
}
b[i] = tmp;
}
int res = 0;
for(int i=0; i<n; i++){
for(int j=i+1; j<n; j++){
// 最大公因数
res += (gcd(b[i], e[j]) == 1);
}
}
return res;
}
};
2.1.4 学习题解反思
我的解法:
时间复杂度O(
n
2
n^2
n2)
空间复杂度O(
n
n
n)
灵神太强了,以后跟着灵神学了,用一个表记下已经出现的最高位的数字的次数,然后改为枚举最高位的可能(就是[1,9]),可以降时间。
2.1.5 bug日记
第一题wa一发,菜哭。求最高位的时候的循环结束条件边界取错(错误情况取tmp>10)。
2.2 题2
LeetCode 6471. 得到整数零需要执行的最少操作数
2.2.1 题意
给定两个数num1和num2,每次对 num1 减去 2^i + num2 ,求使num1变成0的次数
2.2.2 分析
假设对num1做k次减后变成0,那么有
n
u
m
1
=
k
∗
n
u
m
2
+
2
i
1
+
2
i
2
+
.
.
.
+
2
i
k
num1=k*num2+2^{i_1}+2^{i_2}+...+2^{i_k}
num1=k∗num2+2i1+2i2+...+2ik
即:
n
u
m
1
−
k
∗
n
u
m
2
=
2
i
1
+
2
i
2
+
.
.
.
+
2
i
k
num1-k*num2=2^{i_1}+2^{i_2}+...+2^{i_k}
num1−k∗num2=2i1+2i2+...+2ik
考虑等式右边的含义:
2
i
1
2^{i_1}
2i1表示二进制下第
i
1
i_1
i1位为1,
2
i
2
2^{i_2}
2i2表示二进制下第
i
2
i_2
i2位为1…
但是如果两个相同的位相加,为1的位数会-1,如
i
1
=
=
i
2
,
2
i
1
+
2
i
2
=
2
i
1
+
1
i_1==i_2,2^{i_1}+2^{i_2}=2^{i_1+1}
i1==i2,2i1+2i2=2i1+1
也就是说,等式右边是在二进制表示下1的个数最多只有k位的数。
那么他有下界嘛,可以简单预料到二进制下1的个数最少为1。
这是一个粗略的下界,对于等式右边每个数有
2
i
j
>
=
1
2^{i_j}>=1
2ij>=1,
那么
n
u
m
1
−
k
∗
n
u
m
2
>
=
k
num1-k*num2 >= k
num1−k∗num2>=k
1 <= num1 <= 10^9
-10^9 <= num2 <= 10^9
总结一下,满足以下条件的最小值即为答案
b
i
t
C
o
u
n
t
(
n
u
m
1
−
k
∗
n
u
m
2
)
<
=
k
bitCount(num1-k*num2)<=k
bitCount(num1−k∗num2)<=k &&
n
u
m
1
−
k
∗
n
u
m
2
>
=
k
num1-k*num2 >=k
num1−k∗num2>=k
2.2.3 我的解法
class Solution {
public:
long bitCount(long x){
long res = 0;
while(x!=0){
// 用lowbit求1的个数
x &= (x-1);
res++;
}
return res;
}
int makeTheIntegerZero(int num1, int num2) {
// nums1 = 2^a + 2^b + 2^c + k * nums2
int res = 0;
// 防溢出
long n1 = num1;
long n2 = num2;
for(long i=1; i<=64; i++){
// 枚举符合条件的i,
// i不可能为0次,因为num1!=0
n1 -= n2; // 每次递减
if((n1)>=i && bitCount(n1) <=i){
// 满足条件即为结果
return i;
}
}
return -1;
}
};
2.2.4 学习题解反思
我的解法:
时间复杂度O(1), (这个复杂度极不严谨,建议学习灵神)
空间复杂度O(1)
关于结果上界是64,有没有可能出现64以上次数的结果?
当k=64时,
n
u
m
1
−
k
∗
n
u
m
2
num1-k*num2
num1−k∗num2的结果中有至少65位为1才能出现该情况。
也就是
n
u
m
1
−
k
∗
n
u
m
2
num1-k*num2
num1−k∗num2溢出long long型。
但是注意看数据范围最坏情况
n
u
m
1
=
1
0
9
,
n
u
m
2
=
−
1
0
9
,
此时
n
u
m
1
−
64
∗
n
u
m
2
=
65
∗
1
0
9
<
L
O
N
G
_
M
A
X
num1=10^9,num2=-10^9,此时num1-64*num2=65*10^9<LONG\_MAX
num1=109,num2=−109,此时num1−64∗num2=65∗109<LONG_MAX不会溢出,所以一个粗浅的答案上界为64.
2.2.5 bug日记
2.2.5.1 下限估计不准
很可惜,周赛的时候下限估计的不够准确,导致最后一个hidden case过不了,看了下评论区,也有不少一样情况兄弟。我还是太菜了。
2.3 题3
LeetCode 2750. 将数组划分成若干好子数组的方式
2.3.1 题意
对数组进行划分,每个划分后的子数组只含有1个1,求划分的方法数
2.3.2 分析
看一下数据范围,时间复杂度上限到O( n ∗ l o g n n*logn n∗logn)
1 <= nums.length <= 10^5
0 <= nums[i] <= 1
要求划分的子数组恰好含有1个1,那么可以划分的地方就是数组中两个1的位置之间。
举个例子
数组 [0,1,0,0,1] 可以划分的位置有
[0,1] [0,0,1]
[0,1,0] [0,1]
[0,1,0,0] [1]
这只是两个1之间的划分方式,后面可能还有别的1,它的结果应该是相邻的1的划分种类数乘积在一起。
注意细节上,出现全0没有划分的情况
2.3.3 我的解法
class Solution {
public:
int numberOfGoodSubarraySplits(vector<int>& nums) {
int mod = 1e9+7;
long res = 1;
int n = nums.size();
vector<int> x;
// 找到为1的下标 从小到大顺序
for(int i=0; i<n; i++){
if(nums[i]==1)
x.emplace_back(i);
}
int m = x.size();
// 如果没有出现1
if(x.size() == 0){
return 0;
}
// 获得结果
for(int i=1; i<m; i++){
res = ( (res%mod) * ((x[i]-x[i-1]) %mod) )%mod;
}
return res;
}
};
2.3.4 学习题解反思
我的解法:
时间复杂度O(
n
n
n),
空间复杂度O(
n
n
n)
2.3.5 bug日记
2.3.5.1 特殊情况
全0的情况没考虑,直接wa一发,菜哭。
2.4 题4
2.4.1 题意
n个机器人在水平轴向左/右运动,每个机器人有初始健康值,若机器人发送碰撞,健康值大的机器人健康值-1,健康值小的机器人直接消失,健康值相同两个机器人都消失。求最后活着的机器人的健康值,按初始给定的顺序返回。
2.4.2 分析
看一下数据,复杂度大概在O( n ∗ l o g n n*logn n∗logn)级别。
1 <= positions.length == healths.length == directions.length == n <= 10^5
1 <= positions[i], healths[i] <= 10^9
directions[i] == ‘L’ 或 directions[i] == ‘R’
positions 中的所有值互不相同
按照位置排序
找到机器人,这个机器人往左边运动,且机器人左边有往右运动的机器人
把往右运动的机器人按位置依次放入栈中
模拟碰撞事件
2.4.3 我的解法
class Robot{
public:
// 定义类
int pos;
int healths;
char dir;
// 下标,因为要按原顺序排结果
int index;
Robot (int p, int h, int d, int i): pos(p), healths(h), dir(d), index(i){}
bool operator < (Robot r){
// 重载
return this->pos < r.pos;
}
bool operator > (Robot r){
return this->pos > r.pos;
}
};
bool cmp(Robot a, Robot b){
// 按下标顺序排序
return a.index < b.index;
}
class Solution {
public:
vector<int> survivedRobotsHealths(vector<int>& positions, vector<int>& healths, string directions) {
int n = positions.size();
vector<Robot> robot;
// sort by position
// 按照位置排序
for(int i=0; i<n; i++){
robot.emplace_back(Robot(positions[i], healths[i], directions[i], i) );
}
sort(robot.begin(), robot.end());
// collision
// 发送碰撞
stack<int> right;
// 按照位置排序
// 找到机器人,这个机器人往左边运动,且机器人左边有往右运动的机器人
// 把往右运动的机器人按位置依次放入栈中
// 模拟碰撞事件
for(int i=0;i<n;i++){
if(robot[i].dir == 'L'){
if(right.empty()){
// no robot move twards right
// 这个机器人左边没有向右运动的机器人
// 不会继续发送碰撞
continue;
}
else{
// do collision
while(1){
if(right.empty()){
// 这个机器人左边没有向右运动的机器人
// 不会继续发送碰撞
break;
}
int ind = right.top();
right.pop();
if(robot[ind].healths == robot[i].healths){
// 向左向右的抵消了
robot[ind].healths = 0;
robot[i].healths = 0;
// 向左运动的噶了 不能接着创
break;
}
else if(robot[ind].healths < robot[i].healths){
// 向右运动的被创噶了
// 向左运动的可以接着创
robot[ind].healths = 0;
robot[i].healths -= 1;
}
else{
robot[ind].healths -= 1;
robot[i].healths = 0;
right.push(ind);
// 向左运动的噶了 不能接着创
break;
}
}
}
}
else{
right.push(i);
}
}
vector<int> res;
// sort by index and find res
// 按结果排序
sort(robot.begin(), robot.end(),cmp);
for(int i=0; i<n; i++){
if(robot[i].healths != 0){
// 没被创ga的机器人
res.emplace_back(robot[i].healths);
}
}
return res;
}
};
2.4.4 学习题解反思
时间复杂度O(
n
∗
l
o
g
n
n*logn
n∗logn),
空间复杂度O(
n
n
n)
别问,问就是直接学的题解,灵神的c++的解法也太妙了,是真优雅,学习了。
3. 后记
仅分享自己的想法,有意见和指点非常感谢