leetcode-动态规划

通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。常常适用于有重叠子问题和最优子结构性质的问题,并且记录所有子问题的结果,因此动态规划方法所耗时间往往远少于朴素解法。

动态规划有自底向上和自顶向下两种解决问题的方式。自顶向下即记忆化递归自底向上就是递推

使用动态规划解决的问题有个明显的特点,一旦一个子问题的求解得到结果,以后的计算过程就不会修改它,这样的特点叫做无后效性,求解问题的过程形成了一张有向无环图。动态规划只解决每个子问题一次,具有天然剪枝的功能,从而减少计算量。

目录

5. 最长回文字串

10. 正则表达式匹配

22. 括号生成

 32. 最长有效括号

42. 接雨水

44. 通配符匹配

45. 跳跃游戏ii

53. 最大子数组和

55. 跳跃游戏

62. 不同路径

63. 不同路径ii

 64. 最小路径和


5. 最长回文字串

如果一个子串是回文串,那么它首尾两个字母去除之后仍然是个回文串,用P(i,j)表示字符串s第i个字母到第j个字母组成的串是否为回文串:

 因此动态规划的转移方程为:

设置动态规划的边界条件:长度为1的子串一定是回文串,长度为2的子串当两个字母相同时为回文串。

 从长度较短的字符串向长度较长的字符串进行转移。

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        n = len(s)
        if n < 2:
            return s
        if n == 2:
            if s[0] == s[1]:
                return s
            else:
                return ""
        max_len = 1
        begin = 0
        # 初始化状态转移矩阵,dp[i][j]表示s[i:j+1]是否为回文串
        dp = [[False for _ in range(n)] for _ in range(n)]
        for i in range(n):
            dp[i][i] = True

        # 枚举子串长度,从更短的子串开始循环
        for L in range(2, n+1):
            # 枚举左边界
            for left in range(n):
                right = left + L - 1
                if right > n-1:
                    break
                if s[left] != s[right]:
                    dp[left][right] = False
                else:
                    dp[left][right] = dp[left+1][right-1]
                if dp[left][right] and L > max_len:
                    max_len = L
                    begin = left

        return s[begin: begin+max_len]

第二种方法:枚举子串中心向外扩展

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        start, end = 0, 0

        def expandAroundCenter(s, left, right):
            while left >= 0 and right < len(s) and s[left] == s[right]:
                left -= 1
                right += 1
            return left + 1, right - 1

        for center in range(len(s)):
            left1, right1 = expandAroundCenter(s, center, center)
            left2, right2 = expandAroundCenter(s, center, center + 1)
            if right1 - left1 > end - start:
                start, end = left1, right1
            if right2 - left2 > end - start:
                start, end = left2, right2
        return s[start: end + 1]

10. 正则表达式匹配

leetcode-动态规划_第1张图片

p中的一个字符只能匹配s中一个字符,匹配具有唯一性;

p中的一个字符+“*”可以匹配s中任意个字符,不具有唯一性;

因此考虑使用动态规划堆匹配的方案进行枚举,用f[i][j]表示s的前i个字符与p中的前j个字符是否匹配:

leetcode-动态规划_第2张图片

leetcode-动态规划_第3张图片

s[i] = p[j-1]即s末尾的字符匹配星号前面的字符

s[i] ≠ p[j-1]即丢掉星号和星号前面的字符,尝试匹配前面一串字符

leetcode-动态规划_第4张图片

class Solution(object):
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        s_len, p_len = len(s), len(p)

        # 检测s[i-1]与p[j-1]是否匹配
        def isMatch(i, j):
            if i == 0:
                return False
            if p[j - 1] == '.':
                return True
            return s[i - 1] == p[j - 1]

        # dp[i][j]代表s[:i]与p[:j]是否匹配
        dp = [[False] * (p_len + 1) for _ in range(s_len + 1)]
        dp[0][0] = True  # 空字符串自动匹配

        for i in range(s_len + 1):
            for j in range(1, p_len + 1):
                if p[j - 1] == '*':
                    if isMatch(i, j - 1):
                        dp[i][j] = dp[i][j - 2] | dp[i - 1][j]
                    else:
                        dp[i][j] = dp[i][j - 2]
                else:  # 当s[i]与p[j]匹配时,s[:i]与p[:j]是否匹配取决于dp[i-1][j-1]
                    if isMatch(i, j):
                        dp[i][j] = dp[i - 1][j - 1]

        return dp[s_len][p_len]

22. 括号生成

 当知道n个括号的所有组合以后,求n+1个括号只要往n个括号的结果中插入一对括号即可,插入的过程中保证’(‘’)‘的顺序,并使用set()去重:

