2、    recursive / fibonacci

2.1    蛙跳
mem = {}
def fun(n):
    global mem
    if n in mem:
        return mem[n]
    else:
        if n==1:
            mem[n] = 1
            return 1
        elif n==2:
            mem[n] = 2
            return 2
        else:
            t = fun(n-1)+fun(n-2)
            mem[n] = t
            return t

class Solution:
    def jumpFloor(self, number):
        if number==1:
            return 1
        elif number==2:
            return 2
        elif number>2:
            f1 = 1
            f2 = 2
            for i in range(3,number+1):
                f3 = f1+f2
                f1 = f2
                f2 = f3
            return f3

2.2    猴子登山
mem = {}
def f(n):
    if n in mem:
        return mem[n]
    else:
        if n==1:
            mem[1] = 1
            return 1
        elif n==2:
            mem[2] = 1
            return 1
        elif n==3:
            mem[3] = 2
            return 2
        else:
            t = f(n-1)+f(n-3)
            mem[n] = t
            return t
print(f(m))
#---------------------------------
f1 = 1
f2 = 1
f3 = 2
for i in range(3,m):
    f4 = f1+f3
    f1 = f2
    f2 = f3
    f3 = f4
print(f4)

2.3    汉诺塔
把n个盘子从Left 借助 Mid,移动到Right柱子上,可以分为以下三步:
1、把n-1个盘子从Left 借助 Right,移动到Mid柱子上
2、把剩下最大的那一个盘子从Left移动到 Right柱子上
3、把n-1个盘子从Mid 借助 Left,移动到,Right柱子上
out = []
def hanoi(n, left, mid, right):
    if n==0:
        return
    if n==1:
        t = 'move from ' + left + ' to ' + right
        out.append(t)
        return               
    hanoi(n-1, left, right, mid)
    hanoi(1, left, mid, right)
    hanoi(n-1, mid, left, right)

2.4    放苹果
f(m,n)表示将m个苹果放入n个盘子中的摆放方法总数,
放苹果分为两种情况,一种是有1个盘子为空 f(m,n-1),另一种是每个盘子上至少有1个苹果f(m-n,n),
递推关系:f(m,n) = f(m,n-1) + f(m-n,n)
边界条件:当m==1 or n==1时,f(m,n) =1
def f(m,n):
    if m<0 or n<0:
        return 0   
    elif m==1 or n==1:
        return 1
    else:
        return f(m,n-1) + f(m-n,n)

while 1:
    try:
        m,n = map(int,input().strip().split())
        print(f(m,n))
    except:
        break

3、    dfs / bfs / back

3.1    组合
        def dfs(iStart,res):           
            s = sum(res)                    # 路径和
            if s>target:
                return               
            if s==target:
                if res not in out:          # 这里也可以去重
                    out.append(res)         # 保存路径
                return                      # 返回空回溯                                       
            for i in range(iStart,n):
                dfs(i+1,res+[num[i]])       # i+1,增加路径
        dfs(iStart,res)

3.2    排列
       def dfs(num,res):
            if not num:
                if res not in out:
                    out.append(res)               
            for i in range(len(num)):
                dfs(num[:i]+num[i+1:],res+[num[i]])
        dfs(num,res)

3.3    数独
def dfs(k, res):
    if k==n:                # 如达到最后一个节点
        #print(res)
        return res          # 返回第一个合法解,如无返回可打印所有合法解
    if 0<=k<=n-1:           # 限定搜索范围保证访问不越界
        i = I[k]            # 取行值
        j = J[k]            # 取列值
        for v in value:                         # 遍历当前节点每个候选解
            if check(S,i,j,v):                  # 检查当前节点当前候选解合法性
                S[i][j] = v                     # 如合法赋值
                solve_opt = dfs(k+1, res+[v])   # 递归进入下一个节点,第二个参数加入当前节点合法解。dfs()的返回值solve只有两种情况,
                if solve_opt:                   # 1、为空,说明下一个节点几个候选解check()都返回false,即本节点当前候选解不对,
                    return solve_opt            #    本节点要回溯执行下一个候选解,因此要恢复S[i][j]=0,对一下个候选解执行check()
                S[i][j] = 0                     # 2、不为空时,说明到达终点k==n,solve返回所有路径点res
