LeetCode Weekly Competition 353 (2023/07/09) parece haber fallado la prueba de subsecuencia creciente más larga de LIS, parece que se probará nuevamente

Este artículo ha incluido AndroidFamily , problemas técnicos y de lugar de trabajo, preste atención a la cuenta pública [Peng Xurui] y [BaguTree Pro] Knowledge Planet para hacer preguntas.

T1. Encuentra el mayor número alcanzable (Fácil)

  • Etiquetas: simulación

T2 El número máximo de saltos necesarios para llegar al subíndice final (Medio)

  • Etiquetas: programación dinámica, árbol de segmento de línea de punto abierto dinámico

T3 Construir el subarreglo no decreciente más largo (Medio)

  • Etiquetas: programación dinámica

T4. Hacer que todos los elementos de la matriz sean iguales a cero (Medio)

  • Etiquetas: codicioso, matriz diferencial, suma de prefijos


T1. Encuentra el mayor número alcanzable (Fácil)

https://leetcode.cn/problems/find-the-maximum-achievable-number/

Solución (simulación)

Pregunta de simulación simple, en cada ronda de operación, puede agregar 1 a num y restar 1 a x, por lo que el x máximo es num + 2 * t.

class Solution {
    
    
    fun theMaximumAchievableX(num: Int, t: Int): Int {
    
    
        return num + 2 * t
    }
}

Análisis de complejidad:

  • Complejidad del tiempo: O ( 1 ) O(1)O ( 1 )
  • Complejidad del espacio: O ( 1 ) O(1)O ( 1 )

T2 El número máximo de saltos necesarios para llegar al subíndice final (Medio)

https://leetcode.cn/problems/maximum-number-of-jumps-to-reach-the-last-index

Solución 1 (Programación Dinámica)

Similar a "55. Juego de saltos" , pero en esta pregunta, la posición a la que cada operación puede saltar debe satisfacer nums[j] - nums[i] ≤ target, y la posición saltable en el juego de saltos es [i - nums [ i], i + nums[i]], y solo puede saltar a la derecha cada vez (j > i). Es fácil pensar que para la posición nums[i], debe haber un salto desde cierta posición en nums[0, i - 1], es decir, dp[i] = dp[j] + 1, y dinámica la programación se puede utilizar lograr.

class Solution {
    
    
    fun maximumJumps(nums: IntArray, target: Int): Int {
    
    
        // 跳转到差值绝对值不大于 target 的位置,不能返回
        val n = nums.size
        val dp = IntArray(n) {
    
     -1 }
        dp[0] = 0
        for (j in 1 until n) {
    
    
            for (i in 0 until j) {
    
    
                // 寻找合法子问题
                if (-1 != dp[i] && Math.abs(nums[j] - nums[i]) <= target) {
    
    
                    dp[j] = Math.max(dp[j], dp[i] + 1)
                }
            }
        }
        return dp[n - 1]
    }
}

Análisis de complejidad:

  • Complejidad del tiempo: O ( n 2 ) O(n^2)O ( n2 )Hay n estados en total, y cada estado debe enumerarseO ( n ) O(n)O ( n ) estados, por lo que la complejidad de tiempo general esO ( n 2 ) O (n ^ 2)O ( n2 )
  • Complejidad espacial: O ( n ) O(n)O ( n ) espacio de matriz DP.

Solución 2 (árbol de segmento de línea de punto abierto dinámico)

En la solución del problema 1, al enumerar cada elemento nums[i], verificamos el número máximo de saltos de elementos en el rango de [nums[i] - destino, nums[i] - destino] en el elemento predecesor j, entonces que dp[i] = dp[j] + 1, este proceso se puede resumir en dos acciones:

  • Actualización de punto único: dp[nums[i]] = subestado + 1
  • Consulta de intervalo: subestado = max{nums[i] - k, nums[i] - 1}

Esta es una estructura de datos que involucra actualización de punto único y consulta de intervalo, que puede realizar un árbol de segmento de línea (basado en el dominio de valor), y es similar al problema de " 2407. Subsecuencia creciente más larga II ". Además, debido a que el rango de valores de los datos de entrada es muy grande, primero debemos discretizar o usar el árbol de segmento de línea de punto abierto dinámico, y aquí se usa el método de punto abierto dinámico.