class Solution(object):
    def generateParenthesis(self, n):
        """
        :type n: int
        :rtype: List[str]
        """
        res = ["()"]
        if n == 1:
            return res

        def insertpar(res):
            npath = len(res)
            npar = len(res[0])
            ans = []
            for i in range(npath):
                for left in range(npar):
                    for right in range(left+1, npar+2):
                        tmp = list(res[i])
                        tmp.insert(left, '(')
                        tmp.insert(right, ')')
                        ans.append(''.join(tmp))
            return ans

        while n > 1:
            res = insertpar(res)
            res = list(set(res))
            n -= 1

        return res

组装法:

class Solution(object):
    def generateParenthesis(self, n):
        """
        :type n: int
        :rtype: List[str]
        """
        if n == 1:
            return list({'()'})
        res = set()
        for i in self.generateParenthesis(n - 1):
            for j in range(len(i) + 2):
                res.add(i[0:j] + '()' + i[j:])
        return list(res)

已知i < n时括号所有排列,对于i = n的情况,假设新加进来的左括号在最左边,那么之前的所有括号要么在新括号里面,要么在新括号右面:

p + q = n - 1

class Solution(object):
    def generateParenthesis(self, n):
        """
        :type n: int
        :rtype: List[str]
        """
        if n == 0:
            return []
        total_l = [[None], ["()"]]
        for i in range(2, n + 1):  # 开始计算i组括号时的括号组合
            l = []
            for p in range(i):  # 开始遍历 p q ,其中p+q=i-1 , p 作为索引
                q = i - 1 - p
                now_list1 = total_l[p]
                now_list2 = total_l[q]
                for k1 in now_list1:
                    for k2 in now_list2:
                        if not k1:
                            k1 = ""
                        if not k2:
                            k2 = ""
                        tmp = "(" + k1 + ")" + k2
                        l.append(tmp)  # 把所有可能的情况添加到 l 中
            total_l.append(l)  # l这个list就是i组括号的所有情况,添加到total_l中,继续求解i=i+1的情况
        return total_l[n]

 32. 最长有效括号

# 暴力解法,超时
class Solution(object):
    def longestValidParentheses(self, s):
        """
        :type s: str
        :rtype: int
        """
        max_length = 0

        def isright(s):
            stack = []
            for i in s:
                if i == '(':
                    stack.append('(')
                else:
                    if not stack:
                        return False
                    stack.pop()
            if stack:
                return False
            return True

        for left in range(len(s) - 1):
            if s[left] == ')':
                continue
            for right in range(left + 1, len(s)):
                if s[right] == '(':
                    continue
                if isright(s[left: right + 1]):
                    max_length = max(max_length, right - left + 1)

        return max_length

动态规划:dp[i]表示以i结尾的最长有效括号的长度,s[i]=='(' 时 dp[i]=0

leetcode-动态规划_第5张图片

如果字符串形如 “......))" i - dp[i-1] - 1如果是“)”则前面必没有有效字符(想使“)”有效则前面必有一个与之对应的“(”,那样dp[i-1]的值就应该更大)

class Solution(object):
    def longestValidParentheses(self, s):
        """
        :type s: str
        :rtype: int
        """
        n = len(s)
        if n == 0:
            return 0
        # dp[i]为以i结尾的最长有效括号的长度
        dp = [0 for _ in range(n)]
        for index in range(1, n):
            if s[index] == '(':
                continue
            if s[index-1] == '(' and s[index] == ')':
                if index > 1:
                    dp[index] = dp[index-2] + 2
                else:
                    dp[index] = 2
            if s[index-1] == ')' and s[index] == ')':
                if index-dp[index-1] > 0:
                    if s[index-dp[index-1]-1] == '(':
                        dp[index] = dp[index-1] + dp[index-dp[index-1]-2] + 2
        return max(dp)

42. 接雨水

leetcode-动态规划_第6张图片

 方法一:动态规划,维护两个数组leftMax和rightMax,记录下标i左边和右边的最大高度。leetcode-动态规划_第7张图片

class Solution(object):
    def trap(self, height):
        """
        :type height: List[int]
        :rtype: int
        """
        n = len(height)
        leftMax = [0 for _ in range(n)]
        leftMax[0] = height[0]
        rightMax = [0 for _ in range(n)]
        rightMax[n-1] = height[n-1]
        n = len(height)
        for i in range(1, n):
            leftMax[i] = max(height[i], leftMax[i-1])
        for i in range(n-2, -1, -1):
            rightMax[i] = max(height[i], rightMax[i+1])
        ans = 0
        for i in range(n):
            ans += min(leftMax[i], rightMax[i]) - height[i]
        return ans

