1945 奶牛棒球(枚举 + 双指针)

1. 问题描述:

农夫约翰的 N 头奶牛排成一排,每头奶牛都位于数轴中的不同位置上。它们正在练习投掷棒球。农夫约翰观看时,观察到一组三头牛 (X,Y,Z) 完成了两次成功的投掷。牛 X 把球扔给她右边的牛 Y,然后牛 Y 把球扔给她右边的牛 Z。约翰指出,第二次投掷的距离不少于第一次投掷的距离,也不超过第一次投掷的距离的两倍。请计算共有多少组牛 (X,Y,Z) 可能是约翰所看到的。

输入格式

第一行包含整数 N。接下来 N 行,每行描述一头牛的位置。

输出格式

输出奶牛三元组 (X,Y,Z) 的数量。 (X,Y,Z) 需满足,Y 在 X 的右边,Z 在 Y 的右边,并且从 Y 到 Z 的距离在 [XY,2XY] 之间,其中 XY 表示从 X 到 Y 的距离。

数据范围

3 ≤ N ≤ 1000,奶牛所在的位置坐标范围 [0,10 ^ 8]。

输入样例:

5
3
1
10
7
4

输出样例:

4
样例解释
四个可能的奶牛三元组为:1−3−7,1−4−7,4−7−10,1−4−10。
来源:https://www.acwing.com/problem/content/1947/

2. 思路分析:

分析题目可以知道我们需要枚举出所有满足条件的三元组,数据范围为10 ^ 3,所以时间复杂度只能够控制在O(n ^ 2)或者O(n ^ 2 * logn),首先我们需要使用数组或者列表存储所有的点的位置,然后从小到大排序,方便从小到大枚举所有点。首先可以使用两层循环枚举其中的两个点(时间复杂度为O(n ^ 2)),这样相当于这两个点是固定的,所以主要是枚举第三个点,由题目可知第三个点是有一定的取值范围的,如下图所示:

由题目中的约束可以得到:y - x <= z - y <= 2(y - x),整理一下就可以得到第三个点的取值范围:2y - x <= z <= 3y - 2x,并且我们在枚举的时候前两个点一定是有序的,所以y一定在x的后面,而2y - x与3y - x也在y的后面,所以z在一定的取值范围内,问题就转化为了在如何快速确定z的取值范围,当我们确定z的取值范围之后那么范围中的点都是符合要求的,枚举前面两个点的时间复杂度就是n ^ 2,所以我们需要考虑logn的算法来确定第三个点的取值范围,可以考虑二分或者双指针来确定z的取值边界,因为双指针比较好写一点所以我们可以考虑能否使用双指针来解决,我们可以固定其中一个点x,当y向后的时候z也是向后走的,所以可以使用双指针来确定z的取值范围,对于区间左端点我们可以找到大于等于2y - x的最小值,对于右端点也是类似地我们可以找到大于3y - 2x的最小值然后左边的那个位置就是小于等于3y - 2x的最大值(这两者是可以相互转化的),这样我们就可以使用双指针分别确定第三个点的左边界和右边界,区间长度就是第三个点满足的个数。

3. 代码如下:

class Solution:
    def process(self):
        n = int(input())
        p = list()
        for i in range(n):
            p.append(int(input()))
        # 从小到大排序
        p.sort()
        i, j = 0, 1
        res = 0
        while i + 2 < n:
            j = i + 1
            while j + 1 < n:
                l, r = j + 1, j + 1
                # 找到z的左边界
                while l < n and p[l] - p[j] < p[j] - p[i]: l += 1
                # 找到z的右边界    
                while r < n and p[r] - p[j] <= 2 * (p[j] - p[i]): r += 1
                # 注意r的左边的那个位置才是满足要求的位置所以r - l就是当前区间的长度的满足的个数
                res += r - l
                j += 1
            i += 1
        return res


if __name__ == '__main__':
    print(Solution().process())
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值