华为OD机考算法题:篮球比赛

2023-09-19 23:45:00

目录

题目部分

解读与分析

代码实现


题目部分

题目篮球比赛
难度
题目说明篮球(5V5)比赛中,每个球员拥有一个战斗力,每个队伍的所有球员战斗力之和为该队伍的总体战斗力。现有 10 个球员准备分为两队进行训练赛,教练希望 2 个队伍的战斗力差值能够尽可能的小,以达到最佳训练效果。给出 10 个球员的战斗力,如果你是教练,你该如何分队,才能达到最佳训练效果?请说出该分队方案下的最小战斗力差值。
输入描述10 个篮球队员的战斗力(整数,范围[1,10000])战斗力之间用空格分隔,如:10 9 8 7 6 5 4 3 2 1。
不需要考虑异常输入的场景。
输出描述输出描述最小的战斗力差值,如:1。
补充说明
------------------------------------------------------
示例
示例1
输入10 9 8 7 6 5 4 3 2 1
输出1
说明1、2、5、9、10 分为一队,3、4、6、7、8分为一队,两队战斗力之差最小,输出差值1。备注:球员分队方案不唯一,但最小战斗力差值固定是1。


解读与分析

题目解读

10 个数字分成两组,每组 5 个数字,使两组数字之和的差值最小。请输出最小差值。 

分析与思路

本题与《https://athena.blog.csdn.net/article/details/132807904华为OD机考算法题:MVP争夺战https://athena.blog.csdn.net/article/details/132807904》类似,都是把数字分组,要求分组后的数字达到某种要求。不同的是,在《MVP争夺战》中,每组的数字个数是不定的,而在本题中,每组数字的个数是固定的,都是 5。相对而言,本题的思路和实现更简单。

从 10 个数中,穷尽所有的情况,每种情况计算一下两队的差值,找出差值最小的那个。

假设这两组分别为 A 组和 B 组,这 10 个数字分别为 T_{1}T_{2}……T_{10}
我们穷尽的顺序是,先把 T_{1} 放到 A 组,然后基于剩下的 9 个数字,采用相同的方法,穷尽 9 个数字取 4 个的所有可能情况。这样从 10 个数字中取 5 个的情况就降维成了 9 个数字取 4 个,同样的方法,最后可以降为成 6 个数字里取 1 个。
以上是 T_{1}  放到 A 组的情况,接下来就是 T_{2} 放到 A 组,然后从 T_{3} 到 T_{10} 这 8 个数字中,穷尽 8 个数字 取 4 个数字的所有可能情况。

每遍历一种情况,计算两组的数字之差(设为 diff)时,如果 diff 比之前遍历时计算的最小差(设为minDiff)更小,那么更新 minDiff 的值。遍历完所有情况后,最终求得的 minDiff 值即为题目所要求的输出。这种算法遍历的次数为 C_{10}^{5}

在以上的遍历算法中,存在重复计算的情况。题目只关心两组的差值,所以在任意一种组合,把 A 组的数字和 B 组的数字交换,可以看做是同一种情况。如 T_{1}T_{2}T_{3}T_{4}T_{5} 放到 A 组 与 T_{6}T_{7}T_{8}T_{9}T_{10} 放到 A 组其实是等同的。为了减少重复,我们可以假设 A 组的数字之和小于或等于 B 组的数字之和,即 A 组数字之和小于或等于所有数字之和(设为 sum)的一半。那么在穷举所有情况时,发现 A 组数字之和已经超过 (sum/2),那么就可以忽略这种组合了。这样可以减少一半遍历,遍历次数变为 \frac{C_{10}^{5}}{2}

既然需要保证A组数字之和小于或等于 (sum/2),我们可以先对数字进行从小到大排序,进一步减少遍历次数。

综上,此算法的遍历次数不超过 \frac{C_{10}^{5}}{2}。下面,我们计算一下最大遍历次数。

由组合计算公式 C_{m}^{n} = \frac{m!}{n!(m-n)!},可以得出  \frac{C_{10}^{5}}{2} = \frac{ 10!}{(5!)(5!)*2} = 126,最多不超过 126 次。


代码实现

Java代码

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

/**
 * 篮球比赛
 * @since 2023.09.15
 * @version 0.1
 * @author Frank
 *
 */
