Reserva de conocimientos--Algoritmos básicos-Programación dinámica

1. Introducción

La primera vez que entré en contacto con la programación dinámica no sabía lo que significaba, después de hacer las preguntas descubrí que la programación dinámica es un método para convertir grandes problemas en pequeños problemas y resolver pequeños problemas mediante cálculos repetidos. Se llama programación dinámica. Por ejemplo, si sube las escaleras, uno o dos escalones a la vez, y descubre cuántos algoritmos hay, puede dividirlo entre la cantidad de métodos en el último nivel igual a la cantidad de métodos en el nivel anterior. más el número de métodos en los dos primeros niveles. Este es un algoritmo recursivo. Pero esto a menudo excede el límite de tiempo, porque hay muchas repeticiones, como un total de 5 pasos, F (5) = F (4) + F (3), donde F (4) = F (3) + F(2), donde F(3) se calcula repetidamente. En este momento, necesitamos almacenar el valor calculado para evitar cálculos repetidos. Este es el algoritmo recursivo de memoria.

La recursividad es una forma de implementar un programa: una función se llama a sí misma.

Programación dinámica: es una idea de resolución de problemas: los resultados de problemas a gran escala se calculan a partir de los resultados de problemas a pequeña escala. La programación dinámica se puede implementar mediante recursividad (búsqueda de memorización)

escenas a utilizar

cumplir dos condiciones

  • cumplir una de las siguientes condiciones

    • Encuentre el valor máximo/mínimo (Máximo/Mínimo)

    • Preguntar si es factible (Sí/No)

    • Encuentra el número factible (Count(*))

  • Satisface No se puede ordenar/intercambiar (No se puede ordenar/intercambiar)

2. Combate real

2.1 Pregunta 70

Supongamos que estás subiendo escaleras. Necesitas  n pasos para llegar a la cima del edificio.

Cada vez que puedas subir  1 o  2 dar un escalón. ¿De cuántas maneras diferentes puedes llegar a la cima del edificio?

Pensamientos: Una pregunta muy clásica usando programación dinámica. Siempre he tenido una pregunta sobre cómo ejecutar la secuencia recursiva. ¿Debo calcular dos F(n-1) y F(n-2) al mismo nivel al mismo tiempo o ¿Primero? Calcule el F (n-1) anterior hasta que se calcule F (n-1) y luego calcule F (n-2). Esta vez lo imprimí directamente y descubrí que se calculó el F (n-1) anterior. recursivamente. ), en este momento el almacenamiento se ha almacenado y el cálculo de F (n-2) no se repetirá más adelante.

 Cambiar a dp ascendente (programación dinámica)

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]

Debido a que el problema se ajusta a la secuencia de Fibonacci, el cálculo directo

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 Pregunta 118

Dado un número entero no negativo  numRows, generar antecedentes del Triángulo Yanghui  numRows .

En el "Triángulo de Yang Hui", cada número es la suma de los números en la parte superior izquierda y superior derecha.

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 Pregunta 198

Eres un ladrón profesional que planea robar casas en la calle. Hay una cierta cantidad de efectivo escondida en cada habitación. El único factor de restricción que afecta su robo es que las casas adyacentes están equipadas con sistemas antirrobo interconectados. Si dos casas adyacentes son asaltadas por ladrones la misma noche, el sistema Alarmará automáticamente .

Dada una serie de números enteros no negativos que representan la cantidad de dinero almacenada en cada casa, calcula la cantidad máxima de dinero que puedes robar en una noche  sin activar el dispositivo de alarma  .

Experiencia: cuando vea este tipo de preguntas sobre cómo encontrar la cantidad máxima, debería pensar en la programación dinámica, que consiste en dividir un gran problema en pequeños problemas para resolver. Esta pregunta es que el problema de la cantidad máxima se puede dividir en el problema de encontrar la cantidad máxima local, y luego se puede resolver de forma recursiva. Se siente como una pregunta mejorada sobre cómo subir escaleras, con un criterio máximo adicional. De izquierda a derecha. También puedes utilizar la recursividad, de derecha a izquierda.

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])

El método anterior utiliza una matriz para almacenar los resultados. Teniendo en cuenta que la cantidad total más alta de cada casa solo está relacionada con la cantidad total más alta de las dos primeras casas de la casa, se puede utilizar una matriz rodante y solo es necesario almacenar la cantidad total más alta de las dos primeras casas en cada una. momento.

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 Pregunta 279

