一.怎么写递归
- 1.一定要搞清楚当前你的递归程序的功能是什么,以及需不需要返回值,如果需要,则返回的值是什么,如果对当前递归程序要完成的功能模棱两可、一知半解,那么逻辑肯定混乱,也就不容易写出正确的递归程序。
- 2.找递归终止条件,根据函数的参数来看,看参数满足什么样的条件时是到了递归出口(常见的递归出口是,比如
对于list(列表为空,只含有一个元素),对于二叉树(结点为空,结点是叶子结点),对于链表(头指针为空,只含有一个元素)
等等),注意:这种递归终止情况是由:1
.这个递归程序解决的就是一个终止条件,比如斐波那契就是为了算n=1或者2,归并排序一个列表,就是一个只含有一个元素的列表或者空列表。2
这个终止情况是由上个状态递归到这里的,所以对于这个递归出口要注意,要联系上个状态看是直接返回某个值还是说对某个全局变量进行某种更改。 - 3.就是做出递归关系式,当前状态的解决是怎么通过递归函数解决的,即怎么把一个大问题分割成若干小问题,通过解决若干个小问题再组合起来,来求解这个大问题(两种情况:1.先递归,再对递归回来的状态进行处理 2.先处理一下当前状态,然后再递归)。
1.以归并排序的代码作分析如下:
def merge_sort(alist):
# 1-此函数的功能是对alist列表进行归并排序,之后返回排序好的新列表
"""归并排序"""
# 2-递归的出口是什么呢?对列表进行操作,不断分割列表,那么当列表长度小于等于1的时候,无可分割了,那就结束,直接返回这个列表即可
n = len(alist)
if n <= 1:
return alist
mid = n // 2
# 3-要对当前列表进行归并排序,怎么通过递归解决当前这个问题呢
# 递归关系式:merge_sort(alist) = (left_li=merge_sort(alist[:mid]) ,right_li = merge_sort(alist[mid:]),之后合并left_li和right_li)
#3-1.对左半部分进行归并排序,直接调用此函数,这就是利用递归,直接处理左半部分(这里可能有点难以理解,但是不用想那么多merge_sort的功能不就是对alist进行归并排序嘛)
left_li = merge_sort(alist[:mid])
#3-2对右半部分同理
right_li = merge_sort(alist[mid:])
#3-3左半部分有序了,右半部分也有序了,那么再把左右部分合并到一起,使其整体有序了,就可以了
#调用递归之后,还需要对递归得到的结果进行处理,才能最终得到结果
#有的直接调用递归,并返回递归得到的值就可以解决这个问题了
left_pointer, right_pointer = 0, 0
result = []
while left_pointer < len(left_li) and right_pointer < len(right_li):
if left_li[left_pointer] <= right_li[right_pointer]:
result.append(left_li[left_pointer])
left_pointer += 1
else:
result.append(right_li[right_pointer])
right_pointer += 1
result += left_li[left_pointer:]
result += right_li[right_pointer:]
return result
2.以反转链表(leetcode-206)的递归代码作分析如下:
class Solution:
def reverseList(self, head):
# 1-此函数的功能是对以head为头结点的链表进行反转,之后返回反转后的新链表的头结点
# 2-递归的出口是什么呢?对链表进行反转,显然head为空是一个出口,直接return,由于是对链表进行反转,所以当链表中只有一个元素的时候,即not head.next,也直接返回
if not head or not head.next:
return head
# 3-要对当前以head为头结点的链表进行反转,怎么通过递归解决当前这个问题呢
#递归关系式:reverseList(head) = (p=reverseList(head.next),再把p和head连接起来)
# 3-1.先缩小范围,对head.next为头结点的链表进行反转,此时返回的头结点为p
p = self.reverseList(head.next)
# 经过上述递归之后,现在链表为:
# head -> 1 <- 2 <- 3 <- 4 <- 5(p)
# 3-2.经过上述递归后,head.next为头结点的链表已经进行了反转,此时,再将头结点加入即可
# 注意:上述递归并没有对head结点进行操作,所以此时head的指向并没有改变!!!!这很重要
head.next.next = head
head.next = None
return p
3.以移除链表元素(leetcode-203)的递归代码作分析如下:
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def removeElements(self, head, val):
# 1-此函数的功能移除以head为头结点的链表中值为val的结点,并返回新链表的头结点
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
# 2-递归出口是什么,显然,链表为空时,直接返回head
if not head:
return head
# 3-要对当前以head为头结点的链表中删除值为val的结点,怎么通过递归解决当前这个问题呢
# 递归关系式:removeElements(head) = (nexttemp=removeElements(head.next),再把nexttemp和head连接起来)
# 3-1.先缩小范围,对head.next为头结点的链表进行处理,此时返回的头结点为nexttemp
nexttemp = self.removeElements(head.next,val)
# 3-2整个链表head右边的已经处理好了(即head右边的链表中值为val的结点已经删除了),此时就只需考虑head结点的值是否为val了
if head.val == val:
return nexttemp
else:
head.next = nexttemp
return head
二.递归的优化(以斐波那契数列为例)
# 写法1
def fibonacci_1(n):
if n <= 1:
return n
return fibonacci_1(n - 1) + fibonacci_1(n - 2)
# 写法2,利用自带的装饰器减少重复计算
import functools
# @functools.lru_cache(maxsize=128, typed=False)
# maxsize 是保存最近多少个调用的结果,最好设置为 2 的倍数,默认为 128。如果设置为 None 的话就相当于是 # maxsize 为正无穷了。还有一个参数是 type,如果 type 设置为 true,即把不同参数类型得到的结果分开保存,如 f(3) 和 f(3.0) 会被区分开
#只有python3能用
@functools.lru_cache(None)
def fibonacci_1(n):
if n <= 1:
return n
return fibonacci_1(n - 1) + fibonacci_1(n - 2)
#写法3
# 带记忆化搜索的递归,避免了重复子问题的多次计算
def fibonacci_2(n, memo):
if memo[n] != -1:
return memo[n]
elif n <= 1:
memo[n] = n
return memo[n]
else:
memo[n] = fibonacci_2(n - 1, memo) + fibonacci_2(n - 2, memo)
return memo[n]
# 自底向上的动态规划,时间复杂度O(n),空间复杂度O(n)
def fibonacci_3_1(n):
res = [0, 1]
for i in range(n - 1):
res += [res[-1] + res[-2]]
return res[-1]
def fibonacci_3_2(n):
res = [-1] * (n + 1)
res[0], res[1] = 0, 1
for i in range(2, n + 1):
res[i] = res[i - 1] + res[i - 2]
return res[-1]
# 自底向上的动态规划,时间复杂度O(n),空间复杂度O(1)
# 因为当前状态只与之前的两个状态有关,所以我们不需要一个O(n)的
# 额外数组,只需要两个临时变量即可,空间复杂度变成了O(1)
def fibonacci_4(n):
first, seccond = 0, 1
for i in range(1, n):
third = first + seccond
first = seccond
seccond = third
# 上面的三行就等于下面的这一行
# first, seccond = seccond, first + seccond
return seccond
- 举例子,[爬楼梯2]
#写法1,通过0%,很显然,直接递归,复杂度太大
n = int(input())
def fibonacci(n):
if n < 3:
return 1
return fibonacci(n - 1) + fibonacci(n - 3)
print(fibonacci(n))
#写法2,通过60%,没有设置最大栈深度(如果在PyCharm中执行n太大的话,比如500,就会提示:
# RecursionError: maximum recursion depth exceeded in comparison,而在牛客网上提交的话
# 会显示:请检查是否存在语法错误或者数组越界非法访问等情况,case通过率为60.00%
n = int(input())
import functools
@functools.lru_cache(None)
def fibonacci(n):
if n < 3:
return 1
return fibonacci(n - 1) + fibonacci(n - 3)
print(fibonacci(n))
#可以通过
n = int(input())
import sys
import functools
sys.setrecursionlimit(1000000)
@functools.lru_cache(None)
def fibonacci(n):
if n < 3:
return 1
return fibonacci(n - 1) + fibonacci(n - 3)
print(fibonacci(n))