class Solution {
    
    
    fun maximumJumps(nums: IntArray, target: Int): Int {
    
    
        // 值域线段树(动态开点)
        val tree = SegementTree(-1e9.toLong(), 1e9.toLong())
        tree.set(0L + nums[0], 0)
        for (i in 1 until nums.size) {
    
    
            val step = tree.query(0L + nums[i] - target, 0L + nums[i] + target) + 1 // 区间查询
            tree.set(0L + nums[i], step) // 单点更新
            if (i == nums.size - 1)  return if (step < 0) -1 else step // 终点
        }
        return -1
    }

    // 动态开点线段树(单点更新没必要做 Lazy)
    private class SegementTree(private val from: Long, private val to: Long) {
    
    
        // 线段树根节点
        private val root = Node(from, to, Integer.MIN_VALUE)

        // 线段树节点(区间范围与区间最值)
        private class Node(val left: Long, val right: Long, var value: Int) {
    
    
            // 中位索引
            val mid : Long get() = (left + right) shr 1 // 存在负数,不能使用无符号右移
            // 左子树
            val l by lazy {
    
     Node(left, mid, Integer.MIN_VALUE) }
            // 右子树
            val r by lazy {
    
     Node(mid + 1, right, Integer.MIN_VALUE) }

            // 单点更新
            fun set(pos: Long, value: Int) {
    
    
                // println("[$left, $right] set=[$pos, $value]")
                // 1、当前节点不处于区间范围内
                if (left > pos || right < pos) return
                // 2、叶子节点
                if (left == right) {
    
    
                    this.value = Math.max(this.value, value)
                    return
                }
                // 3、更新左子树
                if (pos <= mid) l.set(pos, value)
                // 4、更新右子树
                if (pos > mid) r.set(pos, value)
                // 5、合并子节点的结果
                this.value = Math.max(l.value, r.value)
            }

            // 区间查询
            fun query(from: Long, to: Long): Int {
    
    
                // println("[$left, $right] query[$from, $to]")
                // 1、当前节点不处于区间范围内
                if (this.left > to || this.right < from) return Integer.MIN_VALUE
                // 2、当前节点完全处于区间范围之内
                if (this.left >= from && this.right <= to) return value
                // 3、合并子节点的结果
                return Math.max(l.query(from, to), r.query(from, to))
            }
        }

        // 单点更新
        fun set(pos: Long, value: Int) {
    
    
            root.set(pos, value)
        }

        // 区间查询
        fun query(from: Long, to: Long): Int {
    
    
            return root.query(from, to)
        }        
    }
}

Análisis de complejidad:

  • Complejidad del tiempo: O ( nlg U ) O(nlgU)O ( n l gU ) por operación de consultaO ( lg U ) O(lgU)O ( l gU ) la complejidad temporal total esO ( nlg U ) O(nlgU)O ( n l gU ) ;
  • Complejidad espacial: O ( n ) O(n)O ( n ) espacio de tabla hash

T3 Construir el subarreglo no decreciente más largo (Medio)

https://leetcode.cn/problems/longest-non-decreasing-subarray-from-two-arrays/

Solución 1 (Programación Dinámica)

¿Cuántas personas en el área de discusión lo consideran como el problema de subsecuencia creciente más larga de LIS?

Para cada posición i, si y solo elige num1[i] o nums2[i], debe pensar en "cuál elegir".

Definimos dp[i][0/1] para representar el subarreglo no decreciente más largo que termina en la posición i, luego para la posición i, hay 2 opciones:

  • Si nums1[i] puede construir una subsecuencia no decreciente con nums1[i - 1] y nums2[i - 2], entonces se puede construir un subarreglo más largo, dp[i][0] = max{dp[i-1 ][0], dp[i-1][1]} + 1;
  • Si nums2[i] puede construir una subsecuencia no decreciente con nums1[i - 1] y nums2[i - 2], entonces se puede construir un subarreglo más largo, dp[i][1] = max{dp[i-1 ][0], dp[i-1][1]} + 1;
  • De lo contrario, dp[i][0] = dp[i][1] = 1
