[LeetCode] -Codicioso-1

prefacio

Registre los codiciosos problemas relacionados con los algoritmos encontrados al cepillar LeetCode, el primer artículo

452. Detona un globo con el mínimo de flechas

Cuando vi la operación de intervalo, originalmente quería usar la diferencia para hacerlo. Después de escribir una prueba, descubrí que la matriz era demasiado grande porque había una muestra [[-2147483646, -2147483645], [2147483646, 2147483647]] . Entonces quiero ver si puedo ajustar la relación correspondiente entre los subíndices de la matriz. Mirando el significado de la pregunta, el valor de cada intervalo de globo es [-2 31, 2 31 - 1]. Parece que no importa cómo ajuste Si no funciona,
entonces veamos la solución .

Para utilizar el menor número de flechas, elegimos con avidez cada flecha en el extremo x de un determinado globo i , de modo que siempre que su inicio x sea menor o igual que el extremo x de i , todos los globos posteriores pueden ser traspasado por la misma flecha. Entonces, primero ordene en orden ascendente según x final , luego seleccione el primer extremo x como la posición de la primera flecha, y luego retroceda para ver cuántos globos pueden ser perforados por esta flecha juntos, una vez que encuentre un globo cuyo inicio x es mayor que la posición de la primera flecha, el extremo x de este globo se utiliza como la posición de la siguiente flecha, y así sucesivamente.

public int findMinArrowShots(int[][] points) {
    
    
    if (points.length == 0) {
    
    
        return 0;
    }
    Arrays.sort(points, new Comparator<int[]>() {
    
    
        public int compare(int[] point1, int[] point2) {
    
    
            //要升序排序,本来习惯写类似于 return o1.val - o2.val 来实现,这里由于样例中有出现
            //[[-2147483646,-2147483645],[2147483646,2147483647]] 这样的例子,加减法会溢出,所以只能通过比较来实现
            if (point1[1] > point2[1]) {
    
    
                return 1;
            } else if (point1[1] < point2[1]) {
    
    
                return -1;
            }
            return 0;
        }
    });
    int pos = points[0][1];
    int ans = 1;
    for (int[] balloon: points) {
    
    
        if (balloon[0] > pos) {
    
    
            pos = balloon[1];
            ans++;
        }
    }
    return ans;
}

55. Juego de saltos

Para saber si podemos llegar a la última posición, solo necesitamos mantener la posición maxFar más lejana que se pueda alcanzar con cada paso de salto, siempre que encontremos que maxFar es mayor o igual que la última posición, podemos determinar inmediatamente que podemos llegar a la última posición. Entonces solo necesitamos calcular con avidez y mantener esta distancia más lejana.

public boolean canJump(int[] nums) {
    
    
    int len = nums.length;
    int maxFar = 0;
    for (int i = 0; i < len; i++) {
    
    
    	//与跳跃游戏Ⅱ不同,这里有可能跳不到终点:如果当前位置已经超出了前面所能跳到的最远距离,就说明无法继续往后跳了
        if (i > maxFar) return false;
        maxFar = Math.max(maxFar, i + nums[i]); //维护最远距离
        if(maxFar >= len - 1) return true; //发现最远距离已经达到最后一个位置直接返回true
    }
    return true;
}

45. Juego de salto II

Suponiendo que nums [0] es igual a x (x debe ser mayor o igual a 1), entonces el rango que se puede saltar en el primer paso es [1, x]. Esto no significa que debamos ir ax en este primer paso. Esta es una solución óptima local pero no necesariamente conduce a una solución óptima global.

Lo que debemos pensar es que [1,x] es el rango al que puede saltar nuestro primer salto. En otras palabras, la posición objetivo del primer salto puede ser cualquier posición en este intervalo. En otras palabras, la posición objetivo de el segundo salto puede ser La posición inicial debe ser una posición en este intervalo, pero puede ser cualquier posición en este intervalo

