Knowledge Reserve--Basic Algorithms-Dynamic Programming

1 Introduction

The first time I came into contact with dynamic programming, I didn’t know what it meant. After doing the questions, I discovered that dynamic programming is a method of turning big problems into small problems and solving small problems through repeated calculations. This method is called dynamic programming. For example, if you go up the stairs, one or two steps at a time, and find out how many algorithms there are, you can divide it into the number of methods in the last level equal to the number of methods in the previous level plus the number of methods in the first two levels. This is a recursive algorithm. But this often exceeds the time limit because there are a lot of repetitions. For example, there are 5 orders in total, F(5)=F(4)+F(3), where F(4)=F(3)+F(2) , where F(3) is repeatedly calculated. At this time, we need to store the calculated value to avoid repeated calculations. This is the memory recursive algorithm.

Recursion is a way of implementing a program: a function calls itself.

Dynamic programming: It is a problem-solving idea. The results of large-scale problems are calculated from the results of small-scale problems. Dynamic programming can be implemented using recursion (Memorization Search)

scenes to be used

satisfy two conditions

  • Meet one of the following conditions

    • Find the maximum/minimum value (Maximum/Minimum)

    • Ask if it is feasible (Yes/No)

    • Find the feasible number (Count(*))

  • Satisfies Can not sort / swap (Can not sort / swap)

2. Actual combat

2.1 Question 70

Suppose you are climbing stairs. You need  n stairs to reach the top of the building.

You can climb  one 1 or  2 two steps at a time. How many different ways can you climb to the top of a building?

Thoughts: A very classic question using dynamic programming. I have always had a question about how to execute the recursive sequence. Should I calculate two F(n-1) and F(n-2) at the same level at the same time or first? Calculate the previous F(n-1) until F(n-1) is calculated and then calculate F(n-2). This time I printed it out directly and found that the previous F(n-1) was calculated recursively. ), what should be stored has been stored at this time, and there will be no repeated calculation when calculating F(n-2) later.

 Change to bottom-up dp (dynamic programming)

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n==1:
            return 1
        elif n==2:
            return 2
        dp = [0]*(n+1)
        dp[1] = 1
        dp[2] = 2
        for i in range(3,n+1):
            dp[i] = dp[i-1] + dp[i-2]
        
        return dp[n]

Because this problem conforms to the Fibonacci sequence, we can directly calculate

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        a = b = 1
        for i in range(2, n + 1):
            a, b = b, a + b
        return b

2.2 Question 118

Given a non-negative integer  numRows, generate the forward direction of "Yang Hui Triangle"  numRows .

In "Yang Hui's Triangle", each number is the sum of the numbers on its upper left and upper right.

class Solution(object):
    def generate(self, numRows):
        """
        :type numRows: int
        :rtype: List[List[int]]
        """
        if numRows==1:
            return [[1]]
        result = [[1]]
        for i in range(2,numRows+1):
            current = [1]*i
            if i>2:
                for j in range(1,i-1):
                    current[j] = result[i-2][j-1]+result[i-2][j]
            result.append(current)
        
        return result

2.3 Question 198

You are a professional thief planning to steal houses along the street. There is a certain amount of cash hidden in each room. The only restrictive factor that affects your theft is that the adjacent houses are equipped with interconnected anti-theft systems. If two adjacent houses are broken into by thieves on the same night, the system will automatically call the police. .

Given an array of non-negative integers representing the amount of money stored in each house, calculate the maximum amount of money you can steal in one night  without triggering the alarm device  .

Experience: When you see this kind of problem of seeking the maximum amount, you must think of dynamic programming. Dynamic programming is to divide big problems into small problems one by one to solve. This question is that the problem of the maximum amount can be divided into the problem of finding the local maximum amount, and then it can be solved recursively. It feels like an upgraded question on climbing stairs, with an additional max judgment. From left to right. You can also use recursion, from right to left.

class Solution(object):
    def rob(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) == 1:
            return nums[0]
        elif len(nums) == 2:
            return max(nums[0], nums[1])
        dp = [0]*(len(nums)+1)
        dp[1] = nums[0]
        dp[2] = nums[1]
        for i in range(3,len(nums)+1):
            dp[i] = max(dp[i-3] + nums[i-1], dp[i-2] + nums[i-1])
        return max(dp[-1], dp[-2])

The above method uses an array to store the results. Considering that the highest total amount of each house is only related to the highest total amount of the first two houses of the house, a rolling array can be used, and only the highest total amount of the first two houses needs to be stored at each moment.

class Solution(object):
    def rob(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) == 1:
            return nums[0]
        elif len(nums) == 2:
            return max(nums[0], nums[1])
        dp = [0]*(len(nums)+1)
        left = nums[0]
        right = max(nums[0], nums[1])
        for i in range(3,len(nums)+1):
            left, right = right, max(left + nums[i-1], right)
        return right

