Leetcode动态规划练习

动态规划算法的设计步骤:

  1. 刻画最优解的结构特征(寻找最优子结构)
  2. 递归地定义最优解的值(确定递归公式,动态规划法的重点就是这个)
  3. 计算最优解的值(有两种方法:带备忘录自顶向下法、自底向上法)
  4. 利用计算出的信息构造一个最优解(通常是将具体的最优解输出)

动态规划进阶——————>
git实时更新

文章目录

  • 动态规划算法的设计步骤:
  • Best Time to Buy and Sell Stock
  • Best Time to Buy and Sell Stock II
  • Longest Palindromic Substring
  • Unique Paths
  • Unique Paths II
  • Minimum Path Sum
  • Maximum Subarray

Best Time to Buy and Sell Stock

Say you have an array for which the ith element is the price of a given stock on day i.

If you were only permitted to complete at most one transaction (i.e., buy one and sell one share of the stock), design an algorithm to find the maximum profit.

Note that you cannot sell a stock before you buy one.

Example 1:

Input: [7,1,5,3,6,4]
Output: 5
Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.
             Not 7-1 = 6, as selling price needs to be larger than buying price.

Example 2:

Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.

solution:

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """
        # (1)暴力法,也是最容易想到的
        # maxResult = 0
        # for i in range(len(prices)):
        #     for j in range(len(prices)-i-1):
        #         if(prices[j+i+1]-prices[i] > maxResult):
        #             maxResult = prices[j+i+1]-prices[i]
        # return maxResult
        
        # (2)遍历一次
        minP = sys.maxint
        maxP = 0
        for i in range(len(prices)):
            if (prices[i] < minP):
                minP = prices[i]
            elif(prices[i] - minP > maxP):
                maxP = prices[i] - minP
        return (0 if maxP<0 else maxP)

Best Time to Buy and Sell Stock II

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete as many transactions as you like (i.e., buy one and sell one share of the stock multiple times).

Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).

Example 1:
Input: [7,1,5,3,6,4]
Output: 7
Explanation: Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5-1 = 4.
             Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6-3 = 3.
Example 2:
Input: [1,2,3,4,5]

Output: 4
Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4.
             Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are
             engaging multiple transactions at the same time. You must sell before buying again.

Example 3:
Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.

solution:感觉还是没有用到动态规划的东西,只要后面一天的的股票比前一天高,就把股票的利润加到收益中

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """
        # 只要后面的股票价格比前一天高,就把利润添加到收益中。
        maxP = 0
        for i in range(len(prices)-1):
            if (prices[i+1]>prices[i]):
                maxP+=(prices[i+1]-prices[i])
        return maxP

Longest Palindromic Substring

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

Input: "cbbd"
Output: "bb"

solution:第一次还是先想到的是暴力法(没有通过时间的限制),后面又使用了动态规划,但是运行时间不是很理想,有待改进

动态规划步骤:

矩阵DP初始值都为0

首先是初始化(对角线NP[i][i]上都设置为1,以对角线上的元素为中心的回文长度都为奇数;有两辆相同的将对应的DP[i+1][i]
元素设置为1,以此两元素为中心的回文长度均为偶数)—>

然后是在初始化的基础上进行寻找—>

首先假设回文的长度为3,之后依次递增。如果中心的两侧的两个字符是相同的,将其规划进来,将DP中的对应位置设置为1,在进行的过程中用到了前面的结果,一定程度上提高了效率。

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        # 暴力法,总是最先想到的
        # def isRevrse(s):
        #     if(s==s[::-1]):
        #         return True
        #     else:
        #         return False
        # maxlen = 0
        # maxi = 0
        # for i in range(len(s)):
        #     for j in range(len(s)-i+1):
        #         if(isRevrse(s[i:i+j]) & (j > maxlen)):
        #             maxlen = j
        #             maxi = i
        # return s[maxi:maxi+maxlen]
        ### 动态规划一
        dp=[[0 for i in range(len(s))] for j in range(len(s))]
        maxLen = 1
        left = 0
        for i in range(len(s)): #初始化
            dp[i][i]=1
            left = i
        for i in range(len(s)-1): #初始化
            if s[i]==s[i+1]:
                dp[i][i+1]=1
                left = i
                maxLen = 2
        for l in range(3,len(s)+1,1):     # l表示当前要检测的字串串的长度
            for i in range(0,len(s)-l+1,1):  # i表示当前要检测的字符串的起始位置
                j = i + l -1                 # j表示当前要检测的字符串的尾位置
                if (dp[i+1][j-1]==1 and s[i]==s[j]):
                    dp[i][j] = 1
                    left = i
                    maxLen = l
        return s[left:left+maxLen]

Unique Paths

A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
How many possible unique paths are there?

Above is a 7 x 3 grid. How many possible unique paths are there?

Note: m and n will be at most 100.

Example 1:

Input: m = 3, n = 2
Output: 3
Explanation:
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
1. Right -> Right -> Down
2. Right -> Down -> Right
3. Down -> Right -> Right
Example 2:

Input: m = 7, n = 3
Output: 28

Solution:看到题目的第一步,按照动态规划的思路来思考。

因为机器人只能向右和向下走,到达第一行格子都只有一种走法:dp[0][]=1,到达第一列格子只有一中走法:dp[][0]=1。

那么到达一个格子的路线数,就应该是左侧格子路线加上上侧格子的路线的和:dp[i][j] = dp[i-1][j]+dp[i][j-1]。

但是这里不用找出最优解,而是找出了所有的解。采用备忘录的方法。

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        dp = [[0 for i in range(m)] for j in range(n)]
        for i in range(m):
            dp[0][i] = 1
        for i in range(n):
            dp[i][0] = 1
        for i in range(m-1):
            for j in range(n-1):
                dp[j+1][i+1] = dp[j+1][i]+dp[j][i+1]
        return dp[n-1][m-1]

