递归问题

基本结构回顾练习题

  1. 实现一个MyQueue类,该类用两个栈来实现一个队列。
queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek();  // 返回 1
queue.pop();   // 返回 1
queue.empty(); // 返回 false
你只能使用标准的栈操作 -- 也就是只有 push to top, peek/pop from top, size 和 is empty 操作是合法的。
  1. 请设计一个栈,除了常规栈支持的pop与push函数以外,还支持min函数,该函数返回栈元素中的最小值。执行push、pop和min操作的时间复杂度必须为O(1)。

  2. 用队列实现,给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值

## 1.用两个栈实现队列
class myQueue:
    def __init__(self):
        self.items = []
        self.tmp = []
    
    def isEmpty(self):
        return self.items == []
    
    def enqueue(self, item):    # 只能从队尾加入
        return self.items.append(item)
    
    def dequeue(self):          # 只能从队首出去
        for i in range(len(self.items)):
            self.tmp.append(self.items.pop())
        self.items = self.tmp
        return self.items.pop() 
    
## 测试
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"   
duilie = myQueue()
duilie.items = [1,2,3]
duilie.isEmpty()
duilie.enqueue(2)
duilie.items
duilie.dequeue()
duilie.items
False






[1, 2, 3, 2]






1






[2, 3, 2]
## 2.设计一个栈支持min函数
class myStack:
    def __init__(self):
        self.items = []
        self.min_item = []

    def push(self, item):
        self.items.append(item)
        if self.min_item==[] or self.min_item[-1]>=item:
            self.min_item.append(item)
        
    def pop(self):
        if self.min_item[-1]==self.items.pop():
            self.min_item.pop()
        
            
    def getMin(self):
        return self.min_item[-1]

## 测试
zhan = myStack()
zhan.push(2.2)
zhan.items,zhan.min_item
zhan.push(3.4)
zhan.items,zhan.min_item
zhan.push(1.8)
zhan.items,zhan.min_item
zhan.getMin()
zhan.pop()
zhan.items,zhan.min_item,zhan.getMin()
([2.2], [2.2])






([2.2, 3.4], [2.2])






([2.2, 3.4, 1.8], [2.2, 1.8])






1.8






([2.2, 3.4], [2.2], 2.2)
## 3.滑动窗口的最大值,输入num和窗宽k,用队列实现
def maxK(k,nums):
    maxnum = []
    while len(nums)>=k:
        maxnum.append(nums[0])
        for i in range(1,k):
            if maxnum[-1] < nums[i]:
                maxnum.pop()
                maxnum.append(nums[i])
        nums.pop(0)
    return maxnum

# 测试
maxK(k=5, nums = [1, 3, -1, 3.2, -5, 4, 3, 6, 7.5, 2, 6.8, 9])
[3.2, 4, 4, 6, 7.5, 7.5, 7.5, 9]

递归

要点:

  • 把问题分解为更小规模相同问题
  • 调用自身
  • 要有一个基本结束条件
#### 递归理解小故事:
def tell_story(i):
    if i > 0:      # 结束条件
        i = i-1 
        print('从前有座山,山里有座庙,庙里有个老和尚,他在讲:')
        return tell_story(i)   # 调用自身

# 测试
tell_story(3)
从前有座山,山里有座庙,庙里有个老和尚,他在讲:
从前有座山,山里有座庙,庙里有个老和尚,他在讲:
从前有座山,山里有座庙,庙里有个老和尚,他在讲:
#### list累加
num=[1,3,5,7,9]
def listSum(num):
    if len(num)==1:   # 基本结束条件
        return num[0]
    else:
        return num[0]+listSum(num[1:])   # 第一次:a0+a1,第二次a0+a1+a2,第三次...直到len=1