2.4 Question 279

complete knapsack problem

Translate the topic: a complete square number is an item (can be used indefinitely), and a positive integer n is a backpack. How many items are there at least when this backpack is filled?

The analysis of the five steps of dynamic rules is as follows :

  1. Determine the meaning of the dp array (dp table) and the subscripts, dp[j]: The minimum number of perfect square numbers whose sum is j is dp[j]
  2. Determine the recursion formula. dp[j] can be derived from dp[j - i * i], and dp[j - i * i] + 1 can be used to form dp[j]. At this time we need to select the smallest dp[j], so the recursion formula is: dp[j] = min(dp[j - i * i] + 1, dp[j]);
  3. How to initialize the dp array, dp[0] represents the minimum number of perfect square numbers whose sum is 0, then dp[0] must be 0. A classmate asked, 0 * 0 is also considered a type. Why is dp[0] equal to 0? Look at the topic description and find several complete square numbers (such as 1, 4, 9, 16, ...). The topic description does not say that it should start from 0, and dp[0]=0 is completely for recursive formulas. What should the non-zero subscript dp[j] be? From the recursive formula dp[j] = min(dp[j - i * i] + 1, dp[j]); we can see that the smallest dp[j] must be selected every time, so the dp[ with a non-0 subscript j] must be initially set to the maximum value, so that dp[j] will not be overwritten by the initial value during recursion.
  4. Determine the traversal order. We know that this is a complete backpack. If we want to find the number of combinations, the outer for loop traverses the items, and the inner for loop traverses the backpack. If you want to find the number of permutations, the outer for loop traverses the backpack, and the inner for loop traverses the items.
  5. Example to derive dp array

So in this question, the outer for traverses the backpack, the inner for traverses the items, or the outer for traverses the items, and the inner for traverses the backpack, it is all possible!

Here I will first give the code for traversing the backpack in the outer layer and traversing the items in the inner layer:

class Solution(object):
    def numSquares(self, n):
        """
        :type n: int
        :rtype: int
        """
        dp = [10]*(n+1)
        dp[0] = 0
        for i in range(n+1):
            j = 1
            while j*j <= i:
                dp[i] = min(dp[i-j*j]+1, dp[i])
                j = j + 1
        return dp[n]

 The outer layer traverses the items and the inner layer traverses the code of the backpack:

Because it is a complete backpack, you can have many items in the outer layer, and the inner layer can always be loaded.

class Solution(object):
    def numSquares(self, n):
        """
        :type n: int
        :rtype: int
        """
        dp = [10]*(n+1)
        dp[0] = 0
        i = 1
        while i * i <= n:
            j = i * i
            while j <= n:
                dp[j] = min(dp[j-i*i]+1, dp[j])
                j = j + 1
            i = i + 1
        return dp[n]

2.5 Question 322

You are given an array of integers  coins representing coins of different denominations and an integer  amount representing the total amount.

Calculate and return the minimum number of coins required to make up the total amount   . If no coin combination completes the total amount, return it  -1 .

You can think of the number of each type of coin as infinite.

Thoughts: It’s another complete backpack question. According to the five steps of the movement rules above,

  1. Determine the meaning of dp[j]. dp[j] is the minimum number of coins when the backpack capacity (that is, amount) is j.
  2. The recursive formula is dp[j]=min(dp[j-coin]+1, dp[j])
  3. Initialization, when the total amount is 0, the number of coins required is 0, so dp[0]=0. And because the minimum is sought, the maximum value float('inf') is stored in the initialization dp.
  4. Determine the order of traversal, and combine the items outside and the backpack inside.
  5. Take an example to derive the dp array. For example, dp[5].
class Solution(object):
    def coinChange(self, coins, amount):
        """
        :type coins: List[int]
        :type amount: int
        :rtype: int
        """
        if amount==0:
            return 0
        dp = [float('inf')]*(amount+1)
        dp[0] = 0
        # 完全背包
        # 组合,外层物品,内层背包
        for i in range(len(coins)):
            coin = coins[i]
            j = coin
            while j <= amount:
                dp[j] = min(dp[j-coin]+1, dp[j])
                j = j + 1
        if dp[amount]==float('inf'):
            return -1
        else:
            return dp[amount]

2.6 Question 139

You are given a string  s and a list of strings  wordDict as a dictionary. Please judge whether it can be spliced ​​using words that appear in the dictionary  s .

Note: It is not required to use all the words that appear in the dictionary, and words in the dictionary can be used repeatedly.

Example 1:

Input: s = "leetcode", wordDict = ["leet", "code"]
 Output: true
 Explanation: Return true because "leetcode" can be spliced ​​into "leet" and "code".

Example 2:

Input: s = "applepenapple", wordDict = ["apple", "pen"]
 Output: true
 Explanation: Return true because "applepenapple" can be spliced ​​from "apple" "pen" "apple". 
