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.
- Revisión anterior: LeetCode Weekly Competition No. 352 Una competencia semanal especial sobre subarreglos
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
- LeetCode Weekly Competition No. 352 Una competencia semanal especial sobre subarreglos
- LeetCode Weekly Competition No. 350 Ventana deslizante y preguntas de plantilla de discretización
- LeetCode Biweekly Competition 107. Interesantes preguntas T2
- LeetCode Biweekly Competition No. 104 Programación dinámica de flujo, pensamiento estructurado de hierro