Programación dinámica: problema de subsecuencia (C++)

Prefacio

Artículos anteriores sobre programación dinámica:

  1. Introducción a la programación dinámica: modelo de secuencia de Fibonacci y multiestado.
  2. Programación dinámica: problemas de rutas y subarreglos

problema de subsecuencia

1. Subsecuencia creciente más larga (media)

Enlace : subsecuencia creciente más larga

  • Descripción de la pregunta
    Insertar descripción de la imagen aquí

  • Pasos para hacer las preguntas

  1. Representación de estado
    Para dp lineal, usualmente usamos las dos representaciones siguientes:
    (1) Terminando en una posición determinada,...
    (2) Comenzando desde una posición determinada,...
    Generalmente usamos el primero de estos dos métodos, con A cierta posición es el final, y combinada con los requisitos de la pregunta, podemos definir la representación del estado como dp[i]: la longitud de la subsecuencia creciente más larga entre todas las subsecuencias que terminan en la posición i.

  2. Ecuación de transición de estado
    Para la subsecuencia que termina en la posición i, hay dos posibilidades:
    (1) No seguir a otros, solo uno solo, dp[i] = 1
    (2) Seguir [0, 1, 2,... , i - 1] detrás de estas posiciones, asumiendo 0 <= j <= i - 1, si la subsecuencia se puede mantener incrementando (nums[j] < nums[i]), se puede conectar después de esta posición.
    Enumere j de 0~i - 1 para ver qué posición tiene la longitud más grande:
    es decir, dp[i] = max(dp[i], dp[j] + 1)

  3. Inicialice
    cada posición a un mínimo de 1 y todas se inicializarán a 1.

  4. Orden de llenado del formulario:
    asegúrese de que al completar el estado actual, se haya calculado el estado requerido y que el orden de llenado del formulario sea de izquierda a derecha .

  5. Valor de retorno
    No hay forma de determinar directamente la posición final de la subsecuencia más larga, y el valor máximo se actualiza mientras dp .

  • Código
class Solution {
    
    
public:
    int lengthOfLIS(vector<int>& nums) {
    
    
        int n = nums.size();
        //dp[i]表示以i位置为结尾的最长递增子序列
        vector<int> dp(n, 1);    
       int ret = 1;
        for(int i = 1; i < n; i++)
        {
    
    
            //从[0, i-1]看一圈,找接在那个符合条件的位置后面可以让子序列最长
            for(int j = 0; j < i; j++)       
                if(nums[j] < nums[i])
                    dp[i] = max(dp[i], dp[j] + 1);                         
            //看看能不能更新最大
            ret = max(ret, dp[i]);
        }
        return ret;
        //时间复杂度:O(N ^ 2)
        //空间复杂度:O(N)
    }
};

2. Secuencia de swing (mediana)

Enlace : Secuencia de swing

  • Descripción de la pregunta
    Insertar descripción de la imagen aquí

  • Pasos para hacer las preguntas

  1. Representación de estado
    Según la experiencia anterior, podemos definir la representación de estado como dp[i]: la longitud máxima de todas las secuencias de swing que terminan en la posición i .

  2. Ecuación de transición de estado
    Para una secuencia de oscilación con una longitud mayor que 1, hay dos situaciones:
    (1) En un estado ascendente, como (1, 7, 4, 9).
    (2) En un estado decreciente, como (1, 17, 10).
    Por lo tanto, necesitamos registrar dos estados al mismo tiempo, donde f [i] representa la longitud de la secuencia de oscilación más larga que termina en la posición i y está en el estado ascendente, y g [i] representa el estado descendente .

    Después de analizar la secuencia del swing, analicemos una sola posición, hay dos posibilidades:
    (1) No conectar detrás de otros, jugar solo, dp[i] = 1
    (2) Conectar después de [0, 1, 2,… , i - 1] detrás de estas posiciones, sea 0 <= j <= i - 1.
    ① Si está en un estado ascendente después de estar conectado a la posición j (nums [i] - nums [j] > 0), debe terminar en la posición j y estar en un estado descendente, es decir, f [i ] = gramo[j] + 1 .
    ② Si está en un estado descendente después de estar conectado a la posición j (nums [i] - nums [j] <0), debe terminar en la posición j y estar en un estado ascendente, es decir, g [ i ] = f[j] + 1 .

  3. La longitud mínima de la secuencia de inicialización
    es 1 y todas las posiciones se inicializan en 1 .

  4. Orden de llenado del formulario:
    asegúrese de que al completar el estado actual, se haya calculado el estado requerido y que el orden de llenado del formulario sea de izquierda a derecha .

  5. Valor de retorno
    No hay forma de determinar directamente el final de la secuencia de swing más larga, por lo que el valor máximo se actualiza mientras dp .

  • Código