Note that you can reuse words from the dictionary.

Example 3:

输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

Experience: What I want is direct violent sorting, continuous recursive looping, cutting off the matching ones, and continuing the recursion on the remaining ones until the loop is completed. But it timed out in the end, giving an outrageous example.

class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        # 背包问题
        # s是背包,wordDict是物品
        # 完全背包问题
        # 排列问题,外层用背包,内层用物品
        def dp(str, wordDict):
            if str == '':
                return True
            for i in range(len(wordDict)):
                a = str[:len(wordDict[i])]
                # print(a, wordDict[i])
                if a == wordDict[i]:
                    if dp(str[len(wordDict[i]):], wordDict):
                        return True
                    else:
                        continue
                else:
                    continue
            return False
        
        return dp(s, wordDict)
        

I wrote it after reading the solution, and insisted on the five-part movement rule. The main first step is about the meaning of dp[i]. i] is whether the characters whose length is i (must be a number) can be composed of words in the dictionary) and the loop can also be a string, and the recursive formula is not very easy to think about.

class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        # 背包问题
        # s是背包,wordDict是物品
        # 完全背包问题
        # 排列问题,外层用背包,内层用物品
        len_ = len(wordDict)
        i = 0
        j = 0
        # 1.dp[i]是当背包容量(字符串长度)为i时,True表示可以拆分为一个或多个单词
        # 2.if dp[i-j]为True且[i,j]有单词存在,则dp[i]也为True
        # 3.dp[0]=True
        # 4.循环顺序,排列问题,外层用背包,内层用物品
        # 5.举例,leetcode长度为8,
        dp = [False]*(len(s)+1)
        dp[0] = True
        for i in range(1,len(s)+1):
            for word in wordDict:
                if s[i-len(word):i]==word:
                    # print(i, word)
                    if dp[i]==False:
                        dp[i] = dp[i-len(word)]
        # print(dp)
        return dp[-1]


        

2.7 Question 300

Given an array of integers  nums , find the length of the longest strictly increasing subsequence in it.

A subsequence  is a sequence derived from an array by removing (or not removing) elements from the array without changing the order of the remaining elements. For example, [3,6,2,7] is  [0,3,1,6,2,2,7] a subsequence of an array.

Experience: I didn’t make it myself, mainly because I was stuck on the recursive formula. I always thought about dp[i]=dp[ij]. The j in the recursive formula can actually be changed in a loop, and I paid too much attention to nums [j]>nums[i], in fact, just find the longest one. Mostly with max.

class Solution(object):
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # 完全背包问题
        # 排列
        # 1.dp[i]为当数组长度为i时(以nums[i-1]为结尾的数组),最长严格递增子序列的长度
        # 2.递推公式,j<i,当nums[j]<nums[i]时,dp[i]=max(dp[j]+1, dp[i])
        # 3.初始化,dp[0]=1,dp=1*(len(nums)+1)
        # 4.循环顺序,排列,外层物品,内层背包
        # 5.举例,[0,1,0,3,2,3]
        dp=[1]*(len(nums))
        max_len = 1
        for i in range(1,len(nums)):
            j = 0
            while j < i:
                if nums[j]<nums[i]:
                    dp[i]=max(dp[j]+1, dp[i])
                j = j + 1
            if max_len<dp[i]:
                max_len = dp[i]

        return max_len

2.8 Question 152

Given an array of integers  nums , please find the non-empty continuous subarray with the largest product in the array (the subarray contains at least one number), and return the product corresponding to the subarray.

The answer to the test case is a  32-bit  integer.

A subarray  is a contiguous subsequence of an array.

Thoughts: After thinking about it for a long time, I still didn’t write the recursion formula correctly, so I had to violently traverse it, and it timed out. Also, you need to use deep copy copy.deepcopy(list) to copy the list, otherwise the value of the original list will be modified.

