第219场LeetCode周赛

第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)=n1可以采用数学归纳法:
首先 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)=k1,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)=2k1,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)=n1的结论。

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,j1),返回二者之间较大的即可。

很显然这样做出现了大量重复计算,比如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][j1])
这一点跟上面是一致的,区别在于,正确的计算顺序将节省很多计算量。
比如我们计算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;
  }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值