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())