一枚菜鸟的leetcode刷题笔记 - Day8

文章目录

    • 62 - 不同路径
      • 解法一:递归
      • 动态规划
    • 70 - 爬楼梯
    • 78 - 子集
      • 解法一:定义递归函数找到当前index开始的数组的所有子集
      • 解法二:对数组的每个元素考虑选或不选

62 - 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?

解法一:递归

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        if n == 1 or m == 1:
            return 1

        return self.uniquePaths(m-1, n) + self.uniquePaths(m, n-1)

很不幸超时了,超时样例是m=23,n=12.

递归的思路很简单,机器人从(1,1)走到(m,n)的不同路径数 = 机器人从(1,1)走到(m-1,n)的不同路径数 + 机器人从(1,1)走到(m,n-1)的不同路径数。而递归的base情况就是 m=1 或者 n=1. 因为此时只需要一直向右走或者一直向下走就可以了,都是只有一种情况,所以返回 1.

动态规划

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        #维护一个表格table,table的第(i,j)个位置记录了机器人走到当前位置的不同路径数
        table = [[0 for _ in range(n)] for _ in range(m)]
        #先把第一行和第一列填好,因为这些位置的路径数都是1
        for i in range(m):
            table[i][0] = 1
        for j in range(1, n):
            table[0][j] = 1
        #开始填表格
        for i in range(1, m):
            for j in range(1, n):
                table[i][j] = table[i-1][j] + table[i][j-1]
        
        return table[m-1][n-1]

动态规划其实和递归的想法是一样的,核心点都在于想清楚:机器人从(1,1)走到(m,n)的不同路径数 = 机器人从(1,1)走到(m-1,n)的不同路径数 + 机器人从(1,1)走到(m,n-1)的不同路径数。这就是状态转移方程:table[i][j] = table[i-1][j] + table[i][j-1]. 不同在于递归是倒着往回推,动态规划是从初始情况往后推。注意 table[i-1][j] 和 table[i][j-1] 都要在 table[i][j] 前被遍历到。

70 - 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。

class Solution:
    def climbStairs(self, n: int) -> int:
        if n == 1:
            return 1
        if n == 2:
            return 2
        return self.climbStairs(n-1) + self.climbStairs(n-2)

因为一次可以走1或2步,因此 n级台阶的走法 = n-1级台阶的走法(最后走一步) + n-2级台阶的走法(最后走两步)
递归超时了。因为递归比循环慢很多,下面改写成循环的解法。

class Solution:
    def climbStairs(self, n: int) -> int:
        s1 = 1
        s2 = 2
        for i in range(3, n+1):
            if s1 < s2:
                s1 = s1 + s2
            else:
                s2 = s1 + s2
        if n == 1:
            return 1
        elif n == 2:
            return 2
        else:
            return max(s1, s2)

注意返回时分情况讨论。

78 - 子集

给你一个整数数组 nums ,返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。

示例一:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例二:
输入:nums = [0]
输出:[[],[0]]

分析:
这一题和全排列不同,数组元素的顺序不能换,不然就会导致重复的情况。

本题解法参考了简单问题细致分析,两种DFS回溯思路

解法一:定义递归函数找到当前index开始的数组的所有子集

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans = []

        def backtrack(index, tmp):
            ans.append(tmp)
            for i in range(index, len(nums)):
                backtrack(i+1, tmp + [nums[i]])

        backtrack(0, [])
        return ans

这个递归比较有意思的是,没有一个显式定义的出口,而是通过循环控制递归结束。即 for 循环完成,当前层次的递归就结束了。回溯还是通过递归完成的,因为递归完成一次相当于入栈出栈一次,出栈即回溯了。

那么此处的递归函数backtrack到底是什么意思呢,它实现了以当前index为开头的所有情况,即数组 nums[index, len(nums)] 的所有子集。

以[1,2,3]为例,此解法元素的添加顺序是:[], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]

解法二:对数组的每个元素考虑选或不选

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ans = []

        def backtrack(index, tmp):
            if index == len(nums):
            #定义了递归的base情况,当nums中的所有数字都被考虑过一遍时,return
                return ans.append(tmp)
            #子集中选择当前nums[index]
            backtrack(index+1, tmp + [nums[index]])
            #子集中不选择当前nums[index]
            backtrack(index+1, tmp)
        
        backtrack(0, [])
        return ans

想法非常简单,对于每个元素都考虑选进子集和不选进子集两种情况,一次遍历数组中的所有元素,就会形成一棵树,最终的所有情况是树的所有叶子结点。我感觉这个解法里回溯的意味很浅,主要是递归,不过递归本质上就是回溯了。

以[1,2,3]为例,此解法元素的添加顺序是:[1,2,3], [1,2], [1,3], [1], [2,3], [2], [3], []

你可能感兴趣的:(leetcode,算法,数据结构,动态规划,leetcode)