Por lo tanto, para cada posición i en este intervalo, podemos calcular la posición más lejana que pueden alcanzar saltando un paso atrás, es decir, i + nums [i], y el resultado más grande no es el resultado del segundo salto. ¿Distancia que puede alcanzar? Para ser precisos, es la posición más lejana a la que puede saltar el primer + segundo salto. Aunque es posible, no necesitamos saber dónde está la posición inicial del salto más lejano, solo necesitamos saber que es menor que x, la posición más lejana del salto anterior. De hecho, no necesitamos saber que el rango del primer salto es [1, x], solo necesitamos saber que la posición más lejana es x. Podemos considerar la coordenada 0 como la posición más lejana del salto 0. Para que el primer salto ya no sea especial.

Calculando con esta estrategia, es obvio que el resultado final será el recuento mínimo de saltos global.

Aunque la palabra codicia no se menciona, de hecho, en nuestro algoritmo, la distancia máxima de cada salto es una consideración muy importante y también puede verse como una manifestación de codicia.

Tenga en cuenta que la pregunta dice que siempre puede llegar a la última posición, por lo que no es necesario considerar la situación en la que no puede continuar saltando después de saltar al medio.

public int jump(int[] nums) {
    
    
    int length = nums.length;
    int lastJumpMaxFar = 0; //记录上一跳所能到达的最远位置
    int maxPosition = 0; //维护更新下一跳所能到达的最远位置
    int steps = 0;       //记录最终步数
    for (int i = 0; i < length - 1; i++) {
    
    
        maxPosition = Math.max(maxPosition, i + nums[i]);
        //可以尝试提前跳出循环,可能不用遍历整个数组
        if(maxPosition >= length - 1) return steps + 1; 
        //已经跳到了上一跳所能到达的最远位置,还想往后的话,必须再跳一步,
        //且这一步所能跳到的最远距离就是 maxPosition
        if (i == lastJumpMaxFar) {
    
     
            lastJumpMaxFar = maxPosition;
            steps++;
        }
    }
    return steps;
}

1353. Número máximo de reuniones a las que se puede asistir

Para una serie de reuniones a las que se puede asistir en un momento determinado, se debe elegir con avidez la que tiene el tiempo de finalización más pequeño, es decir,
para un día determinado (a partir de 1, es decir, el primer día) , puede haber una serie de horas de inicio menores o iguales al día, la reunión cuya hora de finalización es mayor o igual al día, entonces, para qué reunión asistir en este momento, la que tenga la hora de finalización más pequeña debe ser con avidez seleccionado, porque la reunión con una hora de finalización mayor tiene más espacio para la selección

Dado que la selección de cada punto de tiempo seguirá cambiando a medida que cambie el día del punto de tiempo, se requiere un mantenimiento dinámico.

Por lo tanto, debe usar una cola de montón/prioridad y debe seleccionar la que tenga la hora de finalización más pequeña, por lo que si hay un montón superior pequeño cuyo elemento es la hora de finalización de la reunión, primero ordene todas las reuniones en orden ascendente. Ordene
según la hora de inicio de la reunión, porque primero debemos ordenar la reunión según la hora de inicio. El tiempo juzga a qué reuniones es probable que asistan en un determinado momento del día, lo que requiere que la hora de inicio de la reunión sea menor. que o igual al día
y luego enumere cada día desde 1, y agregue la hora de finalización de todas las reuniones cuya hora de inicio sea menor o igual al día al montón, y luego extraiga la hora de finalización de todas las reuniones con una hora de finalización menor que día fuera del montón. El resto son todas las reuniones cuya hora de inicio sea menor o igual al día y hora de finalización sea mayor o igual al día, y luego seleccione la que tenga la hora de finalización más pequeña, es decir, el elemento superior en este momento, y asista la reunion correspondiente

public int maxEvents(int[][] events) {
    
    
    PriorityQueue<Integer> queue = new PriorityQueue<Integer>();
    //按照会议开始时间
    Arrays.sort(events, (a,b) ->  a[0] - b[0]);
    int i = 0, res = 0, n = events.length;
    int day = 1;                            //day表示每一天
    while(i < n || !queue.isEmpty()){
    
    
        //将每个当前时间有可能进行的会议的结束时间添加到优先队列中
        while (i < n && events[i][0] <= day){
    
    
            queue.offer(events[i++][1]);
        }
        //到这里,队列中放的都是开始时间小于等于当前时间点的会议的结束时间
        //所以,排除掉结束时间小于当前时间点的会议
        while (queue.size() > 0 && queue.peek() < day){
    
    
            queue.poll();
        }
        //排除后,堆顶元素就是开始时间小于等于当前时间点,结束时间大于等于当前时间点,且结束时间最小的一个
        //所以选择这个会议去参加,这一步即为整个算法的 贪心 所在
        if (queue.size() > 0) {
    
    
            queue.poll();
            res++;
        }
        day++;
    }
    return res;
}