class Solution {
    
    
public:
    int wiggleMaxLength(vector<int>& nums) 
    {
    
    
        //dp[i]表示以i位置为结尾的最长摆动序列长度
        int n = nums.size();
        vector<int> f(n, 1);//处于上升状态
        vector<int> g(n, 1); //处于下降状态
        int ret = f[0]; //记录最终结果
        for(int i = 1; i < n; i++)
        {
    
    
            for(int j = 0; j < i; j++)
            {
    
    
                int gap = nums[i] - nums[j];
                //处于上升
                if(gap > 0)
                    f[i] = max(f[i], g[j] + 1);
                //处于下降
                else if(gap < 0)
                    g[i] = max(g[i], f[j] + 1);
                //相同的情况为1不用处理
            }
            ret = max({
    
    ret, f[i], g[i]});
        }
        return ret;
        //时间复杂度:O(N ^ 2)
        //空间复杂度:O(N)
    }
};

3. El número de subsecuencias crecientes más largas (media)

Enlace : El número de subsecuencias crecientes más largas.

  • Descripción de la pregunta
    Insertar descripción de la imagen aquí

  • Pasos para hacer las preguntas

  1. Representación de estado
    Según la experiencia previa, podemos definir la representación de estado dp[i]: el número de la subsecuencia creciente más larga que termina en la posición i .

  2. La ecuación de transición de estado
    necesita actualizar el número de subsecuencias crecientes más largas en la posición actual. No es más que mirar qué posiciones tienen las mayores longitudes. Pero el problema es que ahora solo hay el número de secuencias en las posiciones anteriores y no hay longitud, por lo que necesitamos agregar una tabla para registrar la longitud:
    (1) count[i]: el número de la subsecuencia creciente más larga que termina en la posición i
    (2) len[i]: la longitud de la subsecuencia creciente más larga terminando en la posición i

    len[i] Como se mencionó anteriormente, analizamos count[i]:
    (1) Si no está conectado después de otros, la longitud máxima es 1, count[i] = 1
    (2) Está conectado después [0, 1, 2,..., i - 1] Después de estas posiciones, suponiendo 0 <= j <= i - 1, la subsecuencia que puede seguir aumentando (nums[j] < nums[i]) se puede conectar después de esta posición.

    Enumere j de 0~i - 1 y analice de acuerdo con la longitud que sigue a esa posición:
    ① Si es menor que la longitud original (len[i] > len[j] + 1), no se preocupe.
    ② Es mayor que la longitud original (len [i] <len [j] + 1), no importa cuántos sean los números de secuencia originales, debe cortarse severamente y actualizar el número para que sea más largo, es decir, contar[i] = contar[j ] ] .
    ③La misma longitud que el original (len[i] == len[j] + 1), el recuento aumenta, es decir, count[i] += count[j] .

  3. La longitud mínima de la secuencia de inicialización
    es 1 y todas se inicializan en 1 .

  4. Orden de llenado del formulario:
    asegúrese de que al completar el estado actual, se haya calculado el estado requerido y que el orden de llenado del formulario sea de izquierda a derecha .

  5. El valor de retorno
    (1) completa el trabajo anterior. Sabemos la longitud y el número de la subsecuencia creciente más larga que termina en cada posición, pero no sabemos la secuencia más larga que termina en qué posiciones, por lo que necesitamos que un lado dp actualice la longitud máxima . longitud_máxima .
    (2) Conociendo la longitud máxima, solo necesitamos recorrer la tabla de recuento una vez y contar las secuencias con la longitud max_length .

  • Código