方法二:单调栈

leetcode-动态规划_第8张图片

class Solution(object):
    def trap(self, height):
        """
        :type height: List[int]
        :rtype: int
        """
        ans = 0
        stack = []
        for i, h in enumerate(height):
            while stack and h > height[stack[-1]]:
                top = stack.pop()
                if not stack:
                    break
                left = stack[-1]
                curWidth = i - left - 1
                curHeight = min(height[left], height[i]) - height[top]
                ans += curWidth * curHeight
            stack.append(i)
        return ans

方法三:双指针

leetcode-动态规划_第9张图片

class Solution(object):
    def trap(self, height):
        """
        :type height: List[int]
        :rtype: int
        """
        ans = 0
        left, right = 0, len(height) - 1
        leftMax, rightMax = 0, 0
        while left < right:
            leftMax = max(leftMax, height[left])
            rightMax = max(rightMax, height[right])
            if height[left] < height[right]:
                ans += leftMax - height[left]
                left += 1
            else:
                ans += rightMax - height[right]
                right -= 1
        return ans

44. 通配符匹配

leetcode-动态规划_第10张图片

leetcode-动态规划_第11张图片

class Solution(object):
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        m, n = len(s), len(p)
        # m+1 行 n+1 列,dp[i][j]表示s[i]与p[j]是否匹配
        dp = [[False for _ in range(n+1)] for _ in range(m+1)]
        dp[0][0] = True
        for j in range(1, n+1):
            if p[j-1] == '*':
                dp[0][j] = True
            else:
                break
        for i in range(1, m+1):
            for j in range(1, n+1):
                if p[j-1] == '*':
                    dp[i][j] = dp[i-1][j] | dp[i][j-1]
                elif p[j-1] == '?' or s[i-1] == p[j-1]:
                    dp[i][j] = dp[i-1][j-1]
        return dp[m][n]

45. 跳跃游戏ii

leetcode-动态规划_第12张图片

class Solution(object):
    def jump(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n = len(nums)-1
        i = 0
        count = 0
        while i <= n:
            if i == n :
                return count
            if i + nums[i] < n:
                maxrange = i + nums[i] + nums[i + nums[i]]
                tmp = nums[i]
                for step in range(1, nums[i]+1):
                    twostep = i + step + nums[i+step]
                    if twostep > maxrange:
                        maxrange = twostep
                        tmp = step
                i += tmp
                count += 1
            else:
                count += 1
                return count

53. 最大子数组和

计算以当前下标为结束的最大连续子数组和dp[i]

dp[i] = max(dp[i-1] + nums[i], nums[i])

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        dp = [0 for _ in range(len(nums))]
        dp[0] = nums[0]
        for i in range(1, len(nums)):
            dp[i] = max(dp[i-1] + nums[i], nums[i])
        return max(dp)

55. 跳跃游戏

leetcode-动态规划_第13张图片

class Solution(object):
    def canJump(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        n = len(nums)
        maxrange = 0
        for i in range(n):
            if i <= maxrange:
                maxrange = max(maxrange, i+nums[i])
                if maxrange >= n-1:
                    return True
        return False

62. 不同路径

leetcode-动态规划_第14张图片

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

63. 不同路径ii

leetcode-动态规划_第15张图片

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):
        n = len(obstacleGrid)
        m = len(obstacleGrid[0])
        d = {}
        def dfs(i, j):
            if (i,j) in d:
                return d[i,j]
            #边界/障碍物检查    
            if i >= n or j >= m or obstacleGrid[i][j] == 1:
                return 0		
            #达到重点了    
            if i == n - 1 and j == m - 1:
                return 1
            #继续往右(i,j+1)、往下(i+1,j)递归调用    
            d[i,j] = dfs(i + 1, j) + dfs(i, j + 1)
            return d[i,j]
        return dfs(0, 0)

 64. 最小路径和

class Solution(object):
    def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        n_row, n_col = len(grid), len(grid[0])
        dp = [[0 for _ in range(n_col)] for _ in range(n_row)]
        dp[0][0] = grid[0][0]
        for i in range(1, n_row):
            dp[i][0] = dp[i-1][0] + grid[i][0]
        for j in range(1, n_col):
            dp[0][j] = dp[0][j-1] + grid[0][j]
        for i in range(1, n_row):
            for j in range(1, n_col):
                dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
        return dp[n_row-1][n_col-1]

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