# 测试
listSum(num)
25
#### 任意进制转化
def toStr(n, base):
    converString = "0123456789ABCDEF"  # 最大为16进制
    if n < base:
        return converString[n]
    else:
        return toStr(n//base,base)+converString[n%base]

# 测试
toStr(35,2)
toStr(192345,16)
'100011'






'2EF59'

递归调用的实现:

  • 每次调用时,现场数据压入系统调用栈(栈帧)
  • 返回时,从栈顶获得返回地址,弹出栈帧,按地址返回。

递归作图:

### 画一个五角星
import turtle
t2 = turtle.Turtle()
t2.pencolor('red')
for i in range(6):
    t2.forward(100)
    t2.right(144)
turtle.done()
#### 画一个螺旋
import turtle
t3 = turtle.Turtle()

def drawS(t,linelen):
    if linelen>0:
        t.forward(linelen)
        t.right(90)
        drawS(t,linelen-5)

drawS(t3,100)
#### 画一个二叉树
import turtle

def plotTree(t,branch):
    if branch>5:
        t.forward(branch)
        t.right(20)
        plotTree(t,branch-15)
        t.left(40)
        plotTree(t,branch-15)
        t.right(20)
        t.backward(branch)
    
t4 = turtle.Turtle()
t4.left(90)
t4.backward(70)
plotTree(t4,80)
turtle.done()

from IPython.display import Image
Image(filename = 'Desktop/快乐研一/python/ercha.jpg', width=300, height=300)
jpeg

经典问题

1——汉诺塔

## 三柱子汉诺塔实现
def moveTower(n,fromT,withT,toT):
    if n>=1:
        moveTower(n-1,fromT,toT,withT)
        print(f"moving disk{n} from {fromT} to {toT}")
        moveTower(n-1,withT,fromT, toT)

moveTower(3,"#1","#2","#3")
moving disk1 from #1 to #3
moving disk2 from #1 to #2
moving disk1 from #3 to #2
moving disk3 from #1 to #3
moving disk1 from #2 to #1
moving disk2 from #2 to #3
moving disk1 from #1 to #3

2——探索迷宫

### 1.考虑用矩阵实现一个迷宫
### 2.墙壁 + 通道  
### 3.避免死循环,记录走过的路径,避开
### 4.结束条件:①碰到墙②碰到走过的③碰到边界

练习题

  • 斐波那契数列
  • 青蛙跳台阶
  • 跳水台问题
  • 井字游戏(稍难)
## 1.斐波那契数列
## 从0和1开始,用前面两项相加得到当前项
def fib(n):   # 第几个数
    if n==0: return 0
    if n==1: return 1
    if n>1:
        return fib(n-1)+fib(n-2)      
#测试
for i in range(10): print(fib(i))
    
    
## 2.青蛙跳台阶问题
## 青蛙一次可以跳上一级台阶,也可以跳两级,问跳上n级台阶总共有几种跳法
def numWays(n):
    if n==1:return(1)
    if n==2:return(2)
    if n-2>=1:return(numWays(n-1)+numWays(n-2))
# 测试
numWays(7)


## 3.跳水板
## 正好使用n块木板(有两种长度)生成所有可能长度:
def boardlen(n,short,longer):
    length = []
    for i in range(n+1):
         length.append((n-i)*short+i*longer)
    return length
# 测试:
boardlen(4,2,3)


## 4.递归乘法——正整数相乘
# 如果写成循环非常简单
A=1;B=10;tmp=0
for i in range(A):
    tmp = tmp+B
print(tmp)
# 如果写成递归呢
def multiply(A:int,B:int) -> int:
    if A==1: 
        return B
    if A>=2: 
        return multiply(A-1,B)+B
## 测试
multiply(3, 9)
0
1
1
2
3
5
8
13
21
34





21






[8, 9, 10, 11, 12]






10






27

井字游戏(递归)

import random
## 井字游戏(递归)
# 结束条件:1.放满了,2.三个相同字符填充行、列、对角线
# 用递归列出所有可能的结束情况,每次递归下一次
def wins(jin):
    ifwin = False
    if jin[0]==jin[4]==jin[8]!=0 or jin[2]==jin[4]==jin[6]!=0: ifwin=True   # 对角
    if jin[0]==jin[1]==jin[2]!=0 or jin[3]==jin[4]==jin[5]!=0 or jin[6]==jin[7]==jin[8]!=0: ifwin = True  # 行
    if jin[0]==jin[3]==jin[6]!=0 or jin[1]==jin[4]==jin[7]!=0 or jin[2]==jin[5]==jin[8]!=0: ifwin=True   # 列
    return ifwin

def xiaqi(jin,jin_num):   # 每一次下棋
    ## 停止条件
    if len(jin_num)>=1 and (not wins(jin)):
        
    ## 递归调用
        locat = random.choice(jin_num)   # 从空位中选出一个位置
        ## 轮到谁下棋
        if len(jin_num)%2==0:
            jin[locat]="乙"
        else:jin[locat]="甲"
        jin_num.remove(locat)
        print("第{}次:\n{}\n{}\n{}".format(len(jin)-len(jin_num),jin[0:3],jin[3:6],jin[6:9]))
        return xiaqi(jin, jin_num)   # 下完之后的棋盘和空位继续下棋

    ##  停止之后,判断局势
    if len(jin_num)==0 and (not wins(jin)): print("平局")
    if wins(jin): 
        if len(jin_num)%2==0:
            print("甲 win!")
        else:
            print("乙 win!")
        
xiaqi(jin = [0]*9, jin_num = list(range(0,9)))
第1次:
[0, 0, 0]
['甲', 0, 0]
[0, 0, 0]
第2次:
[0, 0, 0]
['甲', 0, 0]
[0, '乙', 0]
第3次:
[0, 0, 0]
['甲', 0, '甲']
[0, '乙', 0]
第4次:
[0, '乙', 0]
['甲', 0, '甲']
[0, '乙', 0]
第5次:
[0, '乙', 0]
['甲', '甲', '甲']
[0, '乙', 0]
甲 win!

优化问题和贪心策略

## 硬币找零问题
### 一共有1分,5分,10分,25分四种硬币,寻找兑换硬币最少数量
def coins(coinValue, change):  #输入硬币体系,和需要找零的金额
    min_coins = change   # 最大是change次
    if change in coinValue: min_coins = 1
    else:
        for i in range(len(coinValue)):
            if change > coinValue[i]:
                tmp = coins(coinValue, change - coinValue[i])+1   # 如果先兑换一个一分的
                if tmp < min_coins:
                    min_coins = tmp
    return min_coins

coins([1,5,10,25],53)    
# 超级慢,重复计算太多 
5
## 改进方法
def newcoin(coinValue,change,mychange,mymin_coin):
    min_coins = change
    if change in coinValue:
        min_coins=1
    elif change in mychange:
        return mymin_coin[mychange.index(change)]
    else:
        for i in range(len(coinValue)):
            if change >= coinValue[i]:
                tmp = newcoin(coinValue, change-coinValue[i], mychange, mymin_coin)+1
                if tmp < min_coins:
                    min_coins = tmp
                    mychange.append(change-coinValue[i])
                    mymin_coin.append(tmp)
    return min_coins

## 测试
newcoin([1,5,10,25],53,[],[])
5
### 博物馆盗窃问题
### 假设已经拿了k件,拿第k-1件
### 1.如果前k件的重量和20公斤差值小于2——停止
### 从所有能拿的东西里,选一件拿走-》递归,价值=前k件价值最大值+这一件的价值,在对比计算价值最大值
def max_value(currentkg, maxkg,currentvalue,itemkg,itemvalue):
    maxvalues = 0
    if currentkg+min(itemkg) > maxkg: 
        currenkg = min(itemkg)
        maxvalues = itemvalue[itemkg.index(min(itemkg))]
    else:
        for i in range(len(itemkg)):
            if currentkg + itemkg[i] <= maxkg:
                currentkg, currentvalue = max_value(currentkg+itemkg[i], maxkg, currentvalue,itemkg,itemvalue)
                tmp = currentvalue+itemvalue[i]
                if tmp >= maxvalues:
                    maxvalues = tmp
    return currentkg, maxvalues

# 测试
max_value(0,12,0,[2,4,6],[8,20,32])
(12, 56)
### 分发糖果,有 N 个孩子站成了一条直线
# 每个孩子至少分配到 1 个糖果。
# 相邻的孩子中,评分高的孩子必须获得更多的糖果高
# 老师至少准备多少糖果
# 最少糖果=上一个最少糖果+-1,
def min_candy(grade, number, candy, current_sum):
    if number==1:
        candy=1
        current_sum=1
    else:
        candy,current_sum = min_candy(grade, number-1, candy, current_sum)
        if grade[number-1] > grade[number-2]:
            candy = candy+1
            current_sum = current_sum+candy
        if grade[number-1]==grade[number-2]:
            current_sum = current_sum+candy
        if grade[number-1]<grade[number-2]:
            if candy==1:
                current_sum = current_sum+2
                candy = 1
            else:
                candy = 1
                current_sum = current_sum+candy
    return candy,current_sum
    
print("总糖果数{}".format(min_candy([10,20,30,30,20,10,20,40,50,30,10,20],12,0,0)[1]))
1+2+3+3+2+1+2+3+4+2+1+2
总糖果数26





26