300. Subsecuencia creciente más larga

Para una subsecuencia creciente, si el último elemento es más pequeño, entonces la posibilidad de encontrar más números mayores que el último elemento es mayor, lo que significa que la subsecuencia creciente más larga encontrada al final será más larga.

Entonces, para una serie de subsecuencias crecientes más largas de la misma longitud, elegimos con avidez la que tiene el último elemento más pequeño y usamos una matriz min para guardar el último elemento de la subsecuencia creciente más larga de cada longitud, es decir, min [i] significa que la longitud es El último elemento más pequeño de la subsecuencia creciente más larga de i, y usamos la variable maxLen para mantener la longitud de la subsecuencia más larga encontrada actualmente.

Luego iteramos a través de cada elemento de la matriz nums[i]. Si nums[i] es mayor que min[maxLen], es decir, mayor que el elemento final de la subsecuencia creciente más larga encontrada actualmente, entonces naturalmente podemos actualizar la longitud de la subsecuencia más larga a maxLen + 1 y actualice el último elemento correspondiente a esta longitud a nums[i]

Por el contrario, si nums[i] es menor o igual que min[maxLen], significa que no podemos actualizar la longitud de la subsecuencia creciente más larga. Pero en este momento podemos realizar nuestra operación codiciosa: dado que nums[i] es menor o igual que el elemento final de la subsecuencia creciente más larga actual, entonces podemos encontrar una longitud menor o igual que maxLen que satisfaga el extremo mínimo de la subsecuencia creciente de longitud j El elemento es mayor que nums[i] pero el elemento final mínimo de la subsecuencia creciente de longitud j - 1 es menor que nums[i], por lo que podemos actualizar el elemento final mínimo min[j] de la subsecuencia creciente de longitud j a nums[ i]

Después de atravesar la matriz, maxLen es la longitud de la subsecuencia creciente más larga.

public int lengthOfLIS(int[] nums) {
    
    
    if (nums.length == 0) {
    
    
        return 0;
    }
    int maxLen = 1,numsLen = nums.length;
    //最长递增子序列的长度可以是nums.length,所以数组大小要为nums.length+1
    int[] min = new int[numsLen + 1];
    //初始化长度为1的递增子序列的末尾元素为nums[0]
    min[maxLen] = nums[0];
    for (int i = 1; i < numsLen; ++i) {
    
    
        if (nums[i] > min[maxLen]) {
    
    
            min[++maxLen] = nums[i];
        }else{
    
    
            //可以发现min数组是具有递增性的,所以可以用二分查找
            //如果找不到说明所有的数都比 nums[i] 大,此时要更新 min[1],所以这里将 pos 设为 0
            int l = 1, r = maxLen, pos = 0;
            while (l <= r) {
    
    
                int mid = (l + r) >> 1;
                if (min[mid] < nums[i]) {
    
    
                    pos = mid;
                    l = mid + 1;
                } else {
    
    
                    r = mid - 1;
                }
            }
            //找到j(即pos+1)满足 min[j] > nums[i] && min[j - 1] < nums[i]
            min[pos + 1] = nums[i];
        }
    }
    return maxLen;
}

Codicioso + dos puntos

1011. Capacidad de entregar paquetes dentro de D días

