bilibili 笔试题 [编程题]孙悟空的徒弟 二分好题

链接:https://www.nowcoder.com/questionTerminal/7b841a840db64f5ebec77f6ee7e307e2
来源:牛客网

[编程题]孙悟空的徒弟

热度指数:2640 时间限制:C/C++ 3秒,其他语言6秒 空间限制:C/C++ 128M,其他语言256M
算法知识视频讲解

打败魔人布欧以后,孙悟空收了n个徒弟,每个徒弟战斗力各不相同。他教导所有的徒弟和体术,合体后战斗力为原战斗力相乘。任何两个徒弟都可以合体,所以一共有n*(n-1)/2种合体徒弟。有一天,他想考验一下孙悟天战斗力如何,希望在所有n*(n-1)/2种合体徒弟中选择战斗力第k高的,与孙悟天对战。可是孙悟空徒弟太多了,他已然懵逼,于是找到了你,请你帮他找到对的人。

输入描述:

第一行两个int。徒弟数量:n <= 110^6;战斗力排名:k <= n(n-1)/2
第二行空格分隔n个int,表示每个徒弟的战斗力。

输出描述:

战斗力排名k的合体徒弟战斗力。

示例1
输入

5 2
1 3 4 5 9

输出

36


题意

本题和上面那道leetcode786类似,一个是乘法一个是除法,但本题相对简单

给定一个数组,任意两个数字相乘会得到M = n*(n-1)/2个数字,在这M个数字中找K大值

注意数据范围很大 n <= 1e6,要求使用 复杂度 O (N log N)以内的算法

思路代码
  • 首先,答案具有单调性,我们考虑二分答案,猜mid
  • 问题变成了 判断有多少对(cnt)数字乘积大于mid的判定性问题
    • 如果cnt大于K说明猜的答案小了,就猜大一点
    • 反之就说明答案大了,就猜小一点

那么我们如何高效的check呢 ?

  • 先把数组递减排序

  • 同样具有如下所示的局部单调性

    //递减排序 [ 9, 5, 4, 3, 1 ], 后每一行都呈现出下降趋势
    9*5=45,   9*4=36,   9*3=27,   9*1=9   //固定 lef, 乘积随着rig递增而递减
              5*4=20,   5*3=15,   5*1=5
                        4*3=12,   4*1=4
                                  3*1=3
    
  • 因此我们可以枚举左端点并且二分右端点就可以O(nlogn)的求出比mid大的乘积个数

  • 最后算下来总的复杂度是O( log(W) * n log n),比正解多了一个log的复杂度,

    出题人比较仁慈没有卡掉,官方给出的标程是滑动窗口优化的O(n)check(我看不懂)

//核心代码
int check(int key) { // O ( N log N )的返回比 key 大的乘积的个数
	int cnt = 0;
	for(int i=1; i<n; i++) {
		int p = a[i], lef = i, rig = n, mid, pos = -1;
		while(lef <= rig) {
			mid = (lef + rig) >> 1;
			if(p * a[mid] > key) {
				pos = mid;
				lef = mid + 1;
			} else 
				rig = mid - 1;
		}
		cnt += (~pos && pos>i && pos<=n) ? (pos-i) : 0;
	}
	return cnt;
}
//二分答案
int lef = a[n], rig = a[1]*a[2], mid, ans = lef;	
while(lef <= rig) {
    mid = (lef + rig) >> 1;
    int ret = check(mid);
    if(ret < m) {
        ans = mid;
        rig = mid - 1;
    } else 
        lef = mid + 1;
}
printf("%d\n", ans);

完整代码如下

#define debug
#ifdef debug
#include <time.h>
#endif

#include <iostream>
#include <algorithm>
#include <vector>
#include <string.h>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <math.h>

#define MAXN ((int)1e6+7)
#define ll long long 
#define int long long
#define INF (0x7f7f7f7f)
#define fori(lef, rig) for(int i=lef; i<=rig; i++)
#define forj(lef, rig) for(int j=lef; j<=rig; j++)
#define fork(lef, rig) for(int k=lef; k<=rig; k++)
#define QAQ (0)

using namespace std;

#define show(x...) \
	do { \
	   cout << "\033[31;1m " << #x << " -> "; \
	   err(x); \
	} while (0)

void err() { cout << "\033[39;0m" << endl; }
template<typename T, typename... A>
void err(T a, A... x) { cout << a << ' '; err(x...); }

namespace FastIO{

	char print_f[105];
	void read() {}
	void print() { putchar('\n'); }

	template <typename T, typename... T2>
	   inline void read(T &x, T2 &... oth) {
		   x = 0;
		   char ch = getchar();
		   ll f = 1;
		   while (!isdigit(ch)) {
			   if (ch == '-') f *= -1; 
			   ch = getchar();
		   }
		   while (isdigit(ch)) {
			   x = x * 10 + ch - 48;
			   ch = getchar();
		   }
		   x *= f;
		   read(oth...);
	   }
	template <typename T, typename... T2>
	   inline void print(T x, T2... oth) {
		   ll p3=-1;
		   if(x<0) putchar('-'), x=-x;
		   do{
				print_f[++p3] = x%10 + 48;
		   } while(x/=10);
		   while(p3>=0) putchar(print_f[p3--]);
		   putchar(' ');
		   print(oth...);
	   }
} // namespace FastIO
using FastIO::print;
using FastIO::read;

int n, m, Q, K;

int a[MAXN];

int check(int key) {
	int cnt = 0;
	for(int i=1; i<n; i++) {
		int p = a[i], lef = i, rig = n, mid, pos = -1;
		while(lef <= rig) {
			mid = (lef + rig) >> 1;
			if(p * a[mid] > key) {
				pos = mid;
				lef = mid + 1;
			} else 
				rig = mid - 1;
		}
		cnt += (~pos && pos>i && pos<=n) ? (pos-i) : 0;
		// cnt += (~pos && pos>i && pos<=n) ? (n-(pos-i)) : 0;
		// show(i, pos, (pos-i+1));
	}
	return cnt;
}

bool cmp(int x, int y)  { return x > y; }

signed main() {
#ifdef debug
	freopen("test.txt", "r", stdin);
	clock_t stime = clock();
#endif
	read(n, m);
	for(int i=1; i<=n; i++) read(a[i]);
	sort(a+1, a+1+n, cmp);
#if 0
	for(int i=1; i<=n; i++) {
		for(int j=i+1; j<=n; j++) {
			printf("%lld  ", a[i]*a[j]);
		}
		printf("\n");
	}
	int key = 5;
	int p = check(key);
	// show(key, p);
#endif
	int lef = a[n], rig = a[1]*a[2], mid, ans = lef;	
	while(lef <= rig) {
		mid = (lef + rig) >> 1;
		int ret = check(mid);
		// show(mid, ret);
		if(ret < m) {
			ans = mid;
			rig = mid - 1;
		} else 
			lef = mid + 1;
	}
	printf("%d\n", ans);





#ifdef debug
   clock_t etime = clock();
   printf("rum time: %lf 秒\n",(double) (etime-stime)/CLOCKS_PER_SEC);
#endif 
   return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值