class Solution {
    
    
public:
    int findNumberOfLIS(vector<int>& nums) {
    
    
        int n = nums.size();
        vector<int> count(n, 1); //f[i]表示以i位置为结尾的最长子序列个数
        auto len = count; //g[i]表示以i位置为结尾的最长递增子序列长度
        int max_length = len[0];
        for(int i = 1; i < n; i++)
        {
    
    
            for(int j = 0; j < i; j++)
            {
    
    
                if(nums[i] > nums[j])
                {
    
    
                    //找到了更加长的
                    if(len[i] < len[j] + 1)
                    {
    
    
                        len[i] = len[j] + 1;
                        count[i] = count[j];
                    }
                    else if(len[i] == len[j] + 1) //长度相同                 
                        count[i] += count[j];               
                }
            }
            max_length = max(max_length, len[i]);
        }
        int ret = 0;    //返回值
        //遍历一次,计算最长序列个数
        for(int i = 0; i < n; i++)      
            if(len[i] == max_length)
                ret += count[i];
        return ret;
        //时间复杂度:O(N ^ 2)
        //空间复杂度:O(N)
    }
};

4. La cadena de par más larga (mediana)

Enlace : El mayor número de pares de enlaces.

  • Descripción de la pregunta
    Insertar descripción de la imagen aquí

  • Pasos para hacer las preguntas

  1. Representación de estado
    Según la experiencia previa, definimos la representación de estado dp[i]: la longitud de la cadena de pares de números más larga que termina en la posición i .

  2. Ecuación de transición de estado
    El análisis de esta pregunta es básicamente el mismo que el de la subsecuencia creciente más larga anterior.
    (1) No te conectes detrás de otros, juega solo, dp[i] = 1
    (2) Conéctate detrás de las posiciones [0, 1, 2,..., i - 1], asumiendo 0 <= j <= i - 1. Si se cumple el número de requisitos de la cadena de pares (pares[j][1] <pares[i][0]), se puede conectar después de esta posición.
    Enumere j de 0~i - 1 para ver qué posición tiene la longitud más grande:
    es decir, dp[i] = max(dp[i], dp[j] + 1)

  3. La longitud mínima de inicialización
    es 1 y todos se inicializan en 1 .

  4. Orden de llenado del formulario:
    asegúrese de que al completar el estado actual, se haya calculado el estado requerido y que el orden de llenado del formulario sea de izquierda a derecha .

  5. Valor de retorno
    No hay forma de determinar directamente el final de la cadena de pares más larga, por lo que el valor máximo se actualiza mientras se ejecuta dp .

  • Código
class Solution {
    
    
public:
    int findLongestChain(vector<vector<int>>& pairs) {
    
                  
        sort(pairs.begin(), pairs.end());   //先排序
        int n = pairs.size();
        //dp[i]表示以i位置为终点的最长长度
        vector<int> dp(n, 1);
        int ret = 1; //记录最长
        for(int i = 1; i < n; i++)
        {
    
    
            for(int j = 0; j < i; j++)                      
                if(pairs[j][1] < pairs[i][0]) //如果可以接在后面            
                    dp[i] = max(dp[i], dp[j] + 1);                         
            ret = max(ret, dp[i]);
        }
        return ret;
        //时间复杂度:O(N ^ 2)
        //空间复杂度:O(N)
    }
};

5. Subsecuencia de diferencia definida más larga (media)