class Solution {
    
    
    fun maxNonDecreasingLength(nums1: IntArray, nums2: IntArray): Int {
    
    
        val n = nums1.size
        val dp = Array(n) {
    
     IntArray(2) {
    
     1 } }
        var ret = 1
        for (i in 1 until n) {
    
    
            val x = nums1[i]
            val y = nums2[i]
            if (nums1[i] >= nums1[i - 1]) {
    
    
                dp[i][0] = Math.max(dp[i][0], dp[i - 1][0] + 1)
            }
            if (nums1[i] >= nums2[i - 1]) {
    
    
                dp[i][0] = Math.max(dp[i][0], dp[i - 1][1] + 1)
            }
            if (nums2[i] >= nums1[i - 1]) {
    
    
                dp[i][1] = Math.max(dp[i][1], dp[i - 1][0] + 1)
            }
            if (nums2[i] >= nums2[i - 1]) {
    
    
                dp[i][1] = Math.max(dp[i][1], dp[i - 1][1] + 1)
            }
            ret = Math.max(ret, dp[i][0])
            ret = Math.max(ret, dp[i][1])
        }
        return ret
    }
}

Análisis de complejidad:

  • Complejidad del tiempo: O ( n ) O(n)O ( n ) Hay un total de n subproblemas, y cada subproblema necesita enumerar 4 subestados. La complejidad temporal general esO ( n ) O(n)O ( n ) ;
  • Complejidad espacial: O ( n ) O(n)O ( n ) espacio de matriz DP.

Solución 2 (programación dinámica + matriz rodante)

Dado que dp[i] solo utilizará información de dp[i - 1], podemos optimizar la complejidad del espacio utilizando matrices móviles:

class Solution {
    
    
    fun maxNonDecreasingLength(nums1: IntArray, nums2: IntArray): Int {
    
    
        val n = nums1.size
        var dp = IntArray(2) {
    
     1 }
        var ret = 1
        for (i in 1 until n) {
    
    
            val x = nums1[i]
            val y = nums2[i]
            val newDp = IntArray(2) {
    
     1 }
            if (nums1[i] >= nums1[i - 1]) {
    
    
                newDp[0] = Math.max(newDp[0], dp[0] + 1)
            }
            if (nums1[i] >= nums2[i - 1]) {
    
    
                newDp[0] = Math.max(newDp[0], dp[1] + 1)
            }
            if (nums2[i] >= nums1[i - 1]) {
    
    
                newDp[1] = Math.max(newDp[1], dp[0] + 1)
            }
            if (nums2[i] >= nums2[i - 1]) {
    
    
                newDp[1] = Math.max(newDp[1], dp[1] + 1)
            }
            ret = Math.max(ret, newDp[0])
            ret = Math.max(ret, newDp[1])
            dp = newDp
        }
        return ret
    }
}

Análisis de complejidad:

  • Complejidad del tiempo: O ( n ) O(n)O ( n ) Hay un total de n subproblemas, y cada subproblema necesita enumerar 4 subestados. La complejidad temporal general esO ( n ) O(n)O ( n ) ;
  • Complejidad del espacio: O ( 1 ) O(1)O ( 1 ) Espacio de matriz DP.

T4. Hacer que todos los elementos de la matriz sean iguales a cero (Medio)

https://leetcode.cn/problems/apply-operations-to-make-all-array-elements-equal-to-zero/

Solución 1 (codicioso)

Esta pregunta se considera una pregunta muy simple en las preguntas T4 de la competencia semanal, y es más adecuado ponerla en T2.

Encontramos que la ventana K es fija, y para el primer o último elemento de la matriz, sus restricciones son las más fuertes, es decir: solo desde nums[0] como punto inicial o nums[n-1] como punto final de la ventana eliminar. Por lo tanto, para lograr el objetivo de restablecer todos los elementos de la matriz a cero, buscamos el primer elemento distinto de cero nums[i] de izquierda a derecha como punto de partida para eliminar:

  • nums[i] == 0, saltar;
  • nums[i] < 0, no satisfecho, lo que indica que debe haber un número entero mayor en el intervalo de nums[0, i - 1], lo que hace que nums[i] se elimine por transición;
  • nums[i] > 0, si i > nums.size - k, entonces no se puede construir una ventana de longitud k, que no se cumple, de lo contrario, reste nums[i] de la ventana de nums[i,i +k - 1].
