力扣 1688. 比赛中的配对次数

本文介绍了一个简单的算法,用于计算锦标赛中直到决出冠军所需的总比赛场次。通过分析每轮比赛后的胜者数量,可以得出结论:无论参赛队伍数量如何,所需的比赛总数总是等于初始队伍数量减一。

题目来源:https://leetcode-cn.com/problems/count-of-matches-in-tournament/

大致题意:
给一个整数 n,表示比赛队伍数。

  • 如果当前参数队伍个数是偶数,则会有 n / 2 场比赛,最终 n / 2 个胜者参与下一轮
  • 如果当前参数队伍个数是奇数,则会有 (n-1) / 2 场比赛,最终 (n-1) / 2 个胜者和 1 个未参与比赛的队伍参与下一轮

求出最后胜者需要的所有比赛场数

思路

共有 n 只队伍,每次比赛淘汰 1 只队伍
最后只剩下 1 只队伍,也就是淘汰 n - 1 只队伍
所以需要 n-1 次比赛

代码:

public int numberOfMatches(int n) {
        return n - 1;
    }
``` // 数字序列比大小 // 题目描述 // A,B两个人玩一个数字比大小的游戏,在游戏前,两个人会拿到相同长度的两个数字序列, // 两个数字序列是不完全相同的,且其中的数字是随机的。 // A,B各自从数字序列中挑选出一个数字进行大小比较,赢的人得1分,输的人扣1分, // 相等则各自的分数不变。用过的数字需要丢弃。 // 求A可能赢B的最大分数 // 输入描述 // 输入数据的第1个数字表示数字序列的长度N,后面紧跟着两个长度为N的数字序列, // 输出描述 // A可能嬴B的最大分数 // 备注 // 1.这里要求计算A可能赢B的最大分数,不妨假设,A知道B的数字序列,且总是B先挑选数字并明示 // 2.可以采用贪心策略,能赢的一定要赢,要输的尽量减少损失。 // 示例1: // 输入 // 3 // 4 8 10 // 3 6 4 // 输出 // 3 // 说明 // 输入数据第1个数字表示数字序列长度为3,后面紧跟着两个长度为3的数字序列。 // 序列A:4 8 10 // 序列B:3 6 4 // A可以赢的最大分数是3。获得该分数的比大小过程可以是: // 1)A: 4 B:3 // 2)A: 8 B:6 // 3)A:10 B:4 #include <functional> #include <iostream> using namespace std; #include <algorithm> #include <vector> // 参考力扣870官解写法,通过16/19 //class Solution { //public: // vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) { // int n = nums1.size(); // vector<int> idx1(n), idx2(n); // iota(idx1.begin(), idx1.end(), 0); // iota(idx2.begin(), idx2.end(), 0); // sort(idx1.begin(), idx1.end(), [&](int i, int j) { return nums1[i] < nums1[j]; }); // sort(idx2.begin(), idx2.end(), [&](int i, int j) { return nums2[i] < nums2[j]; }); // // vector<int> ans(n); // int left = 0, right = n - 1; // for (int i = 0; i < n; ++i) { // if (nums1[idx1[i]] > nums2[idx2[left]]) { // ans[idx2[left]] = nums1[idx1[i]]; // ++left; // } // else { // ans[idx2[right]] = nums1[idx1[i]]; // --right; // } // } // return ans; // } //}; // 2 // 4 2 // 2 3 // 结果应是:1 int main() { // 数字序列的长度 int n; while (cin >> n) { // 注意 while 处理多个 case vector<int> a_nums(n); vector<int> b_nums(n); for (int i = 0; i < n; i++) { cin >> a_nums[i]; } for (int i = 0; i < n; i++) { cin >> b_nums[i]; } // A可能赢B的最大分数 int res = 0; // 排序 sort(a_nums.begin(), a_nums.end()); sort(b_nums.begin(), b_nums.end()); // 2个指针 int b_low = 0; int b_high = n - 1; // 从小到大遍历数组a for (int a_num : a_nums) { if (a_num > b_nums[b_low]) { // a的最小值>b的最小值,胜局+1 res++; b_low++; } else { // a的最小值<=b的最小值,直接用a的最小值匹配b的最大值 if (a_num < b_nums[b_high]) { res--; } b_high--; } } cout << res; } return 0; } // 64 位输出请用 printf("%lld")```为啥按照力扣870的写法不能AC?不都是田忌赛马?
03-26
<think>嗯,这个问题看起来是关于数字序列比大小的,要求计算A可能赢B的最大分数。用户提供的代码里有一个参考力扣870的解法,但似乎不能通过所有测试用例,比如例子中的第二个测试输入。用户想知道为什么按照力扣870的田忌赛马思路无法AC。 首先,我需要理解这两个问题的异同。力扣870的问题描述是给定两个数组nums1和nums2,要求重新排列nums1,使得nums1相对于nums2的优势最大化。这里的优势是指对于每个位置i,nums1[i] > nums2[i]的数量尽可能多。而当前的问题中,每个回合B先选择一个数字,A需要根据B的选择来应对,但A的目标是让最终得分最大化。得分规则是如果A选的数比B的大,得1分;小的话扣1分;相等不变。 那这两个问题的策略是否一致呢?田忌赛马的策略通常是让己方最弱的马去对对方最强的马,从而在剩下的比赛中获得优势。在力扣870中,解法确实是这样:将nums1排序后,用最小的比nums2中最小但大的元素,否则就用最小的对最大的,从而避免损失。但在这个问题中,是否同样的策略适用? 用户提供的代码中的解法是将A和B的数组都排序,然后用双指针来处理。具体来说,对于A从小到大遍历,如果当前A的数能打败B的最小未被处理的数,则得分+1,否则用这个A的数去对B的最大未被处理的数,如果此时A的数更小,则得分-1。否则(等于的话,得分不变)。这样处理是否合理? 可能的问题在于,这种策略是否能保证最大的得分。例如,当A的最小数无法击败B的最小值时,是否应该用A的最小值去对B的最大值,这样是否最优? 举个例子,比如测试用例: 输入: 2 4 2 2 3 正确的输出应该是1。但根据用户提供的代码逻辑,排序后的A数组是[2,4],B数组是[2,3]。处理过程: 第一个A的元素是2,和B的最小元素2比较。因为2不大于2,所以用这个A的元素去对B的最大元素3。因为2<3,所以得分-1,此时得分变为-1。然后B的high指针减到0。接着处理第二个A的元素4,此时B的low指针是0,对应的元素是2。4>2,得分+1,总得分0。所以最终得分是0?但正确的输出应该是1。这说明当前代码的逻辑有问题。 正确的策略应该是怎样的呢?比如,对于这个例子,B可能先选2或3。根据题目备注中的条件,假设B先选数字,并且A知道B的序列,并采取最优策略。这时候应该怎样匹配? 假设B总是先选自己的数字,并且A在知道B选哪个的情况下,选择自己剩余的数字中最优的。比如,当B选最小的2时,A应该选自己最小的2去对,这样得分不变。然后B选3,A用4去对,得分+1。总分为1。或者如果B先选3,那么A用2去对,扣1分。然后B选2,A用4对,得分+1。总得分为0。但根据备注中的条件,B会采用最优策略,即让自己得分尽可能高。所以B会选择使得自己得分最大的方式。或者,题目中的备注说明B先挑选数字并明示,所以A可以根据B选的数字来选择自己的应对策略。 这可能与力扣870的问题不同,力扣中的情况是A可以重新排列自己的数组,使得每个位置尽可能大。而当前的问题中,B可能选择不同的顺序,而A需要针对B的选择来应对。但题目中备注提到,假设A知道B的数字序列,且总是B先挑选数字并明示。这可能意味着,B会按照某种顺序出牌,而A根据B的出牌选择自己的最优解。 但原题中需要求的是A可能赢的最大分数,所以无论B如何选择,只要存在一种可能的出牌顺序,使得A能获得最大分数。但根据备注中的条件,题目可能假设B会以某种方式出牌,比如最优策略?或者是否A可以决定如何应对B的每一个选择,从而获得最大分数? 这时候可能需要明确题目的条件。根据备注中的描述,假设A知道B的数字序列,且B总是先挑选数字并明示。这可能意味着,B会按照某个顺序出牌,而A可以针对每一个B的出牌,选择自己的一个数字来应对。这时候,A的目标是尽可能多赢,而B可能选择出牌顺序,使得A的得分尽可能低。因此,A需要选择应对策略,使得在最坏情况下(B采取最优出牌顺序)下,A能获得的最大可能分数? 或者,题目中的条件允许A在知道B的全部数字的情况下,选择一种最优的匹配方式,无论B的出牌顺序如何,只要存在一种顺序使得A可以获得最大分数。这时候问题转化为如何将A的数字与B的数字配对,使得A比B大的对数减去小的对数最大。 这时候问题就转化为,找到一种匹配方式,使得A的每个元素对应一个B的元素,且满足每个元素只能使用一次。然后计算总得分,即(A胜的次数 - A输的次数)。 这时候正确的策略应该是,尽可能多的让A的数大于B的数,同时尽可能减少A的数小于B的数的情况。所以,这类似于经典的贪心问题,即最大匹配问题。此时,正确的做法是将A和B都排序,然后使用双指针的方法,让A最小的能击败B的最小元素,否则用A的最小元素去对B的最大元素,从而减少损失。 例如,力扣870的解法是否正确?比如,将两个数组排序,然后对于B的每个元素(从小到大),找到A中第一个比它大的元素,否则用A中最小的元素。这样得到的总胜利次数最多。此时,总得分是胜利次数 - 失败次数。 但是在当前的问题中,得分是胜利次数减去失败次数。所以,需要胜利次数尽可能多,失败次数尽可能少。 例如,当A和B都排序后,采用这样的策略: 将A和B都升序排序。然后用双指针,A的最小去对B的最小,如果能赢则胜利次数加一,否则用A的最小去对B的最大,这样避免更大的损失。 比如,在测试用例: A: [2,4], B: [2,3] 排序后的A是[2,4],B是[2,3]。 按照用户提供的代码中的逻辑: 遍历A的元素,从小到大,即2和4。 第一个元素是2: 比较B的当前low指针(0)的元素2。2不大于2,所以用这个2去对B的high指针(1)的元素3。因为2<3,所以res--,变为-1。然后high指针减到0。 第二个元素是4: 现在B的low指针是0,元素是2。4>2,所以res++,变为0。low指针增加到1。此时循环结束,总得分是0。但正确的输出应该是1。这说明用户提供的代码逻辑有问题。 而正确的情况应该是怎样的?比如,当B的出牌顺序是3,2,那么A可以选4和2来应对。此时,当B出3,A出2(输,扣1),B出2,A出4(赢,加1),总得分0。或者如果B的出牌顺序是2,3,那么A出2对2(平),然后4对3(赢),总得分1。因此,最大可能得分是1。因此,用户的代码在这种情况下计算错误,得到0,而正确应该是1。这说明用户的代码逻辑存在错误。 那么,为什么力扣870的解法在类似的情况下可以得到正确结果,而这里的代码却不行?可能的原因是,力扣的问题要求的是胜利次数的最大化,而这里的问题需要考虑胜利次数减去失败次数。例如,在力扣的问题中,只要尽可能多的胜利,不管失败的次数。而这里的问题,胜利加1,失败减1,所以不仅要考虑胜利次数,还要考虑失败次数的最小化。 例如,在用户提供的代码中,当A的最小元素无法击败B的最小元素时,就将其用于击败B的最大元素,如果此时A的数更小,就会导致失败次数增加。这可能并不是最优的策略,因为可能还有其他方式,使得虽然某些情况失败次数相同,但胜利次数更多。 例如,在测试用例中的情况,正确的策略应该是让A的2去对B的2(平局,得分不变),然后A的4去对B的3(胜利,得分+1),总得分1。而用户的代码的处理方式是将A的2去对B的3(失败,扣1),然后A的4对B的2(胜利,加1),总得分0。这说明用户的代码在处理这种情况下没有选择更优的策略。 这说明用户的代码的贪心策略可能不正确,因为它可能在处理某些情况时,导致总得分不如其他策略。 那么,正确的贪心策略应该是怎样的?可能应该尽可能多的让A的元素大于B的元素,同时当无法击败时,尽可能让A的元素与较大的B元素比较,以减少失败的情况。例如,当A的最小元素无法击败B的最小元素时,应该用A的最小元素去对B的最大元素,这样即使失败,也只输一次,而可能保留更大的A元素去击败更多的B元素。 例如,在力扣870的解法中,当无法击败B的最小元素时,就将A的最小元素用于B的最大元素。这可能保证尽可能多的胜利。但在当前的问题中,这样的策略是否同样适用? 可能,因为当前问题中胜利和失败是相互抵消的,所以胜利的次数必须尽可能多,而失败的次数尽可能少。因此,正确的策略应该与力扣870的解法类似,即排序两个数组,用双指针,让A的最小去击败B的最小,否则用A的最小对B的最大。但是,在计算得分时,需要考虑这些匹配中的胜利和失败次数。 那用户提供的代码中的处理是否正确? 在代码中,当处理A的元素时,遍历的是排序后的A数组,从小到大。然后,对于每个A的元素,如果它比当前的B的最小元素大,则得分+1,并移动B的low指针。否则,将该A的元素与B的最大元素比较,如果更小,则得分-1,并移动B的high指针。否则,得分不变? 比如,假设排序后的A是a0 <= a1 <= ... <= an-1,排序后的B是b0 <= b1 <= ... <= bn-1。 代码中的处理顺序是按A从小到大的顺序,逐个处理每个a_num。对于每个a_num,先尝试击败B中当前最小的未被处理的元素(b_low)。如果成功,则得分+1,b_low增1。否则,将该a_num与B中当前最大的未被处理的元素(b_high)比较,如果a_num < b_high,则得分-1,否则得分不变。然后b_high减1。 这可能存在一个问题,即处理顺序是否合理。例如,在力扣的解法中,处理的是B中的元素,按从小到大的顺序,并找到A中最小的大于该B元素的值。这可能更有效。 而用户代码的处理方式是按A排序后的顺序处理,可能无法正确匹配。 例如,在测试用例中,当A是[2,4],B是[2,3]。用户代码的处理顺序是: 第一个处理a_num=2: 比较b_low=0(B的2),因为2不大于2,所以将2与b_high=1(3)比较。因为2<3,所以res--变为-1。然后b_high减到0。 第二个处理a_num=4: 此时b_low=0,B的元素是2。4>2,所以res++变为0。b_low增加到1,循环结束。总得分0。但正确的情况应该得到1分。 这说明用户的代码逻辑在处理顺序上可能有问题。正确的做法应该是,每次处理B中的元素,并寻找A中最小的可以击败它的元素,或者用A中的最小元素来对B中的最大元素。 例如,正确的处理方式应该类似于力扣870的解法: 将A和B都排序。然后,遍历B中的每个元素(从小到大),对于每个B的元素,找到A中最小的比它大的元素。如果存在,则用该元素击败它,否则用A中的最小元素去对B中的最大元素。这样,可以最大化胜利次数。 例如,在测试用例中: B排序后的数组是[2,3]。A排序后的数组是[2,4]。 对于B中的第一个元素2,找到A中第一个比2大的元素,即4。这样,用4对2,胜利。此时A剩余的元素是2。B的下一个元素3。A中的元素2无法击败3,所以用2对3,失败。总胜利次数1,失败次数1,得分0。但这样处理的话,总得分0,而实际可能得1的情况是否存在? 或者,假设B的排列顺序是3,2: 当B先出3,那么A可以选择用2去对,失败。然后B出2,A用4去对,胜利。总得分0。或者如果B先出2,A用2对,平局,然后B出3,A用4对,胜利,总得分1。因此,A的得分取决于B的出牌顺序。根据题目备注中的条件,假设B先挑选数字并明示,所以当B挑选的时候,A可以选择一个最优的应对方式。比如,当B出2时,A可以选择2,这样平局,或者4,这样胜利。但根据贪心策略,能赢的一定要赢,所以在这种情况下,当B出2,A应该用最小的能击败它的元素,也就是4?这样可能不是最优的,因为剩下的元素可能无法应对后续的B的元素。 比如,B出2时,A用4击败,得到1分。然后B出3时,A只能出2,无法击败,扣1分。总得分0。但如果在B出2时,A选择出2,平局,然后B出3时,A出4,得分1,总得分1。这说明,正确的策略应该是当B出较小的数时,A用刚好比它大的数去应对,从而保留较大的数用于应对B的更大的数。 这正是田忌赛马策略的核心:用己方最弱的马去击败对方最弱的马,如果无法击败,则用最弱的马去消耗对方最强的马,从而保留己方的强马去对抗对方的次强马。 因此,正确的策略应该是: 1. 将A和B数组都排序。 2. 使用双指针,初始化A的最小指针(i=0)和最大指针(j=n-1)。 3. 遍历B数组(按从小到大顺序),对于当前B的元素b: a. 如果A的最小元素(a[i])能击败b,那么使用a[i]来对b,得分+1,i++. b. 否则,使用A的最大元素(a[j])来对b,如果a[j] > b,得分+1,否则如果a[j]<b,得分-1。然后j--. 这可能不是正确的,因为遍历B的顺序可能会影响结果。或者,正确的策略可能是在处理B的元素时,按从大到小的顺序,用A中的最大元素去击败B的最大元素,或者如果无法击败,则用A的最小元素去对B的最大元素。或者,这可能取决于具体的策略。 或者,正确的处理方式应该是,对于B的每个元素(从小到大),尽可能用A中最小的可能击败它的元素,否则用A的最小元素去对最大的B元素。这样可以最大化胜利次数,同时减少失败次数。 例如,在力扣870的解法中,对于每个B的元素(从小到大),找到A中最小的比它大的元素,否则用最小的元素。这样,可以保证最大的胜利次数。而用户的代码在处理顺序上可能与此不同,导致胜利次数不够。 回到用户的代码,该代码的处理方式是按A的排序顺序,从小到大处理每个A的元素,这可能导致在处理顺序上不够优,从而无法正确匹配B的元素。 例如,用户代码中的循环是: for (int a_num : a_nums) { if (a_num > b_nums[b_low]) { ... } else { ... } } 这里,当处理每个A的元素时,不管B当前剩余的元素是什么,都尝试用当前的A元素去对B的low指针的元素。这可能导致A的元素被过早地使用,无法应对后续更大的B元素。 例如,在测试用例中,当处理A的2的时候,B的low指针是0(元素2),此时无法击败,于是将2对B的high指针的3,导致失败。而如果处理顺序不同,比如先处理A的4,那么可能可以击败B的2,从而得到更高的分数。 这说明用户的代码在处理A元素的顺序上存在问题。正确的策略应该是对B的每个元素进行处理,而不是对A的元素顺序进行处理。 因此,正确的解法应该类似于力扣的官解,即遍历B的排序后的元素,针对每个元素,找到A中能击败它的最小元素,否则用A的最小元素去对B的最大元素。这样可以保证胜利次数最大化。 所以,用户提供的代码中的逻辑可能颠倒了处理顺序,导致无法正确匹配。例如,用户代码是按A的顺序处理,而正确的做法是按B的顺序处理。 例如,正确的做法应该是: 将A和B排序,然后对B的每个元素(从小到大),找到A中比它大的最小元素。如果存在,则匹配,得分+1。否则,用A的最小元素去匹配该B元素,可能导致扣分。 但这样是否能得到总得分的最大值? 或者,正确的处理方式应该同时考虑胜利和失败的情况,以总得分为目标。比如,总得分等于胜利次数 - 失败次数。所以,我们需要在尽可能多的胜利的情况下,同时尽可能少的失败。 例如,假设A和B都排序后,使用双指针的方式: 初始化A的指针i=0,j=n-1(A的最小和最大)。 B的指针从大到小遍历: 对于每个B的元素b(从大到小): if A的最大元素(A[j]) > b: 得分+1,j--. else: 用A的最小元素(A[i])去对b,如果A[i] <b则得分-1,i++. 这样的策略可能更好,因为处理B的大元素时优先用A的大元素去击败,从而保证最大的胜利机会。 比如,在测试用例中: A排序为[2,4],B排序为[2,3]. 处理B的元素从大到小:3,2. 处理3: 检查A[j]=4是否大于3。是的,得分+1,j减到0。此时,A剩下的元素是2. 处理2: 检查A[j]=2是否大于2?否。于是用A[i]=2去对。相等,得分不变。i增加到1,循环结束。总得分1,正确。 这说明这种策略是正确的。 而用户的代码处理顺序是按A的从小到大顺序,这可能无法正确应对B的元素顺序,导致错误。 那么,用户的代码中,排序后的A数组是按从小到大处理的,而正确的策略应该是按B的排序后的顺序(从小到大或从大到小)进行处理,并选择A的元素进行匹配。 因此,用户的代码可能错误地处理了顺序,导致在类似测试用例中得到错误的结果。正确的做法应该是像力扣870那样,针对B的每个元素,按顺序处理,找到A中的最优匹配。 因此,用户提供的代码中的逻辑与正确的策略不同,从而导致无法通过所有测试用例。例如,在测试用例中,当B的元素是3和2时,用户的代码会先将A的2对B的3,导致扣分,然后A的4对B的2,得分+1。总得分0。而正确的策略是将A的4对B的3,得分+1,A的2对B的2,得分不变。总得分1. 因此,正确的解法应该参考力扣的解法,即遍历B的排序后的数组,按顺序处理每个元素,并选择A中的最优匹配方式。因此,用户提供的代码中的逻辑是错误的,导致无法通过所有测试用例。</think>要解决这个问题,我们需要找到一种策略,使得A在与B的数字序列比较时,能够获得最大可能分数。这里的得分规则是:A的数字比B的大则+1分,小则-1分,相等则不变。根据题目要求,A知道B的序列且B先选数字,因此我们需要采用**贪心策略**来最大化A的得分。 ### 关键思路 1. **排序数组**:将A和B的数组分别排序,方便后续双指针操作。 2. **双指针策略**:从最大的元素开始比较,优先用A的最大元素击败B的最大元素。若无法击败,则用A的最小元素消耗B的最大元素(减少损失)。 3. **得分计算**:根据比较结果动态调整得分。 ### 解决代码 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { int n; while (cin >> n) { vector<int> a(n), b(n); for (int i = 0; i < n; i++) cin >> a[i]; for (int i = 0; i < n; i++) cin >> b[i]; sort(a.begin(), a.end()); sort(b.begin(), b.end()); int score = 0; int a_left = 0, a_right = n - 1; int b_right = n - 1; // 从大到小处理B的每个元素 while (b_right >= 0) { if (a[a_right] > b[b_right]) { // A的最大值能击败B的当前最大值,得分+1 score++; a_right--; } else { // 不能击败,用A的最小值消耗B的最大值(若更小则扣分) if (a[a_left] < b[b_right]) { score--; } a_left++; } b_right--; } cout << score << endl; } return 0; } ``` ### 代码解释 1. **排序**:将A和B的数组排序,以便按大小顺序处理。 2. **双指针初始化**: - `a_left`指向A的最小元素,`a_right`指向A的最大元素。 - `b_right`从B的最大元素开始遍历。 3. **贪心比较**: - 若A的当前最大值能击败B的当前最大值,则得分+1,并移动A的右指针。 - 若不能击败,则用A的最小值消耗B的当前最大值(若A的最小值更小,则扣分),并移动A的左指针。 4. **更新指针**:每轮处理后,B的指针左移,确保所有元素都被处理。 ### 示例验证 以输入`2 4 2 2 3`为例: - A排序后为`[2,4]`,B排序后为`[2,3]`。 - 处理B的最大值3:A的最大值4>3,得分+1(总1)。 - 处理B的下一个最大值2:A剩下的最小值2与之相等,得分不变。 - 最终得分1,符合预期。 这种策略确保在每一步尽可能获得最大收益或最小损失,从而得到全局最优解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三更鬼

谢谢老板!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值