Enlace : Subsecuencia de diferencia definida más larga

  • Descripción de la pregunta
    Insertar descripción de la imagen aquí

  • Pasos para hacer las preguntas

  1. Representación de estado
    Basándonos en la experiencia previa, definimos la representación de estado dp[i]: la longitud de la subsecuencia aritmética más larga que termina en la posición del subíndice i .

  2. Ecuación de transición de estado
    La mejor manera de resolver este problema es usar subsecuencias incrementales, pero escribir de esta manera caducará. Podemos analizar las razones:
    (1) Las subsecuencias crecientes pueden ir seguidas de muchas posiciones.
    (2) La subsecuencia aritmética solo puede ir seguida de una posición fija, como (1, 2, 3, 4), la diferencia es 1, el 4 en ella solo puede ir seguido de 3 y otros juicios son redundantes.

    Entonces cambiemos nuestro pensamiento, o (1, 2, 3, 4), la diferencia es 1. Cuando completamos la posición 4, si podemos encontrar directamente el que termina en 3 (arr [i] - diferencia) El más largo aumentar la subsecuencia está bien.
    Podemos vincular el elemento arr[i] a dp[i] para crear una tabla hash. Podemos hacer programación dinámica directamente en esta tabla hash. La ecuación de transición de estado es:
    hash[i] = hash[arr[i] - diferencia] + 1 .

  3. Inicialización
    Al rellenar el formulario, si el preestado no existe, no lo procesaremos por separado (0 más 1 se convierte en 1, que corresponde a uno propio). Entonces solo necesitamos poner el primer elemento en la tabla hash, hash[arr[0]] = 1 .

  4. Orden de llenado del formulario:
    asegúrese de que al completar el estado actual, se haya calculado el estado requerido y que el orden de llenado del formulario sea de izquierda a derecha .

  5. Valor de retorno
    El final de la subsecuencia aritmética más larga es incierto, por lo que el valor máximo se actualiza mientras se ejecuta dp .

  • Código
class Solution
{
    
    
    public:
    int longestSubsequence(vector<int>& arr, int difference) 
    {
    
    
        // 创建⼀个哈希表
        unordered_map<int, int> hash; // {arr[i], dp[i]}
        hash[arr[0]] = 1; // 初始化
        int ret = 1;
        for(int i = 1; i < arr.size(); i++)
        {
    
    
            hash[arr[i]] = hash[arr[i] - difference] + 1;
            ret = max(ret, hash[arr[i]]);
        }
        return ret;
        //时间复杂度:O(N)
        //空间复杂度:O(N)
    }
};

6. Longitud de la subsecuencia de Fibonacci más larga (mediana)

Enlace : Longitud de la subsecuencia de Fibonacci más larga

  • Descripción de la pregunta
    Insertar descripción de la imagen aquí

  • Pasos para hacer las preguntas

  1. Representación de estado
    Según la experiencia, podemos definir la representación de estado como la longitud de la secuencia de Fibonacci más larga que termina en la posición i, pero hay un problema fatal con esta definición: no sabemos si puede formar una secuencia de Fibonacci después de una cierta posición Secuencia de escritura .
    No se puede determinar un elemento, pero si conocemos los dos últimos elementos de la secuencia de Fibonacci, podemos deducir el elemento anterior y así resolver el problema anterior.
    Entonces defina una tabla bidimensional dp[i][j]: tomando las posiciones i y j como la longitud de la secuencia de Fibonacci más larga de los dos últimos elementos .

  2. La ecuación de transición de estado
    estipula que i es menor que j, donde j comienza a enumerar desde [2, n - 1] e i comienza a enumerar desde [1, j - 1].
    Supongamos nums[i] = b, nums[j] = c, entonces el primer elemento de esta secuencia es a = c - b . Discutimos en base a la situación de a:
    (1) a existe, sea su subíndice k, Y a <b, en este momento c se puede conectar después de la secuencia de Fibonacci que termina en a y b, entonces dp[i][j] = dp[k][i] + 1 .
    (2) a existe, pero b <a <c. En este momento, solo b y c pueden estar compuestos por sí mismos, dp [i] [j] = 2 .
    (3) a no existe, en este momento solo puede estar compuesto por b y c, dp [i] [j] = 2 .

    Descubrimos que en el proceso de transición de estado, necesitamos determinar el subíndice del elemento a . Por lo tanto, podemos unir todos los "elementos + subíndices" antes de dp y colocarlos en la tabla hash.

  3. La longitud mínima de inicialización
    es 2 y todos se inicializan en 2 .

  4. El orden de cumplimentación del formulario
    se fija hasta el último número y se enumera el penúltimo número .

  5. Valor de retorno
    El final de la subsecuencia de Fibonacci más larga es incierto, por lo que el valor máximo se actualiza mientras dp .

  • Código
