第219场LeetCode周赛
No.1 比赛中的配对次数
给你一个整数 n ,表示比赛中的队伍数。比赛遵循一种独特的赛制:
如果当前队伍数是 偶数 ,那么每支队伍都会与另一支队伍配对。总共进行 n / 2 场比赛,且产生 n / 2 支队伍进入下一轮。
如果当前队伍数为 奇数 ,那么将会随机轮空并晋级一支队伍,其余的队伍配对。总共进行 (n - 1) / 2 场比赛,且产生 (n - 1) / 2 + 1 支队伍进入下一轮。
返回在比赛中进行的配对次数,直到决出获胜队伍为止。
示例 1:
输入:n = 7
输出:6
解释:比赛详情:
- 第 1 轮:队伍数 = 7 ,配对次数 = 3 ,4 支队伍晋级。
- 第 2 轮:队伍数 = 4 ,配对次数 = 2 ,2 支队伍晋级。
- 第 3 轮:队伍数 = 2 ,配对次数 = 1 ,决出 1 支获胜队伍。
总配对次数 = 3 + 2 + 1 = 6
示例 2:
输入:n = 14
输出:13
解释:比赛详情:
- 第 1 轮:队伍数 = 14 ,配对次数 = 7 ,7 支队伍晋级。
- 第 2 轮:队伍数 = 7 ,配对次数 = 3 ,4 支队伍晋级。
- 第 3 轮:队伍数 = 4 ,配对次数 = 2 ,2 支队伍晋级。
- 第 4 轮:队伍数 = 2 ,配对次数 = 1 ,决出 1 支获胜队伍。
总配对次数 = 7 + 3 + 2 + 1 = 13
提示:
1 <= n <= 200
解析
本题还是例行签到题,数据范围很小,按照要求在n大于1时计算本轮配对次数,叠加在结果上,并更新n即可。不过似乎可以证明,结果就是n-1。
// leetcode weekly contest 219 - code
//No 1
int numberOfMatches(int n) {
int ans = 0;
while (n > 1) {
if (n % 2 == 0) {
ans += n / 2;
n = n / 2;
}
else {
ans += (n - 1) / 2;
n = (n - 1) / 2 + 1;
}
}
return ans;
}
以上是暴力直接做的方法。
证明
F
(
n
)
=
n
−
1
F(n)=n-1
F(n)=n−1可以采用数学归纳法:
首先
F
(
1
)
=
0
,
F
(
2
)
=
1
,
F
(
3
)
=
2
F(1)=0, F(2)=1, F(3)=2
F(1)=0,F(2)=1,F(3)=2
如果我们已知
F
(
k
)
=
k
−
1
,
F
(
k
+
1
)
=
k
F(k)=k-1,F(k+1)=k
F(k)=k−1,F(k+1)=k
那么则可以推出
F
(
2
k
)
=
k
+
F
(
k
)
=
2
k
−
1
,
F
(
2
k
+
1
)
=
k
+
F
(
k
+
1
)
=
2
k
F(2k)=k+F(k)=2k-1, F(2k+1)=k+F(k+1)=2k
F(2k)=k+F(k)=2k−1,F(2k+1)=k+F(k+1)=2k
显然任意大于3的正整数都可以表示为
2
k
2k
2k或
2
k
+
1
2k+1
2k+1其中k是大于等于1的正整数,而小于等于3的情况又可以直接算出,则可推导出
F
(
n
)
=
n
−
1
F(n)=n-1
F(n)=n−1的结论。
No.2 十-二进制数的最少数目
如果一个十进制数字不含任何前导零,且每一位上的数字不是 0 就是 1 ,那么该数字就是一个 十-二进制数 。例如,101 和 1100 都是 十-二进制数,而 112 和 3001 不是。
给你一个表示十进制整数的字符串 n ,返回和为 n 的 十-二进制数 的最少数目。
示例 1:
输入:n = “32”
输出:3
解释:10 + 11 + 11 = 32
示例 2:
输入:n = “82734”
输出:8
示例 3:
输入:n = “27346209830709182346”
输出:9
提示:
1 <= n.length <= 10^5
n 仅由数字组成
n 不含任何前导零并总是表示正整数
解析
本题更像是一个脑筋急转弯。给定一个只包含0-9的字符串,表示一个数,需要一些只包含0-1的数构造出来,构造方式就是对位相加。
那很显然,每个位置的数值是几,就需要这个构造的数中在这一位上有几个1.所以本题就是看需要构造的数,各个数位上最大的数是几就够了。
//No 2
int minPartitions(string n) {
int ans = 0;
for (auto c : n) {
ans = max(c - '0', ans);
}
return ans;
}
No.3 石子游戏 VII
石子游戏中,爱丽丝和鲍勃轮流进行自己的回合,爱丽丝先开始 。
有 n 块石子排成一排。每个玩家的回合中,可以从行中 移除 最左边的石头或最右边的石头,并获得与该行中剩余石头值之 和 相等的得分。当没有石头可移除时,得分较高者获胜。
鲍勃发现他总是输掉游戏(可怜的鲍勃,他总是输),所以他决定尽力 减小得分的差值 。爱丽丝的目标是最大限度地 扩大得分的差值 。
给你一个整数数组 stones ,其中 stones[i] 表示 从左边开始 的第 i 个石头的值,如果爱丽丝和鲍勃都 发挥出最佳水平 ,请返回他们 得分的差值 。
示例 1:
输入:stones = [5,3,1,4,2]
输出:6
解释:
- 爱丽丝移除 2 ,得分 5 + 3 + 1 + 4 = 13 。游戏情况:爱丽丝 = 13 ,鲍勃 = 0 ,石子 = [5,3,1,4] 。
- 鲍勃移除 5 ,得分 3 + 1 + 4 = 8 。游戏情况:爱丽丝 = 13 ,鲍勃 = 8 ,石子 = [3,1,4] 。
- 爱丽丝移除 3 ,得分 1 + 4 = 5 。游戏情况:爱丽丝 = 18 ,鲍勃 = 8 ,石子 = [1,4] 。
- 鲍勃移除 1 ,得分 4 。游戏情况:爱丽丝 = 18 ,鲍勃 = 12 ,石子 = [4] 。
- 爱丽丝移除 4 ,得分 0 。游戏情况:爱丽丝 = 18 ,鲍勃 = 12 ,石子 = [] 。
得分的差值 18 - 12 = 6 。
示例 2:
输入:stones = [7,90,5,1,100,10,10,2]
输出:122
提示:
n == stones.length
2 <= n <= 1000
1 <= stones[i] <= 1000
解析
本题我不由得感叹“石子游戏”这个系列的题目可真多啊。不过这道题相比于之前有些数据范围大的惊人的找规律题目,反而算是较为基础的。
本题最容易想到的,应该是递归的求解。定义一个函数getMax,这个函数用于返回stones范围在
[
i
,
j
]
[i,j]
[i,j]内取得的最大分差。实际上无论是a还是b,他们的最优解就是保证自己取石子时能够拿到最大的分差(能赢就赢得最多,否则就输得尽可能少)二者原则是一样的。那么对于只剩1个石子的情况,拿走之后就没了,都不得分,返回0;对于其他情况,有两种取法,1是拿走最左边的下标为i的,获得分数V[i],此时剩余[i+1,j],对方在此基础上去拿,将得到最大分差
g
e
t
M
a
x
(
s
t
o
n
e
s
,
i
+
1
,
j
)
getMax(stones,i+1,j)
getMax(stones,i+1,j),此时先手者能得到的最大分差就是
V
[
i
]
−
g
e
t
M
a
x
(
s
t
o
n
e
s
,
i
+
1
,
j
)
V[i]-getMax(stones,i+1,j)
V[i]−getMax(stones,i+1,j);同理,拿右边的,将得到
V
[
j
]
−
g
e
t
M
a
x
(
s
t
o
n
e
s
,
i
,
j
−
1
)
V[j]-getMax(stones,i,j-1)
V[j]−getMax(stones,i,j−1),返回二者之间较大的即可。
很显然这样做出现了大量重复计算,比如5个石子,先拿4后拿0,剩下1,2,3,先拿0后拿4还是剩下1,2,3.所以自然而然的想到改为动规的思路。
我们定义
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示石头下标位于
[
i
,
j
]
[i,j]
[i,j]时,先手能够获得的最大分差,显然:
d
p
[
i
]
[
j
]
=
m
a
x
(
V
[
i
]
−
d
p
[
i
+
1
]
[
j
]
,
V
[
j
]
−
d
p
[
i
]
[
j
−
1
]
)
dp[i][j]=max(V[i]-dp[i+1][j],V[j]-dp[i][j-1])
dp[i][j]=max(V[i]−dp[i+1][j],V[j]−dp[i][j−1])
这一点跟上面是一致的,区别在于,正确的计算顺序将节省很多计算量。
比如我们计算dp[2][4],其实依赖的是dp[3][4]和dp[2][3],所以j需要递增,而i需要递减计算。所有i==j的值意味着只有一个石头,值就是0;i>j无需计算。
C++代码如下:
//No 3
int stoneGameVII(vector<int>& stones) {
int l = 0, r = stones.size() - 1, n = stones.size();
vector<int>sum(stones.begin(), stones.end());
for (int i = 1; i < n; ++i) sum[i] = sum[i - 1] + stones[i];
vector<vector<int>>dp(n, vector<int>(n, 0));
for (int i = 0; i < n; ++i) dp[i][i] = 0;
for (int j = 1; j < n; ++j) {
for (int i = j - 1; i >= 0; --i) {
dp[i][j] = max((sum[j] - sum[i]) - dp[i + 1][j], (sum[j - 1] - sum[i] + stones[i]) - dp[i][j - 1]);
}
}
return dp[0][n - 1];
}
这一类题目,其实基本都可以动规来做,只要定义动规数组表示先手能得到的收益即可。因为一个人完成一步操作后,另一个人就成为了当前状态下的先手,二者存在一个镜像关系(你赢就代表我输,你的得分就是我的扣分这种的)一般都遵循这样一个规律。但我们还是要注意数据范围,如果数据显然O(N)都会超时,那代表可能存在着数学规律,满足某些条件先手必胜这种的,而不需要一个个推出来。这种情况可以自己模拟一下,或者用动规方式自己算小范围的值,打表出来看看。
No.4 堆叠长方体的最大高度
给你 n 个长方体 cuboids ,其中第 i 个长方体的长宽高表示为 cuboids[i] = [widthi, lengthi, heighti](下标从 0 开始)。请你从 cuboids 选出一个 子集 ,并将它们堆叠起来。
如果 widthi <= widthj 且 lengthi <= lengthj 且 heighti <= heightj ,你就可以将长方体 i 堆叠在长方体 j 上。你可以通过旋转把长方体的长宽高重新排列,以将它放在另一个长方体上。
返回 堆叠长方体 cuboids 可以得到的 最大高度 。
示例 1:
输入:cuboids = [[50,45,20],[95,37,53],[45,23,12]]
输出:190
解释:
第 1 个长方体放在底部,53x37 的一面朝下,高度为 95 。
第 0 个长方体放在中间,45x20 的一面朝下,高度为 50 。
第 2 个长方体放在上面,23x12 的一面朝下,高度为 45 。
总高度是 95 + 50 + 45 = 190 。
示例 2:
输入:cuboids = [[38,25,45],[76,35,3]]
输出:76
解释:
无法将任何长方体放在另一个上面。
选择第 1 个长方体然后旋转它,使 35x3 的一面朝下,其高度为 76 。
示例 3:
输入:cuboids = [[7,11,17],[7,17,11],[11,7,17],[11,17,7],[17,7,11],[17,11,7]]
输出:102
解释:
重新排列长方体后,可以看到所有长方体的尺寸都相同。
你可以把 11x7 的一面朝下,这样它们的高度就是 17 。
堆叠长方体的最大高度为 6 * 17 = 102 。
提示:
n == cuboids.length
1 <= n <= 100
1 <= widthi, lengthi, heighti <= 100
解析
这道题还是挺难的,至少在数学上我还不能完全证明这样做对的(捂脸
本题给定一些长方体盒子,有长宽高三维,要求我们将盒子按照一定的方向和顺序堆叠起来,堆叠时上面的盒子三维都要小于等于下面的,才能堆放,求最多能堆多高。
本体我的思路是按照某顺序排下序,然后按照类似最长递增子序列的思路动规,求出以每个盒子为最上层能达到的最大高度,全局最大值显然就是每个盒子最上层的值中的最大值。但问题就出在,按啥排序?
首先为了方便比较两个盒子能否堆放(三维都小于等于),我们对每个长方体的3个参数先排个序。
然后这里有一个假设就是最大的高度,是每个长方体都拿最长边做高(不知道怎么证明)
排序方式,其实是遵循一个原则,就是排在前面的一定没办法放在后面的上边,无论怎么转。(当然排在每个长方体不一定能放在前面所有长方体之上,但前面一定放不了后面)我还暂时无法证明下面的排序符合这个原则,也不知道怎么样会破坏这个原则,反正如下的代码能过。
排序后,类似递增子序列的套路,求以每个长方体做最上层的最大高度。而每个长方体做最上层的高度,就是遍历他之前所有的长方体,如果能放上去,就记录这个高度,找到其中最大的。就是对于dp[i],所有小于i的j,对于满足堆叠条件的j,求max(dp[j]),再加上i本身的高度就可以了。
代码如下:
本题的核心还是 怎么知道这样排序就是对的,真叫人头大
//No 4
static bool comp(vector<int>& a, vector<int>& b) {
//return pair<int, int>(a[2], a[0] + a[1]) < pair<int, int>(b[2], b[0] + b[1]);
return a[2] < b[2] || (a[2] == b[2] && a[0] < b[0]) || (a[2] == b[2] && a[0] == b[0] && a[1] < b[1]);
}
int maxHeight(vector<vector<int>>& cuboids) {
int n = cuboids.size();
for (auto& v : cuboids) {
sort(v.begin(), v.end());
}
sort(cuboids.begin(), cuboids.end(), comp);
vector<int>dp(n, 0);
int ans = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (cuboids[j][0] <= cuboids[i][0] && cuboids[j][1] <= cuboids[i][1] && cuboids[j][2] <= cuboids[i][2]) {
dp[i] = max(dp[i], dp[j]);
}
}
dp[i] += cuboids[i][2];
ans = max(ans, dp[i]);
}
return ans;
}