[编程题]万万没想到之抓捕孔连顺

我叫王大锤,是一名特工。我刚刚接到任务:在字节跳动大街进行埋伏,抓捕恐怖分子孔连顺。和我一起行动的还有另外两名特工,我提议

  1. 我们在字节跳动大街的N个建筑中选定3个埋伏地点。
  2. 为了相互照应,我们决定相距最远的两名特工间的距离不超过D。

我特喵是个天才! 经过精密的计算,我们从X种可行的埋伏方案中选择了一种。这个方案万无一失,颤抖吧,孔连顺!
……
万万没想到,计划还是失败了,孔连顺化妆成小龙女,混在cosplay的队伍中逃出了字节跳动大街。只怪他的伪装太成功了,就是杨过本人来了也发现不了的!

请听题:给定N(可选作为埋伏点的建筑物数)、D(相距最远的两名特工间的距离的最大值)以及可选建筑的坐标,计算在这次行动中,大锤的小队有多少种埋伏选择。
注意:

  1. 两个特工不能埋伏在同一地点
  2. 三个特工是等价的:即同样的位置组合(A, B, C) 只算一种埋伏方法,不能因“特工之间互换位置”而重复使用

输入描述:

第一行包含空格分隔的两个数字 N和D(1 ≤ N ≤ 1000000; 1 ≤ D ≤ 1000000)

第二行包含N个建筑物的的位置,每个位置用一个整数(取值区间为[0, 1000000])表示,从小到大排列(将字节跳动大街看做一条数轴)

输出描述:

一个数字,表示不同埋伏方案的数量。结果可能溢出,请对 99997867 取模

输入例子1:

4 3
1 2 3 4

输出例子1:

4

例子说明1:

可选方案 (1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)

输入例子2:

5 19
1 10 20 30 50

输出例子2:

1

例子说明2:

可选方案 (1, 10, 20)

最直接的方法,三个for循环,但这肯定不行,时间复杂度太高了。

其实觉得这道题是一个排列组合问题,在合理的区间取出3个数进行组合。

比如第一个例子,1 2 3 4,是符合题意的,然后来一个公式,
在这里插入图片描述
就可以得出结果了。

所以我们找出合理的区间计算方案并加起来就行,但代码写起来会有一些改变,请往下看。

方法一:(这个方法会超时(牛客网提交得11.2分),但我觉得相对来说会比较容易理解一点)

import java.util.Scanner;
public class Main{
    public static void main(String [] args){
        Scanner sc=new Scanner(System.in);
        int N=sc.nextInt();
        int D=sc.nextInt();
        int[] coordinate=new int[N];
        for(int i=0;i<N;i++){
            coordinate[i]=sc.nextInt();
        }
        Long count=0L;
         /**
	      * 先从第一位开始,把它看为区间左端点,判断它的最大合理区间在哪里,判断条件是coordinate[j]-coordinate[left]
	      * 如果coordinate[j]-coordinate[left]是<=D的话,说明里面数都是符合题目的。
	      * 等循环找到最大区间后就计算方案,然后继续从第二位为区间左端点开始找。
	      * 此时有一个问题,有些会重复,比如说 1 2 3 4 这个例子,1为左区间时,有(2,3,4)这个组合
	      * 2为左区间时,也有这个组合。所以我们换一下思路,当1为区间左端点时,在它的合理区间(除去1),取两个数
	      * 和1组合,此时有Cn2种组法。
	      */
        
        for(int left=0;left<N-2;left++){
            int right=0;
            for(int j=left+2;j<N;j++){
               //这里是找left的最大合理区间
                if(coordinate[j]-coordinate[left]>D){
                    break;
                }
                right=j;
            }
            if(right-left>=2){
            	//right-left是算除了lfet的区间数量
            	//这个公式是计算Cn2的,把Cn2的公式化简就成了n*(n-1)/2
            	//要用long(这个我不是很懂,网上看到的)
                count+=(long)(right-left)*(long)((right-left)-1)/2;
            }
            
        }
        System.out.println(count%99997867);
    }
}

方法二:(参考网上的,自己改进了一点点)

其实思想差不多,说是用了滑动窗口这种方法(我也不是很理解,,)

我觉得这里主要是把区间的右端点(注意,是右端点,如果是左端点的话感觉会有问题的,虽然答案是一样)做固定点,比如说一开始是三个,1 2 3,以3为固定点,从 1 2里组合,只有一种,然后到 1 2 3 4 了,以4为固定点 ,从 1 2 3取两个组合,有3种,加起来就是4种了。

我之前一开始有一个地方是理解不了的,因为没有明确固定一个点(上面的方法有这个思想),后来想想,应该是有固定点的,不过是隐藏了,因为代码是算合理区间的数量-1的(本来合理区间的数量是右下标-左下标+1的,代码没有+1,所以应该是把那个1作为固定点了,我理解那个固定点就是区间右端点)。

import java.util.Scanner;
public class Main{
    public static void main(String [] args){
        Scanner sc=new Scanner(System.in);
        int N=sc.nextInt();
        int D=sc.nextInt();
        int[] coordinate=new int[N];
        for(int i=0;i<N;i++){
            coordinate[i]=sc.nextInt();
        }
        Long count=0L;
        int left=0;
        int right=2;
        while(right<N&&left<right) {
        	if(coordinate[right]-coordinate[left]>D) {
        		left++;
        	}else if (right-left<2) {
				right++;
			}else {
				int len=right-left;
				count+=(long)(len)*(long)(len-1)/2;
				right++;
			}
        }
        System.out.println(count%99997867);
    }
}

最后,因为知道自己的表达能力和理解能力比较弱,如果有什么错误的话,希望大家能多包容,多指出我的错误,谢谢。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值