class Solution {
    
    
public:   
    int lenLongestFibSubseq(vector<int>& arr)
    {
    
    
        int n = arr.size();
        //i->j
        dp[i][j]表示以i,j为后两个的斐波那契数列最长长度
        vector<vector<int>> dp(n, vector<int>(n, 2));
        unordered_map<int, int> hash;
        for(int i = 0; i < n; i++) hash[arr[i]] = i;

        int ret = 2;
        for (int j = 2; j < n; j++)
        {
    
    
            for (int i = 1; i < j; i++)
            {
    
    
                int former = arr[j] - arr[i];
                //a b c,a < b 并且a存在
                if (former < arr[i] &&  hash.count(former))
                {
    
    
                    dp[i][j] = dp[hash[former]][i] + 1;
                }
                ret = max(ret, dp[i][j]);
            }
        }
        //斐波那契序列最小为3,为2的情况返回0
        return ret > 2 ? ret : 0;
        //时间复杂度:O(N)
        //空间复杂度:O(N ^ 2)
    }
};

7. Secuencia aritmética más larga (mediana)

Enlace : secuencia aritmética más larga

  • Descripción de la pregunta
    Insertar descripción de la imagen aquí

  • Pasos para hacer las preguntas

  1. La representación del estado
    es similar a la pregunta anterior. Solo un elemento no puede determinar la apariencia de la secuencia aritmética. Necesitamos los siguientes dos elementos para determinarla, por lo que definimos una tabla bidimensional dp[i][j]: toma i y j como los dos últimos La longitud de la subsecuencia aritmética de elementos más larga .

  2. La ecuación de transición de estado
    estipula que i es menor que j, suponiendo nums[i] = b, nums[j] = c, entonces el primer elemento de esta secuencia es a = 2 * nums[i] - nums[j] (aritmética secuencia) , discutimos de acuerdo con la situación de a:
    (1) a existe, y sea su subíndice k. En este momento, c se puede conectar a la secuencia que termina en a y b, luego dp [i] [j] = dp[k][i] + 1 .
    (2) a no existe, en este momento solo puede estar compuesto por b y c, dp [i] [j] = 2 .

    Encontramos que en la ecuación de transición de estado, necesitamos determinar el subíndice del elemento a . Por lo tanto, podemos unir todos los "elementos + subíndices" y colocarlos en la tabla hash. Hay dos opciones para la tabla hash de esta pregunta:
    (1) Ponerla directamente en la tabla hash antes de dp . Pueden aparecer elementos duplicados (esta pregunta está desordenada, la pregunta anterior es estrictamente incremental), y estos elementos duplicados deben registrarse., sus subíndices deben formarse en una matriz. Antes de completar el formulario, se debe recorrer la matriz para encontrar los subíndices requeridos. Esto lleva mucho tiempo y esta solución no se puede aprobar .
    (2) Solo se puede almacenar en la tabla hash mientras se usa DP. Después de usar la posición i , se almacena en la tabla hash. Sin embargo, el orden de llenado de la tabla debe ser fijo, penúltimo, y del primero al último en la enumeración., no se puede rellenar el formulario fijando la primera pregunta y enumerando la segunda de la pregunta anterior. Veamos este ejemplo: [0, 2, 4, 4, 4, 6, 8, 4, 9, 4, 4]. Los últimos 4 son fijos. Cuando los primeros 4 son los penúltimos, debes buscar el anterior 4. El subíndice (aquí está precedido por [0,2] y no hay 4, lo que significa que este número no debe estar en la tabla hash, sino en la inversa fija, y la enumeración de las dos inversas forma El método de llenado garantiza que se guarde en la tabla hash. Está completamente desordenado en este punto)

  3. La longitud mínima de inicialización
    es 2 y todos se inicializan en 2 .

  4. El orden de llenado del formulario
    se fija desde el penúltimo, y el orden de llenado del formulario es desde el último hasta el último en la enumeración .

  5. Valor de retorno
    El final de la secuencia aritmética más larga es incierto, por lo que el valor máximo se actualiza mientras se ejecuta dp .

  • Código
