递推和递归(学习笔记)

先上两道题巩固上一篇的内容

1.题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

小蓝有很多数字卡片,每张卡片上都是数字 00 到 99。

小蓝准备用这些卡片来拼一些数,他想从 11 开始拼出正整数,每拼一个,就保存起来,卡片就不能用来拼其它数了。

小蓝想知道自己能从 11 拼到多少。

例如,当小蓝有 3030 张卡片,其中 00 到 99 各 33 张,则小蓝可以拼出 11 到 1010,

但是拼 1111 时卡片 11 已经只有一张了,不够拼出 1111。

现在小蓝手里有 00 到 99 的卡片各 20212021 张,共 2021020210 张,请问小蓝可以从 11 拼到多少?

提示:建议使用计算机编程解决问题。

思路:

从1开始枚举,每次检查剩下的卡片能不能拼出这个数字。

如何求一个数的每个位置的数字?先对10 取模,个位上的数字就求出来了,再除以10,原本十位上的数字就变到了个位上,再对 10 取模...

比如2392      2392%10=2(个位数)

                     239%10=9(十位数)......

把当前拼的这个数每一位都拆出来,看看那个数字的卡片还够不够,不够的话就说明拼不了,这时候退出循环,所以最多拼到上一个数

题解:

a = [2021 for i in range(10)]
def check(x):
    while x > 0:
        now = int(x % 10)
        if a[now] > 0:
            a[now] -= 1
        else:
            return 0
        x = x // 10
    return 1  #满足check函数就return 1
cnt = 1
while check(cnt):
    cnt += 1  #这里的cnt是后加,比如50是满足题意的答案,但是check循环让cnt会多加一个1,所以在最后答案中要减1
print(cnt - 1)

2.

 思路优化:由于仅有两个不同的数字组成的时间,直接枚举两个数字a(3次)和b(1次),对于4位数而言,只有4种情况:aaab、aaba、abaa、baaa。 对于Y、D、H,分别利用上述的4个数字进行枚举,判断Y、D、H的合法性:       Y:数字无限制       D=mmdd,前两位mm为月份,范围是01-12,后两位dd为日期,根据当前年份、日期来确定当月天数。       H=hhmm,前两位hh为小时(范围00-23),后两位mm为分钟(范围00-59)

补充: 无论是否为闰年,28 29 和相对的92 98都是不合法的数字,其实无需考虑是否为闰年。 对于月份多少天的处理,我们可以使用数组预处理,这是一个技巧。  month={ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }

day_per_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
#检查日期D是否合法
def check_D(D):
   month = D // 100
   day = D % 100
   if month < 1 or month > 12:
       return 0
   if day < 1 or day > day_per_month[month]:
       return 0
   return 1
#检查时刻H是否合法
def check_H(H):
   h = H // 100
   m = H % 100
   if h < 0 or h > 23:
       return 0
   if m < 0 or m > 59:
       return 0
   return 1
ans = 0
#枚举第一个数字
for a in range(10):
   #枚举第二个数字
   for b in range(10):
       if a == b:
           continue
       #合法数量
       N_Y, N_D, N_H = 4, 0, 0 # N_Y等于4 因为年为aaab aaba abaa baaa 都符合题意
       A = [a, a, a, a]
       #构造月四种情况aaab、aaba、abaa、baaa
       for i in range(4):
           A[i] = b
           number = 0
           for j in A:
               number = number * 10 + j #将四种情况变成四位整数
           N_D += check_D(number)# 每种情况符合月日的进行记录
           N_H += check_H(number)# 符合时分的进行记录
           A[i] = a
       ans += N_Y * N_D * N_H # 三个位置的情况没有关联,所以是乘积。
print(ans)

正式进入递推和递归

递推算法的特点

1.一个问题的求解需要大量重复计算,在已知的条件和所求问题之间总存在着某种相互联系的关系,在计算时,我们需要找到这种关系,进行计算(递推关系式)。 即递推法的关键,就是找到递推关系式,这种处理方式能够将复杂的计算过程,转化为若干步骤的简单重复运送,充分利用计算机运行程序时的时间局部性和空间局部性。

递推算法的思想:

1.首要问题是先找到各个相邻数据项之间的递推关系;

2.递推关系避开了求通项公式的麻烦,且有些题目的通项公式很难求,或者不能进行求解;

3.将复杂问题分解为若干步骤的简单运算;

4.一般来说递推算法就是一种特殊的迭代算法

递推算法的一般步骤:

1. 根据题目确定数据项,并找到符合要求的递推关系式;

2.根据递推关系式设计递推程序;

3.根据题目找到递推的终点;

4.单次查询可以不进行存储,多次查询都要进行存储;

5.按要求输出答案即可。


递归算法: 递归算法是一种从自顶向下的算法,实际上是通过不停的直接调用或者间接的调用自身的函数,通过每次改变变量完成多个过程的重复计算,直到到达边界之后,结束调用。 与递推法相似的是,递归与递推都是将一个复杂过程分解为几个简单重复步骤进行计算。 递归算法的实现的核心是分治策略,即分而治之,将复杂过程分解为规模较小的同类问题,通过解决若干个小问题,进而解决整个复杂问题。

递归算法的思想:

1.将复杂计算过程转换为简单重复子过程;

