题目部分
题目 | 生日礼物 |
难度 | 易 |
题目说明 | 小牛的孩子生日快要到了,他打算给孩子买蛋糕和小礼物,蛋糕和小礼物各买一个,他的预算不超过x元。蛋糕 cake 和小礼物 gift 都有多种价位的可供选择。 |
输入描述 | 第一行表示cake的单价,以逗号分隔。 第二行表示gift的单价,以逗号分隔。 第三行表示 x 预算。 |
输出描述 | 输出数字表示购买方案的总数。 |
补充说明 | 1 ≤ cake.length ≤ 1 ≤ gift.length ≤ 1 ≤ cake[], gift[] ≤ 1 ≤ X ≤2 * |
------------------------------------------------------ | |
示例 | |
示例1 | |
输入 | 10,20,5 5,5,2 15 |
输出 | 6 |
说明 | 小牛有6种购买方案,所选蛋糕与所选礼物在数组中对应的下标分别是: 第1种方案: cake[0]+ gift[0] = 10 + 5 = 15; 第2种方案: cake[0]+ gift[1] = 10 + 5 = 15; 第3种方案: cake[0]+ gift[2] = 10 + 2 = 12; 第4种方案: cake[2]+ gift[0] = 5 + 5 = 10; 第5种方案: cake[2]+ gift[1] = 5 + 5 = 10; 第6种方案: cake[2]+ gift[2] = 5 + 2 = 7。 |
解读与分析
题目解读:
给定 2 组数据,在两组数据中各取 1 个,使 2 个数字之和不高于指定值 x。
分析与思路:
如果逐个遍历两组数字中每一种组合,时间复杂度为 O()。由于 cake、gift 的最大长度为 ,当它们长度较大时,性能会变得很差,我们有更好的方法。
实现如下:
1. 对 cake、gift 进行排序,从大到小。
2. 对 cake 进行二分查找,从 cake 中找到值小于 x 的最大值所对应的下标,设为 minCake。此时,计算出cake 中可能买的 cake 下标范围是 [ minCake, cake.length - 1]。对处于下标范围的 cake 遍历(假设当前正遍历到的下标为 i),进行步骤 3 操作。
3. 如步骤 2,假设当前的 cake 是 cake[i],那么所能购买的 gift 最大价格为 x - cake[i],设为 giftMaxValue。使用二分查找,找到价格不高于 giftMaxValue 的最大值,设下标为 j,那么 gift 的下标可选范围是 [ j, gift.length -1 ]。即意味着当选择 cake[i] 时,gift 的可选个数是 gift.length - j。
4. 继续遍历下一个 cake,即下一个 i,如步骤 3 中,重新计算 giftMaxValue,此时的 giftMaxValue 一定大于前一个计算出来的 giftMaxValue,即意味着这一轮中 gift 下标的 j 的范围一定在 0 和上一轮计算出来的 j 之间,对 [ 0, 上一轮计算的 j ] 进行二分查找,找到不大于 giftMaxValue 的最大值(即最下小标),即计算出选择当前 cake 时,gift 的可选个数。
5. 继续进行步骤 4,直至遍历完所有的 cake,最终所有可选个数之和,即为购买方案总数。
这种方案的时间复杂度为O(nlogn),空间复杂度O(n)。
代码实现
Java代码
import java.util.Arrays;
import java.util.Scanner;
import java.util.Comparator;
/**
* 生日礼物
*
* @since 2023.10.31
* @version 0.1
* @author Frank
*
*/
public class BirthdayGift {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String input = sc.nextLine();
String[] strNumber = input.split(",");
Integer[] cakes = new Integer[strNumber.length];
for (int i = 0; i < strNumber.length; i++) {
cakes[i] = Integer.parseInt(strNumber[i]);
}
input = sc.nextLine();
strNumber = input.split(",");
Integer[] gifts = new Integer[strNumber.length];
for (int i = 0; i < strNumber.length; i++) {
gifts[i] = Integer.parseInt(strNumber[i]);
}
input = sc.nextLine();
int x = Integer.parseInt(input);
processBirthdayGift(cakes, gifts, x);
}
}
private static void processBirthdayGift(Integer[] cakes, Integer[] gifts, int x) {
// 从大到小排序
Comparator<Integer> comp = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
};
Arrays.sort( cakes, comp );
Arrays.sort( gifts, comp );
int cakesFromIndex = findMaxValueIndex( x - 1, cakes.length - 1, cakes );
int sum = 0;
int highIndex = gifts.length - 1;
for( int i = cakesFromIndex; i < cakes.length; i ++ )
{
int giftMaxValue = x - cakes[i];
highIndex = findMaxValueIndex( giftMaxValue, highIndex, gifts );
sum += ( gifts.length - highIndex ) ;
}
System.out.println( sum );
}
private static int findMaxValueIndex( int maxValue, int highIndex, Integer[] srcArr )
{
// 已排序从大到小的数组,取值范围在 [0, fromIndex]
int low = 0;
int high = highIndex;
int mid = ( low + high ) / 2;
while( low <= high )
{
mid = ( low + high ) / 2;
if( maxValue == srcArr[mid] )
{
// 相等,还需判断所有相等的情况
while( mid >= 1 && srcArr[mid - 1 ] == srcArr[mid])
{
mid --;
}
return mid;
}else if( maxValue > srcArr[mid] )
{
high = mid - 1;
}else
{
low = mid + 1;
}
}
// 此时 low > high
if( high < 0 )
{
return 0;
}
if( srcArr[ high ] < maxValue )
{
return high;
}
if( srcArr[low] < maxValue )
{
return low;
}
if( low + 1 <= srcArr.length -1 )
{
return low + 1;
}else
{
return srcArr.length -1;
}
}
}
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()) {
var strNumber = line.split(",");
var cakes = new Array();
for (var i = 0; i < strNumber.length; i++) {
cakes[i] = parseInt(strNumber[i]);
}
line = await readline();
var strNumber = line.split(",");
var gifts = new Array();
for (var i = 0; i < strNumber.length; i++) {
gifts[i] = parseInt(strNumber[i]);
}
line = await readline();
var x = parseInt(line);
processBirthdayGift(cakes, gifts, x);
}
}();
function processBirthdayGift(cakes, gifts, x) {
// 从大到小排序
var comp = function( a, b) {
return b - a;
};
cakes.sort( comp );
gifts.sort( comp );
var cakesFromIndex = findMaxValueIndex( x - 1, cakes.length - 1, cakes );
var sum = 0;
var highIndex = gifts.length - 1;
for( var i = cakesFromIndex; i < cakes.length; i ++ )
{
var giftMaxValue = x - cakes[i];
highIndex = findMaxValueIndex( giftMaxValue, highIndex, gifts );
sum += ( gifts.length - highIndex ) ;
}
console.log( sum );
}
function findMaxValueIndex(maxValue, highIndex, srcArr) {
// 已排序从大到小的数组,取值范围在 [0, fromIndex]
var low = 0;
var high = highIndex;
var mid = parseInt( (low + high) / 2 );
while (low <= high) {
mid = parseInt( (low + high) / 2 );
if (maxValue == srcArr[mid]) {
// 相等,还需判断所有相等的情况
while (mid >= 1 && srcArr[mid - 1] == srcArr[mid]) {
mid--;
}
return mid;
} else if (maxValue > srcArr[mid]) {
high = mid - 1;
} else {
low = mid + 1;
}
}
// 此时 low > high
if (high < 0) {
return 0;
}
if (srcArr[high] < maxValue) {
return high;
}
if (srcArr[low] < maxValue) {
return low;
}
// should never come here
if (low + 1 <= srcArr.length - 1) {
return low + 1;
} else {
return srcArr.length - 1;
}
}
(完)