public class BasketballGame {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		while (sc.hasNext()) {
			String input = sc.nextLine();
			String[] strNumbers = input.split( " " );
			// 此处 count == numbers.count,可以完全不用考虑 count.
			processBasketballGame( strNumbers );
		}
	}
	
	private static void processBasketballGame( String strNumbers[] )
	{
		Integer[] numbers = new Integer[strNumbers.length];
		int sum = 0;
		for( int i = 0; i < numbers.length; i ++ )
		{
			numbers[i] = Integer.parseInt( strNumbers[i] );
			sum += numbers[i];
		}
		
		Arrays.sort( numbers );
		int minDiff = sum;	// minDiff 一定小于 sum
 		
		List<Integer> numList = new ArrayList<Integer>( Arrays.asList( numbers ) );
		
		minDiff = getMinDiff( sum, minDiff, 0, 0, numList );
		System.out.println( minDiff );
	}

	
	private static int getMinDiff( int SUM, int minDiff, int currentSum, int currentCnt, List<Integer> numbers )
	{			
		if( currentCnt + numbers.size() < 5 )
		{
			return SUM;
		}
		
		List<Integer> tmpRemovedNumber = new ArrayList<Integer>(); 
		
		for( int i = 0; i < numbers.size(); i ++ )
		{
			int tmpMinDiff = SUM;
			int tmpNumber = numbers.get( i );
			
			int tmpCurrentSum = currentSum + tmpNumber;
			int tmpCurrentCnt = currentCnt + 1;
			
			if( tmpCurrentSum * 2 > SUM )
			{
				break; // 可以break,因为后面的数字更大。
			}
				
			if( tmpCurrentCnt == 5 )
			{
				tmpMinDiff = SUM - tmpCurrentSum * 2;
				if( tmpMinDiff < minDiff )
				{
					minDiff = tmpMinDiff;
					continue;
				}
			}
			
			tmpRemovedNumber.add( tmpNumber );
			numbers.remove( i );
			tmpMinDiff = getMinDiff( SUM, minDiff, tmpCurrentSum, tmpCurrentCnt, numbers );
			if( tmpMinDiff < minDiff )
			{
				minDiff = tmpMinDiff;
			}
//			numbers.add( i, tmpNumber );  // 不必加回去,可以减少一半的穷举数
		}		
		
		for( int i = 0; i < tmpRemovedNumber.size(); i ++ )
		{
			numbers.add( i, tmpRemovedNumber.get( i ) );
		}
		return minDiff;
	}
	
}

JavaScript代码

const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void async function() {
    while (line = await readline()) {
        // count 可以忽略
        var strNumbers = line.split(" ");
        processBasketballGame(strNumbers);
    }
}();

function processBasketballGame(strNumbers) {
    var numbers = new Array();
    var sum = 0;
    for (var i = 0; i < strNumbers.length; i++) {
        numbers[i] = parseInt(strNumbers[i]);
        sum += numbers[i];
    }

    numbers.sort();
    var minDiff = sum; // minDiff 一定小于 sum

    minDiff = getMinDiff(sum, minDiff, 0, 0, numbers);
    console.log(minDiff);
}

function getMinDiff(SUM, minDiff, currentSum, currentCnt, numbers) {
    if (currentCnt + numbers.length < 5) {
        return SUM;
    }

    var tmpRemovedNumber = new Array();

    for (var i = 0; i < numbers.length; i++) {
        var tmpMinDiff = SUM;
        var tmpNumber = numbers[i];

        var tmpCurrentSum = currentSum + tmpNumber;
        var tmpCurrentCnt = currentCnt + 1;

        if (tmpCurrentSum * 2 > SUM) {
            break; // 可以break,因为后面的数字更大。
        }

        if (tmpCurrentCnt == 5) {
            tmpMinDiff = SUM - tmpCurrentSum * 2;
            if (tmpMinDiff < minDiff) {
                minDiff = tmpMinDiff;
                continue;
            }
        }

        tmpRemovedNumber.push(tmpNumber);
        numbers.splice(i, 1);
        tmpMinDiff = getMinDiff(SUM, minDiff, tmpCurrentSum, tmpCurrentCnt, numbers);
        if (tmpMinDiff < minDiff) {
            minDiff = tmpMinDiff;
        }
    }

    for (var i = 0; i < tmpRemovedNumber.length; i++) {
        numbers.splice(i, 0, tmpRemovedNumber[i]);
    }
    return minDiff;
}

(完)

更多推荐

【计算机网络 - 自顶向下方法】计算机网络和因特网