class Solution(object):
    def maxProduct(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # 连续,nums是物品,最大子数组的乘积是背包
        # 1.dp[i]是最大乘积子数组对应乘积max,i是nums[i]为尾的子数组
        # 2.递推公式:dp[i]=max(nums[j]*(nums[j+1]...nums[i]), dp[i])
        # 3.初始值:dp[0]=nums[0], dp=[1]*len(nums)
        # 4.循环顺序:排序,外层物品。内层背包。
        # 5.示例:[2,3,-2,4]
        dp = copy.deepcopy(nums)
        # dp[0] = nums[0]
        max_ = dp[0]
        for i in range(1,len(nums)):
            j = 0
            while j < i:
                a = 1
                for k in range(j,i+1):
                    a = a * nums[k]
                dp[i] = max(a, dp[i])
                j = j + 1
            if max_ < dp[i]:
                max_ = dp[i]
        print(dp)
        return max_

After reading the analysis, I learned that since there will be multiplication of negative numbers, it is necessary to maintain the maximum value and the minimum value at the same time. The results are as follows

class Solution(object):
    def maxProduct(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # 连续,nums是物品,最大子数组的乘积是背包
        # 1.dp[i]是最大乘积子数组对应乘积max,i是nums[i]为尾的子数组
        # 2.递推公式:
        # dp_max[i]=max(dp_max[i-1]*nums[i], dp_min[i-1]*nums[i], dp[i])
        # dp_min[i]=min(dp_max[i-1]*nums[i], dp_min[i-1]*nums[i], dp[i])
        # 3.初始值:dp[0]=nums[0], dp=[1]*len(nums)
        # 4.循环顺序:排序,外层物品。内层背包。
        # 5.示例:[2,3,-2,4]
        dp_max = copy.deepcopy(nums)
        dp_min = copy.deepcopy(nums)
        max_ = nums[0]
        for i in range(1,len(nums)):
            j = 0
            dp_max[i]=max(dp_max[i-1]*nums[i], dp_min[i-1]*nums[i], dp_max[i])
            dp_min[i]=min(dp_max[i-1]*nums[i], dp_min[i-1]*nums[i], dp_min[i])
            if max_ < dp_max[i]:
                max_ = dp_max[i]
            elif max_ < dp_min[i]:
                max_ = dp_min[i]
        return max_

It was found that the maximum and minimum values ​​do not need to be represented by lists at all, because only the previous max and min are used, so they are directly represented by two variables, which greatly reduces time and memory.

class Solution(object):
    def maxProduct(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # 连续,nums是物品,最大子数组的乘积是背包
        # 1.dp[i]是最大乘积子数组对应乘积max,i是nums[i]为尾的子数组
        # 2.递推公式:
        # dp_max[i]=max(dp_max[i-1]*nums[i], dp_min[i-1]*nums[i], dp[i])
        # dp_min[i]=min(dp_max[i-1]*nums[i], dp_min[i-1]*nums[i], dp[i])
        # 3.初始值:dp[0]=nums[0], dp=[1]*len(nums)
        # 4.循环顺序:排序,外层物品。内层背包。
        # 5.示例:[2,3,-2,4]
        dp_max = nums[0]
        dp_min = nums[0]
        max_ = nums[0]
        for i in range(1,len(nums)):
            dp_max_before = dp_max
            dp_min_before = dp_min
            dp_max=max(dp_max_before*nums[i], dp_min_before*nums[i], nums[i])
            dp_min=min(dp_max_before*nums[i], dp_min_before*nums[i], nums[i])
            if max_ < dp_max:
                max_ = dp_max
            elif max_ < dp_min:
                max_ = dp_min
        return max_

2.9 Question 416

You are given a  non-empty  array  containing only positive  integers   . Please determine whether this array can be divided into two subsets so that the sum of the elements of the two subsets is equal.nums

Example 1:

Input: nums = [1,5,11,5]
 Output: true
 Explanation: The array can be divided into [1, 5, 5] and [11].

Example 2:

Input: nums = [1,2,3,5]
 Output: false
 Explanation: The array cannot be split into two elements and equal subsets.

Experience: After reading the analysis, I still don’t understand it. After watching the video of code randomness, I feel that the speech is still very good, but it’s not enough to just listen to it. Notes have been written. 

class Solution(object):
    def canPartition(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        # 可以看作是0-1背包问题
        # 题目可以抽象为在nums中寻找一个子数组,要求最大价值为总和的一半
        # 该题的重量和价值是一样的
        # 1.dp[j]为背包容量为j时(这里背包的容量和最大价值是一样的,都为总和的一半),子数组的最大价值。
        # 2.递推公式:dp[j]=max(dp[j], dp[j-weight[i]]+value[i])
        # 3.初始化:dp[0]=0, dp=[0]*(len(nums)+1)
        # 4.循环顺序:外层物品正序,内层背包容量倒序(因为是0-1背包,正序的话会重复计算)
        # 5.举例:[1,5,11,5]
        if sum(nums)%2 == 1:
            return False
        target = sum(nums)/2
        dp = [0]*(target+1)
        # 先拿出第一个物品,然后背包的容量递减,看看每个容量怎么装
        # 再放下第一个物品拿出第二个,背包容量再次从target递减,看看现在背包里还能放下吗,最大放多少
        # 依次按顺序拿出物品
        for i in range(len(nums)): # 物品
            j = target
            while j >= nums[i]: # 背包容量要大于物品
                dp[j]=max(dp[j], dp[j-nums[i]]+nums[i]) # 取max是因为最大也不会超过背包的容量target
                if dp[j] == target:
                    return True
                j = j - 1
        return False

 

Guess you like

Origin blog.csdn.net/Orange_sparkle/article/details/132404295