problema completo de mochila

Traduce la pregunta: un número cuadrado perfecto es un artículo (se puede usar en piezas ilimitadas) y un número entero positivo n es una mochila. ¿Cuántos artículos hay al menos para completar esta mochila?

El análisis de los cinco pasos de las reglas dinámicas es el siguiente :

  1. Determine el significado de la matriz dp (tabla dp) y los subíndices, dp[j]: El número mínimo de números cuadrados perfectos cuya suma es j es dp[j]
  2. Determine la fórmula recursiva, dp [j] se puede derivar de dp [j - i * i], y dp [j - i * i] + 1 puede formar dp [j]. En este punto queremos elegir el dp[j] más pequeño, por lo que la fórmula recursiva: dp[j] = min(dp[j - i * i] + 1, dp[j]);
  3. Cómo inicializar la matriz dp, dp [0] representa el número mínimo de números cuadrados perfectos cuya suma es 0, entonces dp [0] debe ser 0. Un compañero preguntó, 0 * 0 también se considera un tipo, ¿por qué dp [0] es igual a 0? Mirando la descripción del problema, encuentre varios números cuadrados perfectos (como 1, 4, 9, 16,...). La descripción del problema no dice que deba comenzar desde 0. dp[0]=0 es puramente para fórmula recursiva. ¿Cuál debería ser el subíndice dp[j] distinto de cero? De la fórmula recursiva dp[j] = min(dp[j - i * i] + 1, dp[j]); podemos ver que el dp[j] más pequeño debe seleccionarse cada vez, por lo que el dp[ con un El subíndice j] distinto de 0 debe establecerse inicialmente en el valor máximo, de modo que dp[j] no se sobrescriba con el valor inicial durante la recursividad.
  4. Determine el orden de recorrido. Sabemos que esta es una mochila completa. Si desea encontrar el número de combinaciones, el bucle for externo atraviesa los elementos y el for interno atraviesa la mochila. Si desea encontrar el número de permutaciones, el bucle for exterior atraviesa la mochila y el bucle interior atraviesa los elementos.
  5. Ejemplo de derivación de matriz dp

Entonces, en esta pregunta, el exterior para atraviesa la mochila, el interior para atraviesa los artículos, o el exterior para atraviesa los artículos y el interior para atraviesa la mochila, ¡todo es posible!

Aquí primero daré el código para atravesar la mochila en la capa exterior y atravesar los elementos en la capa interior:

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]

 La capa exterior atraviesa los artículos y la capa interior atraviesa el código de la mochila:

Al ser una mochila completa, puedes tener muchos artículos en la capa exterior, y la capa interior siempre se puede cargar.

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 Pregunta 322

Se le proporciona una serie de números enteros  coins que representan monedas de diferentes denominaciones y un número entero  amount que representa la cantidad total.

Calcula y devuelve el número mínimo de monedas necesarias para completar el importe total   . Si ninguna combinación de monedas completa el monto total, devuélvela  -1 .

Puedes considerar que la cantidad de cada moneda es infinita.

Pensamientos: Es otra pregunta completa sobre la mochila. De acuerdo con los cinco pasos de las reglas de movimiento anteriores,

  1. Determine el significado de dp[j]. dp[j] es el número mínimo de monedas cuando la capacidad de la mochila (es decir, la cantidad) es j.
  2. La fórmula recursiva es dp[j]=min(dp[j-coin]+1, dp[j])
  3. Inicialización, cuando la cantidad total es 0, la cantidad de monedas requeridas es 0, por lo que dp [0] = 0. Y como se busca el mínimo, el valor máximo float('inf') se almacena en el dp de inicialización.
  4. Determine el orden de recorrido y combine los elementos del exterior y la mochila del interior.
  5. Dé un ejemplo para deducir la matriz dp. Por ejemplo 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 Pregunta 139

Se le proporciona una cadena  s y una lista de cadenas  wordDict a modo de diccionario. Juzgue si se puede unir usando palabras que aparecen en el diccionario  s .

Nota: No es necesario utilizar todas las palabras que aparecen en el diccionario y las palabras del diccionario se pueden utilizar repetidamente.

Ejemplo 1:

Entrada: s = "leetcode", wordDict = ["leet", "code"]
 Salida: verdadero
 Explicación: Devuelve verdadero porque "leetcode" se puede unir en "leet" y "code".

Ejemplo 2:

Entrada: s = "applepenapple", wordDict = ["apple", "pen"]
 Salida: verdadero
 Explicación: Devuelve verdadero porque "applepenapple" se puede unir en "apple" "pen" "apple". 
Tenga en cuenta que puede reutilizar palabras en el diccionario.

Ejemplo 3:

输入: s = "gatosandog", wordDict = ["gatos", "perro", "arena", "y", "gato"]
输出: false

Experiencia: Lo que quiero es una clasificación violenta directa, un bucle recursivo continuo, cortar los que coinciden y continuar la recursividad en los restantes hasta que se complete el bucle. Pero al final se agotó el tiempo, dando un ejemplo escandaloso.

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)
        

Lo escribí después de leer la solución al problema. Me apegué a la regla dinámica de cinco pasos. El paso principal fue sobre el significado de dp[i]. No esperaba que fuera tan abstracto. Siempre usé el resultado. de la pregunta como el significado de dp[i] (la pregunta dp[i] es si los caracteres con longitud i (debe ser un número) pueden estar compuestos por palabras en el diccionario) También hay un bucle o una cadena, y la fórmula recursiva no es muy buena.

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 Pregunta 300

Dada una matriz de números enteros  nums , encuentre la longitud de la subsecuencia estrictamente creciente más larga que contiene.

Una subsecuencia  es una secuencia derivada de una matriz eliminando (o no eliminando) elementos de la matriz sin cambiar el orden de los elementos restantes. Por ejemplo, [3,6,2,7] es  [0,3,1,6,2,2,7] una subsecuencia de una matriz.

Pensamientos: No lo hice yo mismo, principalmente porque estaba atascado en la fórmula recursiva. Siempre pensé en dp [i] = dp [ij]. De hecho, j en la fórmula recursiva también se puede cambiar en un bucle. Además, presté demasiada atención a los números. En el caso de [j]>nums[i], de hecho, simplemente busque el más largo directamente. Principalmente con máx.

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 Pregunta 152

Dada una matriz de números enteros  nums , busque el subarreglo continuo no vacío con el producto más grande en la matriz (el subarreglo contiene al menos un número) y devuelva el producto correspondiente al subarreglo.

La respuesta al caso de prueba es un  entero de 32 bits  .

Un subarreglo  es una subsecuencia contigua de un arreglo.

Pensamientos: Después de pensarlo durante mucho tiempo, todavía no escribí la fórmula de recursividad correctamente, así que tuve que atravesarla violentamente y se agotó el tiempo de espera. Además, debe utilizar copia profunda copy.deepcopy(list) para copiar la lista; de lo contrario, se modificará el valor de la lista original.

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_

Después de leer el análisis, aprendí que dado que habrá multiplicación de números negativos, es necesario mantener el valor máximo y el valor mínimo al mismo tiempo, los resultados son los siguientes

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_

Se descubrió que los valores máximo y mínimo no necesitan estar representados por listas en absoluto, porque solo se usan el máximo y el mínimo anteriores, por lo que están representados directamente por dos variables, lo que reduce en gran medida el tiempo y la memoria.

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 Pregunta 416

Se le proporciona una  matriz  no vacía  que contiene solo números enteros  positivos   . Determine si esta matriz se puede dividir en dos subconjuntos para que la suma de los elementos de los dos subconjuntos sea igual.nums

Ejemplo 1:

Entrada: nums = [1,5,11,5]
 Salida: verdadero
 Explicación: La matriz se puede dividir en [1, 5, 5] y [11].

Ejemplo 2:

Entrada: nums = [1,2,3,5]
 Salida: false
 Explicación: La matriz no se puede dividir en dos elementos y subconjuntos iguales.

Pensamientos: Después de leer el análisis, todavía no lo entiendo. Vi el video grabado por Code Caprice y lo encontré muy bueno. Sin embargo, solo escuchar no es suficiente. Todavía tengo que escribirlo yo mismo e imprimirlo. lista dp para entenderlo completamente. En el código se han escrito notas. 

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

 

Supongo que te gusta

Origin blog.csdn.net/Orange_sparkle/article/details/132404295
Recomendado
Clasificación