目录1.WhatistheInternet?1.1因特网的具体构成1.2因特网的功能2.Networkcore2.1基本介绍2.2分组交换2.2.1序列化时延2.2.2排队延迟和丢包2.2.3分组交换的优缺点2.3电路交换2.3.1基本概念2.3.2电路交换网络中的复用2.3.3电路交换文件传输时间2.3.4分组交换与

SpringBoot整合Redis,基于Jedis实现redis各种操作

前言(三步教你学会redis,主打一个实用)springboot整合redis步骤,并基于jedis对redis数据库进行相关操作,最后分享非常好用、功能非常全的redis工具类。第一步:导入maven依赖<!--springboot整合redis--><dependency><groupId>org.springfr

C++11的半同步半异步线程池

C++11的半同步半异步线程池简介同步队列Take函数Add函数Stop函数SyncQueue完整代码线程池主函数测试简介半同步半异步线程池用的比较多,实现也比较简单。其中同步层包括同步服务层和排队层,指的是将接收的任务排队,将所有的任务排队到一个队列中,等待处理;异步层指多个线程处理任务,异步处理层从同步层取出任务,

shell编程之循环

循环是当循环控制条件为真时,一系列命令迭代执行的代码块。1、for循环语法:forargin[list]这是shell中最基本的循环结构,它与C语言形式的循环有着明显的不同。forargin[list]docommand(s)...done在循环的过程中,arg会从list中连续获得每一个变量的值。forargin"$

深拷贝与浅拷贝,就是这么简单

目录1.拷贝的概念2.浅拷贝2.1.浅拷贝的定义2.2.浅拷贝的实现方式2.3在内存中:3.深拷贝3.1.深拷贝的定义3.2.深拷贝的实现方式3.3在内存中4.深拷贝与浅拷贝的区别5.原型模式与深浅拷贝的关系6.总结1.拷贝的概念在编程中,拷贝(或复制)是常见的操作之一。拷贝操作用于创建一个新对象或数据结构,使其具有与

Mybatis-Plus入门(1)

单表的CRUD功能代码重复度很高,也没有什么难度。而这部分代码量往往比较大,开发起来比较费时。因此,目前企业中都会使用一些组件来简化或省略单表的CRUD开发工作。目前在国内使用较多的一个组件就是MybatisPlus.官方网站如下:简介|MyBatis-Plus当然,MybatisPlus不仅仅可以简化单表操作,而且还

【NLP入门教程】目录

当今,自然语言处理(NaturalLanguageProcessing,NLP)已经成为计算机科学与人工智能领域的重要研究方向之一。它涉及计算机如何理解、分析和生成人类语言,使得计算机可以与人类进行自然而流畅的交流。NLP的应用范围广泛,涵盖机器翻译、文本分类、情感分析、问答系统、语音识别等诸多领域。本教程旨在为初学者

【完全攻略】畅游NLP海洋:HuggingFace的快速入门

目录前言一、HuggingFace介绍1-1、HuggingFace的介绍1-2、安装二、Tokenizer分词库:分词工具2-0、加载BertTokenizer:需要传入预训练模型的名字2-1、使用Tokenizer对句子编码:2-2、使用增强Tokenizer对句子编码:2-3、批量编码单个句子:2-4、添加新词:

【Cocos Creator 3.5实现赛车游戏】10.实现汽车节点的运动逻辑

转载知识星球|深度连接铁杆粉丝,运营高品质社群,知识变现的工具项目地址:赛车小游戏-基于CocosCreator3.5版本实现:课程的源码,基于CocosCreator3.5版本实现上一节的学习后,您已经完成了对汽车节点的控制逻辑,所在在这一章您将会实现让汽车节点去响应对汽车的控制。在这之前您需要想一下真实世界中汽车的

IntelliJ IDEA 2023 年下载、安装教程、好用插件推荐

文章目录下载与安装IDEA常用插件推荐AlibabaJavaCodingGuidelines(阿里巴巴Java开发规约)KeyPromoterX(IDEA快捷键提示)Translation(翻译插件)SaveActions(优化保存插件)CodotaAIAutocomplete(代码自动提示和推荐)Autofillin

Python基于Flask的高校舆情分析,舆情监控可视化系统

目录一、前言二、使用Python爬取舆情数据1.安装requests库2.分析数据3.爬取数据三、通过代理IP提高数据爬取效率1.获取代理IP2.使用代理IP四、使用Flask框架实现舆情监控可视化系统五、使用MongoDB存储数据六、总结一、前言在当今社会,舆情监控越来越被重视。随着互联网技术的发展,我们从传统媒体渠

热文推荐