2.找到递归公式,即能够将大问题转化为小问题的公式;

3.自上而下计算,在返回完成递归过程。

递归算法设计的一般步骤:

1.根据题目设计递归函数中的运算部分;

2.根据题目找到递归公式,题目可能会隐含给出,也可能需要自己进行推导;

3.找到递归出口,即递归的终止条件


经典的斐波那契数列

递推式:f(n) = f(n-1) + f(n-2)

打印第20个数:

递推:for循环

fib=[0 for i in range(25)]
fib[1]=fib[2]=1
for i in range(3,21):
    fib[i]=fib[i-1]+fib[i-2]
print(fib[20])

for循环计算20次

递归:定义函数

cnt = 0 #统计执行了多少次递归
def fib(n): #递归函数
    global cnt
    cnt+=1 
    if n==1 or n==2: 
        #到达终止条件,即最小问题
        return 1
    return fib(n-1)+fib(n-2) 
    #递归调用自己2次,复杂度O(2n)
print(fib(20))
print(cnt)

递归:计算

代码低效的原因:        

return fib (n-1) + fib (n-2) 递归调用了自己2次,倍增 计算fib(n)时,共执行了O(2^n)次递归

 递归的过程中做了重复工作,例如fib(3)计算了2次,其实只算1次就够了。 为避免递归时重复计算,可以在子问题得到解决时,就保存结果,再次需要这个结果时,直接返回保存的结果就行了,不继续递归下去。 这种存储已经解决的子问题结果的技 术称为“记忆化(Memoization)”。

记忆化是递归的常用优化技术。 动态规划也常常用递归写代码,记忆化 也是动态规划的关键技术。

data = [0 for i in range(25)]
cnt = 0
def fib(n):#/递归函数
    global cnt

    cnt += 1
    if data[n] != 0:
#记忆化搜索:已经算过,不用再算,直接返回结果
        return data[n]

    if n == 1 or n == 2:
        data[n] = 1
        return data[n]

    data[n] = fib(n-1) + fib(n-2)#继续递归
    return data[n]

print(fib(20))
print(cnt)
'''
全局变量是在函数外部定义的变量(没有定义在某一个函数内),所有函数内部都可以使用这个变量
一般来说,在函数中不能修改全局变量的值
但使用了global就可以在函数中修改全局变量的值了
'''

题目:

 

上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。

路径上的每一步只能从一个数走到下一层和它最近的左边的那个数或者右边的那个数。此外,向左下走的次数与向右下走的次数相差不能超过 1。

输入描述
输入的第一行包含一个整数 N(1≤N≤100),表示三角形的行数。

下面的 N 行给出数字三角形。数字三角形上的数都是 0 至 100 之间的整数。

输出描述
输出一个整数,表示答案。

输入输出样例
示例

输入

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出

27   <———这里应该为30
思路:

正向搜索,用m层暴力破解,容易超时

逆向搜索

 

我们从递推的思想出发,假设我们从顶层沿着某条路径已经走到了第 i 层,正向着 i+1 层前进, 两条可行路径中我们肯定会选择最大的方向前进,为此我们可以采用递推中的反向递推,即逆推的方式解决,设 a[i][j] 存放从 i,j 出发到达第 n 层的最大值。 我们可以写出递推式:

a[i][j] = max{a[i][j]+a[i+1][j],a[i][j]+a[i+1][j+1]}  

则 逆推到出发点 a[1][1] 为题目所求答案,即第一层到第 N 层的最大值 

 题解:

a = [[0] * 101] * 101
n = int(input())
for i in range(1, n+1):
    a[i] = list(map(int, input().split())) #split分割后形成字符,这里加个int使其变成数字
for i in range(n-1, 0, -1):  #从最后一层逆推
    for j in range(0, i):
        if a[i+1][j] > a[i+1][j+1]: #路径选择
            a[i][j] += a[i+1][j]
        else:
            a[i][j] += a[i + 1][j+1]
print(a[1][0])

这个题目有个漏洞,它给的样例输入的结果数据是错误的,但是平台有点死板,按照出错的数据去改,所以这道题在平台上拿不到AC,但是结果一定是对的

2. 


思路:6个字符,所以我们创建5个vector,分别用来存放1-5次的计算结果

 

 

a = [[] * 13] * 13  #这个只是个初始化的东西,空列表创建数量可以很多
c = input().split()
for i in range(6):

    if c[i] == 'A':
        a[i] = 1

    elif c[i] == 'J':
        a[i] = 11

    elif c[i] == 'Q':
        a[i] = 12

    elif c[i] == 'K':
        a[i] = 13

    else:
        a[i] = ord(c[i]) - ord('0')
ans = [[] for i in range(10)]
ans[0].append(a[0])
for i in range(1, 6):
    for j in range(len(ans[i - 1])):
        ans[i].append(ans[i - 1][j] + a[i])
        ans[i].append(ans[i - 1][j] - a[i])
        ans[i].append(ans[i - 1][j] * a[i])
        ans[i].append(int(ans[i - 1][j]) / a[i])

flag = 0
for j in range(len(ans[5])):
    if ans[5][j] == 42:
        flag = 1
        break
if flag == 1:
    print("YES")
else:
    print("NO")
'''两种创建空列表的方法
1.a = [[]*n]*n
2.a = [[] for i in range(n)]'''

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值