Para el problema del valor óptimo, dé prioridad a si el enfoque de codicioso + dos puntos es factible (¿quién puede rechazar dos puntos?)
En esta pregunta, el peso del paquete que se entregará en un día se divide en dos partes . El límite de los dos puntos es el peso mínimo que se entregará en un día. Es la masa más grande entre todos los paquetes; de lo contrario, el paquete más grande nunca se entregará; el límite derecho de los dos puntos es que todos los paquetes se envían en un día. , es decir, el peso de todos los paquetes
se calcula cuando el número máximo de paquetes enviados en un día es medio. El número requerido de días día. Si el día es menor o igual a días, significa que el peso máximo del paquete enviado en un día puede reducirse. Actualización r = mid; si el día es mayor que días, significa que se debe aumentar el peso máximo del paquete enviado en un día. Actualización l = mid + 1, ¿por qué mid + 1 no es mid? Debido a que el medio en este momento ya no cumple con las condiciones, no es necesario considerarlo más adelante. Si día <= días, este medio puede ser la respuesta, por lo que r = medio en lugar de medio - 1

class Solution {
    
    
    public int shipWithinDays(int[] weights, int days) {
    
    
        int l = -1,r = 0,mid; //二分左边界是
        for(int i : weights){
    
    
            l = l > i ? l : i;
            r += i;
        }
        while(l < r){
    
    
            mid = (l + r) >> 1;
            int day = 1,oneDayWeights = 0;
            for(int i : weights){
    
    
                if(oneDayWeights + i > mid){
    
    
                    day++;
                    oneDayWeights = 0;
                }
                oneDayWeights += i;
            }
            if(day <= days){
    
    
                r = mid;
            }else{
    
    
                l = mid + 1;
            }
        }
        return l;
    }
}

410. Valor máximo de matriz dividida

Para este tipo de problema de encontrar el mejor valor dentro de k oportunidades, puede considerar si se puede completar mediante el método codicioso + bisección.

La suma de una única matriz dividida, el límite derecho es tratar toda la matriz como un conjunto dividido, luego el valor correspondiente es la suma de los elementos de la matriz; el límite izquierdo es tratar cada elemento de la matriz completa como un conjunto dividido, luego los conjuntos divididos correspondientes. El valor máximo de sus respectivas sumas es el valor del elemento más grande en la matriz.

Una vez que se determinan los límites izquierdo y derecho, se puede iniciar la bisección. Cada vez que un valor se divide como el valor máximo de las sumas respectivas de la matriz dividida, y luego la matriz original se divide de acuerdo con el valor máximo. El método específico de división es atravesar la matriz y agregar los elementos atravesados ​​al En el conjunto dividido actual, si la suma del conjunto dividido actual ha excedido el valor máximo, agregue uno al número de divisiones y luego use el elemento actual como nuevo siguiente conjunto dividido. Finalmente, juzgue la relación entre el número de divisiones y k. Si el número de divisiones es mayor que k, significa que la división basada en el valor máximo no cumple las condiciones y el valor máximo es demasiado pequeño, lo que provocará el número de divisiones es demasiado grande. El valor máximo aumenta, por lo que actualiza izquierda = mid + 1. Tenga en cuenta que no es mid, porque la solución correspondiente a mid no es elegible, por lo que no es necesario incluir mid; si Si el número de divisiones es menor o igual que k, podemos encontrar una solución factible, pero puede haber múltiples soluciones factibles, necesitamos encontrar el valor mínimo y máximo de la suma que satisfaga las condiciones, por lo que debemos continuar encuentre una dirección más pequeña, actualice a la derecha = mid, aquí debemos tomar mid, porque es posible que estemos en una situación más Si no se puede encontrar una solución factible más pequeña en un rango pequeño, entonces mid debe conservarse como una solución "garantizada".

public int splitArray(int[] nums, int k) {
    
    
    int left = 0,right = 0;
    for(int i : nums){
    
    
        if(left < i){
    
    
            left = i;
        }
        right += i;
    }
    int mid = 0,curCount = 1,sum = 0;
    while(left < right){
    
    
        mid = (left + right) >> 1;
        curCount = 1;
        sum = 0;
        for(int i : nums){
    
    
            if(sum + i > mid){
    
    
                sum = i;
                curCount++;
            }else{
    
    
                sum += i;
            }
        }
        if(curCount > k){
    
    
            left = mid + 1;
        }else{
    
    
            right = mid;
        }
    }
    return left;
}

Supongo que te gusta

Origin blog.csdn.net/Pacifica_/article/details/125259063
Recomendado
Clasificación