class Solution {
    
    
    fun checkArray(nums: IntArray, k: Int): Boolean {
    
    
        for ((i, x) in nums.withIndex()) {
    
    
            if (x == 0) continue
            if (x < 0 || i > nums.size - k) return false
            for (j in i until i + k) {
    
    
                nums[j] -= x
            }
        }
        return true
    }
}

Análisis de complejidad:

  • Complejidad del tiempo: O ( n 2 ) O(n^2)O ( n2 )Tome k = n/2 en el peor de los casos, y la matriz nums es [1,2,3,4,5...] secuencia creciente, entonces la complejidad del tiempo esO (n 2/4) O(n^2) / 4)O ( n2/4 )_
  • Complejidad del espacio: O ( 1 ) O(1)O ( 1 ) usa solo espacio de nivel constante.

Solución 2 (matriz de diferencias)

Cada operación de par en la Solución 1 es equivalente a una actualización de intervalo, y la complejidad del tiempo se puede optimizar usando una matriz de diferencia (usando marcas en lugar de operaciones de resta):

  • Actualización de intervalo: para elementos en el intervalo nums[i, i + k - 1] menos nums[i], luego para diff[i+k] += nums[i], y para diff[i] -= nums[ i ], la complejidad temporal es O(n);
  • Consultar el valor de un solo punto: el método convencional para el valor real de nums[i] es encontrar la suma del prefijo de diff[i], y la complejidad del tiempo es O(n), que no puede cumplir con los requisitos.
// 超出时间限制
class Solution {
    
    
    fun checkArray(nums: IntArray, k: Int): Boolean {
    
    
        val n = nums.size
        // 差分数组
        val diff = IntArray(n + 1)
        for (i in 0 until nums.size) {
    
    
            // 从差分数组还原真实值(求前缀和)
            var x = nums[i]
            for (j in 0 .. i) {
    
    
                x += diff[j] // (存在重复计算)
            }
            if (x < 0) return false
            if (x == 0) continue
            if (i > nums.size - k) return false
            // 区间更新
            diff[i] -= x
            diff[i + k] += x
        }
        return true
    }
}

Análisis de complejidad:

  • Complejidad del tiempo: O ( n 2 ) O(n^2)O ( n2 )En el ciclo interno, es necesario calcular el prefijo de la matriz de diferencia y restaurar el valor de la matriz. La complejidad de tiempo general esO (n 2) O (n ^ 2)O ( n2 )
  • Complejidad espacial: O ( n ) O(n)O ( n ) espacio de matriz diferencial.

Solución 3 (matriz de diferencias + escaneo lineal)

Dado que enumeramos los elementos de la matriz de izquierda a derecha, podemos mantener la suma del prefijo de la matriz de diferencia mientras realizamos actualizaciones de intervalo de izquierda a derecha.

class Solution {
    
    
    fun checkArray(nums: IntArray, k: Int): Boolean {
    
    
        val n = nums.size
        // 差分数组
        val diff = IntArray(n + 1)
        // 差分前缀和
        var diffSum = 0
        for (i in 0 until nums.size) {
    
    
            // 边更新边维护差分数组的前缀和
            diffSum += diff[i]
            val x = nums[i] + diffSum
            if (x == 0) continue
            if (x < 0 || i > nums.size - k) return false
            // 区间更新
            diff[i] -= x
            diff[i + k] += x
            diffSum -= x
        }
        return true
    }
}

Análisis de complejidad:

  • Complejidad del tiempo: O ( n ) O(n)O ( n ) requiere solo un escaneo lineal;
  • Complejidad espacial: O ( n ) O(n)O ( n ) espacio de matriz diferencial.

Revisión anterior

Supongo que te gusta

Origin blog.csdn.net/pengxurui/article/details/131650944
Recomendado
Clasificación