class Solution {
    
    
public:
    //dp[i][j]表示以i,j为结尾的最长等差数列长度
    int longestArithSeqLength(vector<int>& nums) {
    
    
        int n = nums.size();
        unordered_map<int, int> hash;       
        hash[nums[0]] = 0;
        vector<vector<int>> dp(n, vector<int>(n, 2));
        int ret = 2;
        for (int i = 1; i < n; i++) //倒数第二个
        {
    
    
            for (int j = i + 1; j < n; j++)
            {
    
    
                int former = 2 * nums[i] - nums[j];
                if (hash.count(former))
                    dp[i][j] = dp[hash[former]][i] + 1;
                ret = max(ret, dp[i][j]);
            }
            hash[nums[i]] = i;
        }
        return ret;
        //时间复杂度:O(N ^ 2)
        //空间复杂度:O(N ^ 2)
    }
};

8. División de secuencia aritmética II - subsecuencia (difícil)

Enlace : Secuencia Aritmética División II - Subsecuencia

  • Descripción de la pregunta
    Insertar descripción de la imagen aquí

  • Pasos para hacer las preguntas

  1. La representación del estado
    es consistente con la pregunta anterior. Solo un elemento no puede determinar la apariencia de la secuencia aritmética. Necesitamos los siguientes dos elementos para determinarla, por lo que definimos una tabla bidimensional dp[i][j]: toma i y j como los dos últimos El número de subsecuencias aritméticas de elementos .

  2. Ecuación de transición de estado
    En primer lugar, no hay subsecuencias aritméticas repetidas en esta pregunta. Siempre que las posiciones de los elementos sean diferentes, se consideran subsecuencias diferentes. Por ejemplo, la matriz [7,7,7,7,7 ] tiene tantas subsecuencias aritméticas como 16.

    Se estipula que i es menor que j, suponiendo nums[i] = b, nums[j] = c, entonces el primer elemento de esta secuencia es a = 2 * nums[i] - nums[j] , discutiremos Basado en la situación de a:
    (1) a existe. En este momento, c puede ir seguido de la secuencia que termina en a y b. Sea k el subíndice de a. La situación del subíndice aquí es diferente de la anterior, porque puede haber múltiples a. Necesitamos usar una matriz de subíndices para registrar los subíndices de a en diferentes posiciones. Cuando k <i (a es en i (anteriormente), dp[i][j] += dp[k][i] + 1 , el +1 aquí representa el grupo [a, b, c], solo suma todas las a que cumplen las condiciones .
    (2) a no existe, en este momento solo puede estar compuesto por b y c, dp [i] [j] = 2 .

    Encontramos que en la ecuación de transición de estado, necesitamos determinar el subíndice del elemento a . Por lo tanto, podemos unir todos los "elementos + matrices de subíndices" y colocarlos en la tabla hash.

  3. Inicialización
    No se requiere inicialización, el valor predeterminado es 0.

  4. Orden de cumplimentación del formulario:
    Primero se fija el orden de cumplimentación del formulario , seguido de la enumeración .

  5. Valor de retorno
    Defina la suma variable y acumule mientras dp .

  • Código
class Solution {
    
    
public:
    int numberOfArithmeticSlices(vector<int>& nums) {
    
    
        int n = nums.size();
        //dp[i][j]表示以i,j为结尾的等差数列个数,规定j > i
        //前置可能有存在多个,需要一一加起来
        vector<vector<int>> dp(n, vector<int>(n));
        unordered_map<long long, vector<int>> hash; //数据和下标数组绑定
        for(int i = 0; i < n; i++)
            hash[nums[i]].push_back(i);
        int sum = 0;
        for(int j = 2; j < n; j++)
        {
    
    
            for(int i = 1; i < j; i++)
            {
    
    
                long long former = (long long)nums[i] * 2 - nums[j]; //处理数据溢出
                if(hash.count(former))
                {
    
    
                    for(auto k : hash[former])
                    {
    
    
                        //former必须在左边
                        if(k < i)
                            dp[i][j] += dp[k][i] + 1; //这里的1表示[a,b,c]单独一组
                        else //当前a下标不满足,后面的也一定不满足,可以直接跳出
                            break;
                    }      
                }
                sum += dp[i][j];
            }
        }
        return sum;
        //相同数据不多的情况下
        //时间复杂度:O(N ^ 2)
        //空间复杂度:O(N ^ 2)
    }
};

Supongo que te gusta

Origin blog.csdn.net/2301_76269963/article/details/132914914
Recomendado
Clasificación