solve_opt = dfs(0, res)

3.4    迷宫
xy0 = [0,0]
xy1 = [r-1,c-1]
MOV = [[1,0],[0,1],[-1,0],[0,-1]]                    # 四个方向移动一格
#---------------------------------------------------------------------------------
# 递归调用dfs(xy_new, res+[xy_new])时, res+[xy_new]实际保存了不断增长的路径

res = [xy0]
def dfs(xy0, res):                                          # xy0为当前搜索点,res保存了当前点历史路径
    maze[xy0[0]][xy0[1]] = 9

    if xy0[0] == xy1[0] and xy0[1] == xy1[1]:
        shortest_path = res
        return shortest_path                                # 到达终点时读取res返回最路径

    for mov in MOV:                                         # 四个方向移动
        xy_new = [xy0[0]+mov[0], xy0[1]+mov[1]]             # 移动后的新坐标
        if 0<=xy_new[0]<=r-1 and 0<=xy_new[1]<=c-1 and maze[xy_new[0]][xy_new[1]] == 0:     # 坐标有效且没有被访问过
            shortest_path = dfs(xy_new, res+[xy_new])       # 调用下一个节点,第二个参数传递路径
            if shortest_path:                               # 只有dfs返回不为空时,才能返回,空时不能返回,程序会提前结束
                return shortest_path               

#=============================================================================================================================
xy0 = [0,0,-1]
xy1 = [r-1,c-1,-1]
MOV = [[1,0],[0,1],[-1,0],[0,-1]]                    # 四个方向移动一格
#---------------------------------------------------------------------------------
# Path中的每一行存有路径坐标点,第三个值保存上一个节点的list索引,从0开始
def road(visited):
    shortest_path = []
    shortest_path.append(visited[-1][:-1])
    t = visited[-1][-1]
    while t!=-1:
        shortest_path.append(visited[t][:-1])
        t = visited[t][-1]
    return shortest_path[::-1]

#---------------------------------------------------------------------------------
# BFS(宽度优先搜索)解迷宫问题
def bfs(xy0):
    queue = [xy0]                                    # 开始点进入队列
    visited = []                                     # 记录所有访问过的点,以及每一个点前一个节点索引
    k = 0

    while queue:                                     # 队列不为空即运行
        xy = queue.pop()                             # 队列中取出一个新坐标点
        maze[xy[0]][xy[1]] = 9                       # 开始点标记为已访问值9
        visited.insert(k, xy)                        # 相当于path.append(xy),这样写更清楚

        if xy[0] == xy1[0] and xy[1] == xy1[1]:
            shortest_path = road(visited)            # 到达终点时,反向寻找来时路径
            return shortest_path      

        for mov in MOV:                              # 四个方向移动
            xy_new = [xy[0]+mov[0], xy[1]+mov[1]]    # 移动后的新坐标
            if 0<=xy_new[0]<=r-1 and 0<=xy_new[1]<=c-1 and maze[xy_new[0]][xy_new[1]] == 0:     # 坐标有效且没有被访问过
                xy_new = [xy_new[0],xy_new[1],k]     # 将前一个点的索引加入坐标之后(用于反向寻找路径)
                queue.insert(0, xy_new)              # 新坐标不为终点,新坐标入队列,
        k += 1

3.5    单源最短路
1、对于节点距离,建立一个字典dict_t,以字符形式存储key,距离以数字形式保存在value中,重边取最小
2、定义一个函数dis(i,j):第i节点到第j个节点的距离,访问距离字典,有值返回,无通路返回None
3、dfs搜索,第二个参数传递走过的距离,到达终点时取最小输出,nonlocal关键字的使用
class Solution:
    def findShortestPath(self , n , m , graph ):
        # write code here

        dict_t = {}
        for ijdis in graph:
            key = str(ijdis[0])+'-'+str(ijdis[1])
            if key not in dict_t:
                dict_t[key] = ijdis[2]
            else:
                dict_t[key] = min(ijdis[2],dict_t[key])
        #print(dic)

        def dis(i,j):
            key = str(i)+'-'+str(j)
            if key not in dict_t:
                return None
            else:
                return dict_t[key]
        #print(dis(1,2))
        #print(dis(2,3))
        #print(dis(3,4))

        res = 0
        minpath = float('inf')
        def dfs(i,res):
            nonlocal minpath
            if i==n:
                if res<minpath:
                    minpath = res
            for j in range(1,n+1):
                if dis(i,j):
                    dfs(j,res+dis(i,j))

        dfs(1,res)   
        print(minpath)         
        return minpath

