Leetcode动态规划算法示例讲解

Leetcode动态规划算法示例讲解

  • 爬楼梯(Leetcode 70)
    • code 1
    • code 2
  • 打家劫舍(LeetCode 198)
    • code
  • 最大字段和(LeetCode 53)
    • code
  • 挖金矿
    • code
  • 找零钱(LeetCode 322)
    • code
  • 三角形(LeetCode 120)
    • code
  • LeetCode 1025: Discover Game
    • code

动态规划
动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。动态规划经分解得到子问题往往不是互相独立的,有些子问题被重复计算了很多次。因此若保存已解决的子问题的答案,而在需要时再找出已求得的答案,可避免大量的重复计算,节省时间。
解动态规划问题的步骤:
1.找出状态转移方程
2.设计自顶而下的递归算法 (Top-down approach)
3.改写成自底而上的迭代算法(Bottom-up approach)

爬楼梯(Leetcode 70)

题目在这里就不过多介绍了
递归(调用函数自身称为递归)

        if n == 1:
            return 1
        elif n == 2:
            return 2
        else:
            s1 = self.climbStairs(n-1)
            s2 = self.climbStairs(n-2)
            return s1+s2

Time Limit Exceeded

code 1

class Solution:
    def climbStairs(self, n: int) -> int:
        if n<4:return n
        result=[1,2,3]
        for i in range(3,n):
            result.append(result[i-1]+result[i-2])
        return result[n-1]

Runtime: 36 ms, faster than 55.93% of Python3 online submissions for Climbing Stairs.

Memory Usage: 13.1 MB, less than 5.18% of Python3 online submissions for Climbing Stairs.

思路解析:
主要掌握的关键点是将问题拆分成可递归的子问题。当前楼梯数为n级,具有的走法是n-1级楼梯的走法与n-2级楼梯的走法之和(限制一次只能走一级或两级楼梯)
则状态转移方程很明了
ps.递归的好处是程序结构简单逻辑明了,缺点是计算速度慢,空间复杂度高;
更多的情况下考虑将递归变为迭代可以有效地提高计算速度

code 2

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n == 1 or n == 0:
            return 1
        i = 2
        temp1 = 1
        temp2 = 1
        while i <= n:
            temp1 += temp2
            if i == n:
                return temp1
            i += 1
            temp2 += temp1
            if i == n:
                return temp2
            i += 1

运行时间和所占内存和code 1差不多;

思路解析: 欲求得第i层阶梯处的走法总数,仅需知道第i-1层和i-2层的走法,两项相加即可得到i层。代码中temp1和temp2分别表示i-1和i-2层的走法数,其实就是迭代的思想(具体的迭代过程画图走个三四布即可理解)

打家劫舍(LeetCode 198)

code

class Solution(object):
    def rob(self, nums):
        """
        :type nums: List[int]
        :rtype: int

        """
        if nums==[]:
            return 0
        if len(nums)==1:
            return max(nums)
        dp = [0]*len(nums)
        dp[0] = nums[0]
        dp[1] = max(nums[1],nums[0])
        for i in range(2,len(nums)):
            dp[i] = max(dp[i-1],dp[i-2]+nums[i])
            
        return dp[len(nums)-1]

思路:dp[i]表示从0-i户可以打劫到的最大钱数。则有dp[i] = max(dp[i-1],dp[i-2]+nums[i])。第(i-1)户打劫到的最大钱数+不打劫第i户,与第(i- 2)户打劫的最大钱数+打劫第i户,两者中的最大值

最大字段和(LeetCode 53)

code

class Solution:
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        length=len(nums)
        for i in range(1,length):
            #当前值的大小与前面的值之和比较,若当前值更大,则取当前值,舍弃前面的值之和
            subMaxSum=max(nums[i]+nums[i-1],nums[i])
            nums[i]=subMaxSum#将当前和最大的赋给nums[i],新的nums存储的为和值
        return max(nums)

挖金矿

题目:现有金矿数n,人数w,每个金矿对应的金矿数量为g[n],需要的人数为p[n]
现寻求最佳的人数分配方案使得最终获得的金矿数目最多

解题思路: 状态转移方程dp[n,w]=max(dp[n-1,w],dp[n-1,w-p[n-1]]+g[n-1])
n座金矿,w个人情况下的最佳金矿数 = max{(不挖第n座金矿时的人员配置下的最大价值,挖第n座金矿时剩余人员配置下的最大价值+第n座金矿的金矿数目)}
注意点:当j

code

def mine(n,w,g=[],p=[]):
    arr=[0]*w
    for i in range(w):
        if i+1>=p[0]:
            arr[i]=g[0]
    res=copy.deepcopy(arr)
    print(res)
    ####只有第一座金矿时的最大价值
    ####res代表有i-1座金矿时的最大价值
    ####arr代表有i座金矿时的最大价值
    for i in range(1,n):
        for j in range(w):
            if j+1

找零钱(LeetCode 322)

题目: 钱总数amount,现有零钱列表coins,寻找零钱组合使得使用到的零钱数目最少(使用数字可重复)

思路: dp[i]表示amount为i的时候的最少次数
状态转移方程:dp[i]=min(dp[i-coin]) for coin in coins
关键点: dp=[o] + [float(‘inf’)] * amount 排除在选择min时候的干扰

code

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        MAX=float('inf')
        dp=[0]+[MAX]*amount
        for i in range(1,amount+1):
            for coin in coins:
                if i-coin>=0:
                    dp[i]=min(dp[i],dp[i-coin]+1)
        
        if dp[-1]==float('inf'):
            return -1
        else:
            return dp[-1]

三角形(LeetCode 120)

题目: 三角形数堆,寻找一条路径使得从上到下的路径和最小(其中有路径临近选择约束)

思路: 状态转移方程dp[i][j]=min(dp[i-1][j-1]+triangle[i][j] , dp[i-1][j]+triangle[i][j])
注意点:边界情况 j0和ji的情况

code

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        lens=len(triangle)
        dp=[0]*lens
        dp[0]=triangle[0]
        for i in range(1,lens):
            for j in range(i+1):
                if j==0:
                    dp[i]=[dp[i-1][j]+triangle[i][j]]
                elif j==i:
                    dp[i].append(dp[i-1][j-1]+triangle[i][j])
                else:
                    dp[i].append(min((dp[i-1][j-1]+triangle[i][j]),(dp[i-1][j]
                                                                    +triangle[i][j])))
        return min(dp[-1])

LeetCode 1025: Discover Game

题目: Alice和Bob轮流玩游戏,选定数字N,在尽最大可能的前提下,选择x满足两个条件:1、N%x==0 2、0 如果一方无法满足当前两条件则失败
如果Alice能获得胜利则返回True,否则Bob胜利返回False

思路: 动态规划 当前所在i ,for j in rang(1,i),若存在i%j==0且dp[i-j]==False及存在满足取余条件的情况下有任一个取值使得dp[i-j]即对方失败,则本方胜利;否则对方成功本方失败;
同时有一个小捷径;如果i=2t即i是2的倍数 并且dp[t]==False,那么i为True(因为i%t ==0是接下来的j的for循环的一个特殊案例)

code

class Solution:
   def divisorGame(self, N: int) -> bool:
       dp=[False]*(N+1)
       for i in range(2,N+1):
           if i%2==0 and dp[int(i/2)]==False:
               dp[i]=True
           else:
               for j in range(1,i):
                   if i%j==0:
                       if dp[i-j]==False:
                           dp[i]=True
                           break
       return dp[N]

你可能感兴趣的:(Leetcode学习过程,数据结构)