Unique Paths II

A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).

The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).

Now consider if some obstacles are added to the grids. How many unique paths would there be?

An obstacle and empty space is marked as 1 and 0 respectively in the grid.

Note: m and n will be at most 100.

Example 1:

Input:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
Output: 2
Explanation:
There is one obstacle in the middle of the 3x3 grid above.
There are two ways to reach the bottom-right corner:
1. Right -> Right -> Down -> Down
2. Down -> Down -> Right -> Right

Solution:这一道题目与I题的区别是有了障碍,但是基本思路还是不变的。

只是一旦碰到障碍即将通过此障碍点的路线数量设置为0.

其他思路不变。

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        n = len(obstacleGrid)
        m = len(obstacleGrid[0])
        dp = [[0 for i in range(m)] for j in range(n)]
        for i in range(m):
            if(obstacleGrid[0][i]==1):
                break;
            dp[0][i] = 1
        for i in range(n):
            if(obstacleGrid[i][0]==1):
                break;
            dp[i][0] = 1
        for i in range(m-1):
            for j in range(n-1):
                if(obstacleGrid[j+1][i+1]==1):
                    continue;
                dp[j+1][i+1] = dp[j+1][i]+dp[j][i+1]
        return dp[n-1][m-1]

Minimum Path Sum

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.

Note: You can only move either down or right at any point in time.

Example:

Input:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
Output: 7
Explanation: Because the path 1→3→1→1→1 minimizes the sum.

Solution:此题依然是动态规划的问题,可是说是Unique Paths和Unique PathsII的进化版,前面两道题是求路径的数量,而这道题是求所有路径中的最小值,和前面的题目具有层层递进的关系。

dp[i][j]最优解:从左侧和上侧选出最小的值,加上本点对应的值,即是到达本点的最小值:dp[j+1][i+1] = min(dp[j][i+1],dp[j+1][i])+grid[j+1][i+1]

递归的定义最优解:dp中每一个点的值,都是最小值.

方法:备忘录自顶向下,

输出最优解.

前面的dp用来记录路径的数量,这里的dp用来记录到达dp[i][j]的最小值,

class Solution(object):
    def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        n = len(grid)	#行数
        m = len(grid[0])	#列数
        dp = [[0 for i in range(m)] for j in range(n)]
        dp[0][0] = grid[0][0]
        for i in range(m-1):
            dp[0][i+1] = grid[0][i+1]+dp[0][i]
        for i in range(n-1):
            dp[i+1][0] = grid[i+1][0]+dp[i][0]
        for i in range(m-1):
            for j in range(n-1):
                dp[j+1][i+1] = min(dp[j][i+1],dp[j+1][i])+grid[j+1][i+1]
        return dp[n-1][m-1]

下面是用时最短的提交方案.可以看出他是直接在grid上进行的操作,所以用时上面要比我的要短.

class Solution(object):
    def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        if grid is None:
            return 0
        rows = len(grid)
        cols = len(grid[0])
        if cols == 0:
            return 0
        
        for i in range(1, cols):
            grid[0][i] += grid[0][i - 1]
        for i in range(1, rows):
            grid[i][0] += grid[i - 1][0]
        for i in range(1, rows):
            for j in range(1, cols):
                grid[i][j] += min(grid[i - 1][j], grid[i][j - 1])
        return grid[-1][-1]

Maximum Subarray

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.
Follow up:

If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

solution:很久以前看这道题目的时候,是通过暴力法来解决的,但是没有通过时间的限制。最近练习动态规划也是有了一点点的感觉,来把这道题重新解一次。
这个问题由Jon Bentley曾经讨论(1984年9月第27卷第9期ACM P885通讯)
以下是原文:

the paragraph below was copied from his paper (with a little modifications)

algorithm that operates on arrays: it starts at the left end (element A[1]) and scans through to the right end (element A[n]), keeping track of the maximum sum subvector seen so far. The maximum is initially A[0]. Suppose we've solved the problem for A[1 .. i - 1]; how can we extend that to A[1 .. i]? The maximum
sum in the first I elements is either the maximum sum in the first i - 1 elements (which we'll call MaxSoFar), or it is that of a subvector that ends in position i (which we'll call MaxEndingHere).

MaxEndingHere is either A[i] plus the previous MaxEndingHere, or just A[i], whichever is larger.

局部最优子结构:包含最后一个元素时的最大和字串,
最优解的值:MaxSofar = max(MaxSofar,MaxEnd){ MaxEnd = max(MaxEnd+nums[i],nums[i]), ManEnd可以看作辅助最优子结构 },MaxSofar是目标,MaxEnd是从第一个元素一直遍历到第i个元素时,从第一个元素到第i个元素包含第i个元素时的最优解。(包含第i个元素方便后面的遍历,因为题目要求最优子序列是连续的,如果没有第i个元素后面的遍历将不方便包含后面元素的遍历进行)
计算最优解:自底向上
最优解:输出最大的MaxSofar

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        count = len(nums)
        if count==0:
            return 0
        MaxEnd = nums[0]
        MaxSof = nums[0]
        for i in range(count-1):
            MaxEnd = max(MaxEnd+nums[i+1],nums[i+1])
            MaxSof = max(MaxSof,MaxEnd)
        return MaxSof
class Solution(object):
    def maxSubArray(self, nums):
        # 下面和上面的思路时一样的,但是更加简洁高效
        count = len(nums)
        for i in range(1,count):
            if nums[i-1] > 0:
                nums[i] = nums[i] + nums[i-1]
        return max(nums)

你可能感兴趣的:(算法)