4、    stack / queue

4.1    火车进站
目隐藏条件是N趟火车的进站始终按照原本的火车列表顺序,就是假如有两列火车A,B,B不可能在A之前进站
关键的几个变量说明:
1、cur_idx,当前需要进站处理的火车
2、in_trains、已经进站的火车列表
3、out_trains、已经出站的火车列表
4、res、火车所有可能的出站列表
#--------------------------------------------------------------------
# 两点注意:
# 1、train, station, out在计算过程中不要有赋值操作
# 2、压栈操作[1,2,3]+[4] = [1,2,3,4],这里4如果是单独一个数要加[]
def fun(train, station, out):
    #print('train=',train)
    #print('station=',station)
    #print('out=',out)
    if train and station == []:                # 车站为空,仅火车进站
        #print('1')
        t = train[-1]
        fun(train[:-1], station + [t], out)
    elif train == [] and station:              # 火车为空,车站火车出站
        #print('2')
        rec.append(out + station[::-1])
        return
    else:                                      # 车站和火车都不为空,有两种选择
        #print('3')
        t = train[-1]
        fun(train[:-1], station + [t], out)    # 火车进站
        t = station[-1]
        fun(train, station[:-1], out + [t])    # 车站火车出站

4.2    表达式求值
解题思路:
1、中缀表达式转为后缀表达式
2、为后缀表达式计算规则:从左到右遍历表达式的每个数字和符号,遇到的是数字就进栈,
遇到的时符号就将栈顶的两个数字出栈进行计算,然后将计算结果入栈,最终栈里的值即为计算的结果。
3、预处理:将字符串按实际物理意义,转成字符列表(含多位数和负数、空格、[]、{})
'''
#----------------------------------------
# 输出num1 + operator + num2结果
def getvalue(num1,num2,operator):
    if operator == '+':
        return num1 + num2
    elif operator == '-':
        return num1 - num2
    elif operator == '*':
        return num1 * num2
    elif operator == '/':
        return num1 / num2
    else:
        return []
#----------------------------------------
# 优先级 operator1 >= operator2,输出真
def compare(operator1,operator2):
    if operator1 in '*/' and operator1 in '*/':
        return True
    elif operator1 in '+-' and operator1 in '+-':
        return True
    elif operator1 in '*/' and operator1 in '+-':
        return True
    elif operator1 in '+-' and operator1 in '*/':
        return False
    else:
        return []
#----------------------------------------
# 中缀表达式转为后缀表达式
# 输入L = ['0','-','1','*','(','0','-','1','-','1',')','-','3','*','(','1','-','2',')','-','3','*','4']
# 输出express= ['0',1','0','1','-','1','-','*','-','3','1','2','-','*','-','3','4','*','-']
def suffix_express(L):
    express = []                    # 后缀表达式
    stack = []                      # 存中间符号的栈
    for i in L:
        if i.isnumeric():           # 数字直接入表达式
            express.append(i)
        elif i in '+-':             # 遇到+—号,如果符号栈空,入符号栈
            if len(stack) == 0:
                stack.append(i)
            else:                   # 如果符号栈不为空,符号栈优先级大于等于当前符号的,弹出入表达式
                while stack and compare(stack[-1],i):
                    express.append(stack.pop())
                stack.append(i)     # 当前符号入符号栈
        elif i in '*/(':            # */(直接入符号栈
            stack.append(i)
        elif i in ')':              # 遇到),(之前的符号弹出入表达式
            while stack and stack[-1] != '(':
                express.append(stack.pop())
            if stack:              # 删除(
                stack.pop()
    while stack:                   # 最后栈中所有符号弹出入表达式
        express.append(stack.pop())
    return express
#----------------------------------------
# 为后缀表达式计算规则:
# 从左到右遍历表达式的每个数字和符号,遇到的是数字就进栈,遇到的时符号就将栈顶的两个数字出栈进行计算,
# 然后将计算结果入栈,最终栈里的值即为计算的结果。
# 输入express= ['0',1','0','1','-','1','-','*','-','3','1','2','-','*','-','3','4','*','-']
# 输出计算结果 = result
def compute(suffix_express):
    stack = []
    for i in suffix_express:
        if i.isnumeric():
            stack.append(i)
        elif i in '+-*/':
            n2 = stack.pop()
            n1 = stack.pop()
            stack.append(getvalue(int(n1),int(n2),i))
    if stack:
        return stack.pop()
    return []
#----------------------------------------
# 预处理:将字符串按实际物理意义,转成字符列表(含多位数和负数、空格、[]、{})
# 输入:-1*(-1-1)-3*(1-2)-3*4
# 输出:['0','-','1','*','(','0','-','1','-','1',')','-','3','*','(','1','-','2',')','-','3','*','4']
def preprocess(L):
    B = ''
    for i in L:
        if i in '0123456789+-*/()[]{}':           # 去空格
            B += i
    B = B.replace('[','(')                        # 统一括号
    B = B.replace(']',')')
    B = B.replace('{','(')
    B = B.replace('}',')')
    L = '('+ B +')'                    # 左右加括号用于判断负号

    A = []
    tmp = ''
    for i in range(1,len(L)-1):
        if L[i] == '-' and L[i-1]=='(':        # 负号前必有括号,前加0将负号变减号
            A.append('0')
            A.append('-')
        elif L[i].isnumeric() and L[i+1].isnumeric():
            tmp += L[i]
        elif L[i].isnumeric() and not L[i+1].isnumeric():
            tmp += L[i]
            A.append(tmp)
            tmp = ''
        else:
            A.append(L[i])
    return A
#String = input()
String = '-1*(-1-1)-3*(1-2)-3*4'
#print(String)
List = preprocess(String)
#print(List)
express = suffix_express(List)
#print(express)
result = compute(express)
print(String+' = '+str(result))

4.3    矩阵乘法计算量估算
https://www.nowcoder.com/practice/15e41630514445719a942e004edc0a5b?tab=note
# 多个矩阵连乘的总计算量,输入为多个矩阵的size组成的list二维数组
def times(size):
    n = len(size)
    tmp = 0
    for t in range(n-1):
        tmp += size[0][0] * size[t][1] * size[t+1][1]
    return tmp

while 1:
    try:
        n = int(input())
        size = []
        for i in range(n):
            size.append(list(map(int,input().strip().split())))
        L = input()

        L= '(' + L + ')'                                # 左右加()
        stack = []
        tmp = []
        result = 0
        k = 0
        for i in range(1,len(L)-1):                     # 不考虑左右加的()
            if L[i] == '(':                             # 遇到(,(压栈
                stack.append('(')
            elif L[i].isalpha():                        # 遇到矩阵,size压栈
                stack.append(size[k])
                k += 1
            elif L[i] == ')':                           # 遇到),之前所有矩阵size出栈,并反序
                while stack and stack[-1] != '(':
                    tmp.append(stack.pop())
                if stack:
                    stack.pop()
                tmp = tmp[::-1]
                stack.append([tmp[0][0],tmp[-1][-1]])   # 新矩阵size压栈
                result += times(tmp)                    # 计算乘法计算量并累加
                tmp = []
        print(result)
    except:
        break

4.4    最长无重复子数组
方法一:用一个队列,把元素不停的加入到队列中,如果队列中有相同的元素,就把队首的相同元素及之前的元素都移除
方法二:使用两个指针,一个i一个j,最开始的时候i和j指向第一个元素,然后i往后移,把扫描过的元素都放到map中,如果i扫描过的元素没有重复的就一直往后移,顺便记录一下最大值max,如果i扫描过的元素有重复的,就改变j的位置
class Solution:
    def maxLength(self , arr ):
        queue = []
        maxLen = 0
        for i in arr:
            if i not in queue:
                queue.insert(0,i)
            else:
                while queue[-1] != i:
                    queue.pop()
                queue.pop()
                queue.insert(0,i)
            if len(queue)>maxLen:
                maxLen = len(queue)
        return maxLen