题目描述
下面的图形是著名的杨辉三角形:
如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列: 1,1,1,1,2,1,1,3,3,1,1,4,6,4,1,⋯
给定一个正整数 N,请你输出数列中第一次出现 N是在第几个数?
输入描述
输入一个整数 NNN。
输出描述
输出一个整数代表答案。
输入输出样例
示例:输入6,输出13
评测用例规模与约定
对于 20% 的评测用例,1≤N≤10; 对于所有评测用例,1≤N≤1000000000
运行限制
最大运行时间:1s
最大运行内存: 256M
初步分析
如果直接利用每个数字等于上一行的左右两个数字之和来逐个遍历暴力求解,对于N等于10000000000的情况,没有一个小时电脑算不出来,不符合运行时间限制的1s的要求。
所以要解这一题,必须通过杨辉三角的性质来解决。
杨辉三角的性质
-
杨辉三角的第n行有n+1项,比如第0行有1项,所以杨辉三角的每一行呈等差数列式递增
-
杨辉三角的前n行的数字个数(等差数列前n项和):[(2+n)(n+1)]/2
-
杨辉三角的第n行第m个数字可以使用C(n,m)来表示,其中C(n,m)的计算方式是:
-
杨辉三角左右对称,对称轴是C(2k,k), k=0,1,2,3,…n-1,n
深入分析
-
如果想要求杨辉三角中某一个数字的编号,需要根据杨辉三角的特性来求,
第一步,需要求出这个数字是第几行的第几个数字,比如说我们使用某种方法求出数字N位于第n行的第m个位置;
第二步,求n-1行之前一共有多少个数字(包括第n-1行),(利用等差数列求和公式可以知道n-1行之前一共有[n(n+1)]/2个数字)
第三步,N位于第n行的第m个位置,所以N的编号就是:[n(n+1)]/2+(m+1)
-
因为题目让我们求正整数 N第一次出现 的位置,那么这个位置一定在对称轴的左边。
-
如果沿着对称轴向左下角延申,数字会越来越大
-
斜行和对称轴的关系:第k斜行在对称轴的位置的数字是:C(2k,k)。
-
所以如果拿着N和对称轴上的每一个数字比较(从上往下),在第k-1斜行时N比对称轴上的数字大,在第k行时N比对称轴上的数字小,说明N一定在第k斜行之前(不包括第k斜行),但是不一定在第k-1斜行,有可能在第1,2,3,…,k-1斜行中的任意一行。
-
这时候只需要倒过来从第k-1斜行开始,k-1,k-2,…逐行查找,直到找到N为止即可
-
因为在每一个斜行上的数字可能很多,所以可以在每一个斜行上使用二分查找提高检索速度。在每一个斜行使用二分查找的方法:
二分查找必须有一个查找上限和一个查找下限,查找下限已经有了(每一个斜行在对称轴的位置的数字最小,它的m是下限)
查找上限:求查找上限必须要知道最坏的查找情况是多少。
-
在编写代码时,需要计算C(n,m),为了使计算更加简单,我们把公式化简一下:
代码
完整的代码如下:
import datetime
def C(n, m):
"""第一步,编写一个计算C(n,m)的函数"""
result = 1
num = m
for i in range(0, num):
result *= (n - i) / (m - i)
return int(result) # 因为计算出来的结果是浮点数,但是我们要的是整型,所以类型转换
def searchLine():
"""第二步,通过对称轴找到N所在的斜行最多不会超过多少"""
"""对称轴的通式:C(2k,k),k=0,1,2,..."""
num = 0
while True:
if N < C(2 * num, num): # 如果N比某一个斜行上对称轴上的值小了,说明N一定不会在这一斜行及其以后的斜行出现了,因为最小的值都比N大
break
num += 1
print('在第', int(num-1), '斜行之前(包括)')
return num-1 # N会出现在0,1,2,3,..,num中的任意一斜行斜行是作为C(n,m)中的m的
def binarySearch(low, high, n, k): # low:查找下限(是C(n,m)中的n),high:查找上限(也是C(n,m)中的n),n:待查找的数字,k:目前正在第k斜行行进行查找(是C(n,m)中的m)
"""第三步,定义一个二分查找函数,"""
while low <= high:
middle = (low + high) // 2 # 因为第几行第几行只能是整数,不存在3.5行
middleNumber = C(middle, k) # 第k斜行的第middle行的数字,也可以说是第middle+1行的第k+1个数字
if n == middleNumber: # 如果n和第k斜行的第middle行的数字相等
"""算出编号(公式是:n(n-1)/2+(m+1))"""
print(int(middle * (middle + 1) / 2) + k + 1)
return True
elif n > middleNumber:
low = middle + 1
elif n < middleNumber:
high = middle - 1
return False # 没找到返回False
def searchLocation():
"""第四步,从斜行num开始,从里到外,逐个斜行开始寻找N的位置"""
"""在每一个斜行内,使用二分查找"""
maxLine = searchLine() # 获得最大斜行,从第一斜行开始
for m in range(maxLine, -1, -1): # maxLine,maxLine-1,..,2,1
flag = binarySearch(2*m, N, N, m)
if flag: # 如果找到了
return
if __name__ == '__main__':
N = int(input())
startTime = datetime.datetime.now()
searchLocation()
endTime = datetime.datetime.now()
print('查找时间:', (endTime - startTime).microseconds*10**-3, 'ms')
最后附上使用 每个数字等于上一行的左右两个数字之和 来逐个遍历暴力求解的代码,你可以看看这种方法到底需要多少时间才能运行出来,反正我运行了20分钟是没有运行出来。
"""for循环,求出N在第几斜行(每次加以行),
当出现斜行的第一个数大于N时(此时处在第i斜行,从零开始),
此时N就在第i-1斜行,遍历i-1斜行就行了"""
import time
s = [1, 1] # 第二行的数
n = int(input('输入n的值:'))
flag = True
count = 3 # 前两行的总个数是3
fl = 0 # 如果fl > 10000000000 直接退出就行
startTime = time.time()
if n == 1:
print(1)
else:
while flag:
if fl > 10000000000:
break
temp = [1]
length = len(s)
for i in range(0, length - 1):
temp.append(s[i] + s[i + 1])
if temp[-1] == n: # 如果找到n了退出循环
flag = False
break
temp.append(1)
count += len(temp)
s = temp # 让s存储下一行的数据
fl += 1
print(count - 1)
endTime = time.time()
print('运行时间:', endTime - startTime)
参考链接
https://blog.csdn.net/m0_62277756/article/details/122690022