Algoritmo - Puntero doble

contenido

I. Introducción

2. La idea central del algoritmo

3. Modelo de algoritmo

1. Puntero de colisión

2. Puntero rápido y lento

3. Ventana corredera

4. Clasificación por fusión

4. Resumen


I. Introducción

El doble puntero es un algoritmo básico y ampliamente utilizado.Estrictamente hablando, el doble puntero no es un algoritmo sino más bien una idea. El "puntero" en el puntero doble no es solo el conocido puntero de dirección en C/C++, sino también un índice y un cursor. Este artículo presentará brevemente punteros duales y cuatro modelos comunes de punteros duales para el aprendizaje de referencia de punteros duales.

2. La idea central del algoritmo

Puntero doble se refiere al uso de dos o más punteros para el recorrido y las operaciones correspondientes al atravesar un objeto. Se utiliza principalmente para operaciones con arreglos, que aprovechan la naturaleza secuencial de los arreglos. Los punteros dobles se utilizan a menudo para reducir la complejidad temporal de los algoritmos, ya que el uso de dos punteros puede evitar múltiples bucles.

Tres puntos clave de doble puntero

  • Selección de la posición inicial del puntero
  • dirección del movimiento del puntero
  • velocidad de movimiento del puntero

Estos tres puntos clave son el núcleo y la dificultad del algoritmo de doble puntero.

3. Modelo de algoritmo

1. Puntero de colisión

Punteros en colisión: los punteros izquierdo y derecho se acercan al centro.

1.

Leetcode https://leetcode-cn.com/problems/binary-search/

704. búsqueda binaria

Dada una matriz de enteros ordenada (ascendente) de n elementos nums y un valor objetivo target , escriba una función para buscar el objetivo en nums , devolviendo el subíndice si el valor objetivo existe, y -1 de lo contrario.

Ejemplo :

Entrada:  Salida: 4
 Explicación: 9 aparece en números y tiene subíndice 4nums= [-1,0,3,5,9,12], target= 9

Partiendo de la premisa de que la matriz está ordenada, utilice punteros dobles para la búsqueda binaria. La función de los punteros dobles es "binario" . Primero, los punteros izquierdo y derecho lr apuntan al primer elemento y al elemento final de la matriz respectivamente , y determinan la relación de tamaño entre el valor de la matriz correspondiente al subíndice de la matriz en el medio entre los punteros izquierdo y derecho y el valor objetivo. tres casos de la siguiente manera:

  1. nums[mid] == objetivo   encuentra el valor objetivo, registra el índice de la matriz, finaliza
  2. El valor en medio de nums[mid] > target  es mayor que el valor objetivo y debe continuar buscando en el intervalo  [ l, mid-1 ] 
  3. nums[mid] < target El valor de la mediana es menor que el valor objetivo y debe  continuar buscando en el intervalo [ mid+1 , r ]

3 < 9 l = medio + 1

 encontrar el valor objetivo

el código se muestra a continuación:

class Solution {
    public int search(int[] nums, int target) {
        //数组长度
    int len = nums.length;
    int l = 0 ,r = len - 1;
    //记录目标值下标
    int index = -1;

    while(l <= r){
        //求出中间下标 (数据类型)
        int mid = (r-l) / 2 + l;
        //大于目标值 移动右指针
        if(nums[mid] > target){
            r = mid - 1;
        }
        else if(nums[mid] < target){
            //小于目标值 移动左指针
            l = mid + 1;
        }else{
            //等于目标值 记录下标值 结束循环
            index = mid;
            break;
        }
    }
    return index;
    }
}

2.

Leetcode https://leetcode-cn.com/problems/3sum/

15. La suma de tres números

Dificultad moderada 3979

Dada una  n matriz de enteros  nums, determine  nums si hay tres elementos  a, b, c tales que  a + b + c =  0. Encuentra todos los  0 triples que suman y no se repiten.

Nota: Las respuestas no pueden contener triples duplicados.
 

Ejemplo:

Entrada: números = [-1,0,1,2,-1,-4]
 Salida: [[-1,-1,2],[-1,0,1]]

Primero, ordena la matriz nums El problema requiere encontrar tres grupos de elementos cuya suma sea 0 y no se repita. Primero podemos seleccionar un elemento a, y luego usar punteros dobles para encontrar los elementos elegibles b y c, (preste atención a la deduplicación) Suponiendo que sum = a+b+c, existen los siguientes tres casos:

  1. suma == 0 y la condición, suma estos tres números al conjunto
  2. sum > 0    mueve el puntero derecho hacia la izquierda para hacer que el elemento c sea más pequeño
  3. sum < 0   , muévase hacia la derecha como un puntero para hacer que el elemento b sea más grande

el código se muestra a continuación:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
      List<List<Integer>> res = new ArrayList<List<Integer>>();
      int n = nums.length;
      //对数组进行排序
      Arrays.sort(nums);
      //记录上一个元素,用于去重 
      int index = -1;

         for(int i = 0; i < n;i++ ) {
             //去重
             if(index != -1 && nums[index] == nums[i])
             continue;

             //左右指针
        	 int l = i + 1, r = n - 1;
        	 while(l < r) {
                 // 三个数的和
        		 int num = nums[i] + nums[l]+nums[r];
                 //和为0 符合题意 记录在集合中
        		 if(num == 0) {
        			 List<Integer> list = new ArrayList<Integer>();
        			  list.add(nums[i]);
        			  list.add(nums[l]);
        			  list.add(nums[r]);
        			 res.add(list);
                     //二次去重
        			 int temp = nums[l];
        			 while(l < r && temp == nums[l]) l++;
        		 }else if(num < 0) {
                     //和小于0 右移左指针
        			 l++;
        		 }else {
                     //和大于0 左移右指针
        			 r--;
        		 }	 
        	 } 
             //记录上一次第一个元素的下标
             index = i;  	 
         }
      
      return res;
    }
}

Argumento de corrección de puntero doble: primero, la matriz puede usar punteros dobles. Después de ordenar, primero seleccione un elemento a y use i para señalar este elemento, luego use el puntero l para señalar el elemento b, y el puntero r para señalar el elemento c. sum es la suma de tres elementos, sum tiene solo los tres casos anteriores,

Si el primer caso se cumple inmediatamente suma = 0;

Cuando sum > 0 , mueva el puntero derecho r a la izquierda, en lugar de mover r a la izquierda y l  a la derecha , la razón es que en este momento sum > 0 , en cualquier caso, la suma del puntero l al right será mayor que 0, y el elemento señalado por r es inútil. El elemento señalado por l puede ser elegible para un elemento en el [l,r-1] . Si lr se mueve, se perderán algunas situaciones posibles . Es aún más imposible volver atrás porque se está repitiendo la operación anterior.

Cuando sum < 0 , mueva el puntero izquierdo l hacia la derecha en lugar de retroceder el puntero derecho r . La razón es que el primer movimiento del puntero a este paso indica que los elementos recorridos antes no son elegibles, y el paso anterior puede ser dos posibles sum > 0 sum < 0 , y la posición donde el puntero derecho r se mueve hacia la izquierda en este paso debe ser sum > 0 , por lo que solo hay una posibilidad es sum > 0 , si es sum > 0 , entonces es el paso obtenido moviendo el puntero derecho r hacia la izquierda, y luego hacia atrás Solo se está repitiendo.


2. Puntero rápido y lento

Punteros rápidos y lentos: dos punteros izquierdo y derecho, un bloque y otro lento

1.

876. Nodos intermedios de listas enlazadas

Dada una  head lista no vacía con enlaces simples cuyo nodo principal es , devuelve el nodo medio de la lista enlazada.

Si hay dos nodos intermedios, devuelve el segundo nodo intermedio.

Ejemplo:

Entrada: [1,2,3,4,5]

Salida: Nodo 3 en esta lista (forma serializada: [3,4,5]) Devuelve un nodo con valor 3. (La representación serializada del sistema de evaluación de este nodo es [3,4,5]). Tenga en cuenta que devolvemos un objeto ans de tipo ListNode tal que: ans.val = 3, ans.next.val = 4, ans.next.next.val = 5 y ans.next.next.next = NULL.

Para encontrar el nodo medio de la lista de enlaces simples, simplemente podemos recorrerlo para encontrar el número de nodos en la lista de enlaces simples, y luego recorrerlo para encontrar el nodo medio. Pero, ¿y si se utiliza la idea de un doble puntero?

Recuerde los tres puntos clave del puntero doble, la selección de la posición inicial del puntero doble, porque no conocemos la cola de la lista unida, por lo que el puntero doble al principio y al final obviamente no es factible. Cuál es el valor de los punteros?

En este momento, tenemos que fijarnos en el tercer punto clave, la velocidad del movimiento del doble puntero. Imaginemos que si dos personas parten en línea recta desde la misma posición, y una persona es el doble de rápida que la otra, entonces la distancia de esta persona es el doble que la de la otra en el mismo tiempo. Colocamos dos punteros, uno se mueve el doble de rápido que el otro, por lo que el primero llega al punto final y el segundo solo llega a la posición media, ¿verdad?

La estructura se define como:

 public class ListNode {
     int val;
      ListNode next;
      ListNode() {}
      ListNode(int val) { this.val = val; }
      ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  }

el código se muestra a continuación:

class Solution {
    public ListNode middleNode(ListNode head) {
         ListNode p = head, q = head;
     while(q != null && q.next != null){
        q = q.next.next;
         p = p.next;
     }
     return p;
    }
}

2.

392. Evaluación de la subsecuencia https://leetcode-cn.com/problems/is-subsequence/

Dificultad Fácil 545

Dadas las cadenas  s  y  t  , determine si  s  es   una subsecuencia de t .

Una subsecuencia de una cadena es una nueva cadena formada al eliminar algunos (o no) caracteres de la cadena original sin cambiar las posiciones relativas de los caracteres restantes. (por ejemplo, una subsecuencia de "ace"sí , no)"abcde""aec"

Ejemplo:

Entrada: s = "abc", t = "ahbgdc"
 Salida: verdadero

Para determinar si una cadena s es una subsecuencia de una cadena t, en primer lugar, es necesario aclarar la definición de una subsecuencia, es decir, los caracteres de la cadena t pueden sacar una cadena que es igual a s sin cambiando la posición relativa , entonces s es t subsecuencia. Recorremos t para ver si existe el primer carácter de s. Si lo hay, continuamos recorriendo para encontrar el segundo carácter de s basado en esta posición, y procedemos a su vez. Si se encuentran todos los s, significa que s es el secuencia de palabras de t.

Establecemos dos punteros i, j, i comienza desde el primer carácter de s, j comienza desde el primer carácter de t, cuando son iguales, tanto i como j se desplazan a la derecha , si no son iguales, solo j es desplazado a la derecha , y finalmente si i es igual a la longitud de s , entonces la coincidencia completa es exitosa, y s es una subsecuencia de t.

el código se muestra a continuación:

class Solution {
    public boolean isSubsequence(String s, String t) {
   int n = s.length(), m =t.length();
         if (n > m ) {
			return false;
		}
            int i = 0 ,j = 0;
            while(i< n && j<m ) {
            	if(s.charAt(i) == t.charAt(j))  
            		i++;         	
            		j++;
            	
            }
         return i == n;
    }
}

3. Ventana corredera

Ventana deslizante: los punteros izquierdo y derecho forman una "ventana", el puntero derecho se expande continuamente y el puntero izquierdo se contrae según las condiciones.

1004. Consecutivos máximos III https://leetcode-cn.com/problems/max-consecutive-ones-iii/

Dada una   matriz de sumas  0 ,  podemos cambiar  como máximo las  sumas de 0 a 1.1AK

Devuelve la longitud del subarreglo más largo (contiguo) que contiene solo 1.

Ejemplo:

Entrada: A = [1,1,1,0,0,0,1,1,1,1,0], K = 2
Salida: 6
Explicación: 
[1,1,1,0,0, 1 ,1 ,1,1,1,1 ] Los números en negrita cambian de 0 a 1, la longitud más larga del subarreglo es 6 .

Esta pregunta encuentra el número máximo de 1s consecutivos, es decir, sobre la base de k 0s menor o igual, para encontrar la longitud del intervalo elegible más largo. Usamos el puntero doble lr para representar ambos lados de la ventana y recorremos la matriz desde el principio.Si el valor es 1 o el número de 0 en el intervalo  [ l, r ]  <= k, movemos el puntero r a a la derecha para expandir la ventana y Actualizar el número de 0. Cuando el número de 0 es mayor que k, necesitamos actualizar la longitud máxima del intervalo elegible. Luego mueva l a la derecha para reducir la ventana, actualice el número de 0 y continúe repitiendo hasta que el puntero derecho exceda el límite del subíndice de la matriz y finalice el recorrido. En este momento, el valor de la longitud máxima del intervalo es grabado.

 

 encoge la ventana

 

 

el código se muestra a continuación:

class Solution {
    public int longestOnes(int[] nums, int k) {
     int n = nums.length;
     int l = 0, r = 0;
     int count = 0;  //记录0的个数
     int res = 0;  //记录结果

     while(r < n){
         //当nums[r]为1 或者 子区间零的个数少于k
         if(nums[r] == 1 || count < k){
             //是否为0,若为0则count++
             count += nums[r] == 0 ? 1 : 0;
             r++;
              continue;   
         }

         res = Math.max(res,r-l);

         //nums[r] 为 0
         count++;
         r++;

         //如果子区间0的个数 大于 k ,缩小窗口 l < n防止数组下标越界
          while(count > k ){
             count -= nums[l] == 0 ? 1 : 0;
           l++;
          }
     }
      res = Math.max(res,r-l);
return res;

    }
}

4. Clasificación por fusión

88. Combinar dos matrices ordenadas https://leetcode-cn.com/problems/merge-sorted-array/

Se le dan dos matrices de enteros nums1 y nums2 en orden no decreciente, y dos enteros m y n más, que representan el número de elementos en nums1 y nums2, respectivamente.

Combine nums2 en nums1 para que las matrices combinadas también se organicen en orden no decreciente.

Nota: En última instancia, la matriz fusionada no debe ser devuelta por la función, sino almacenada en la matriz nums1. Para hacer frente a esta situación, la longitud inicial de nums1 es m + n, donde los primeros m elementos representan los elementos que deben fusionarse y los últimos n elementos son 0 y deben ignorarse. La longitud de nums2 es n.

Ejemplo:

Entrada: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
Salida: [1,2,2,3,5,6]
Explicación: es necesario combinar [1,2,3] y [2,5,6].
El resultado combinado es [1,2,2,3,5,6].

Para fusionar dos matrices ordenadas, no se puede usar ningún espacio de matriz adicional y la matriz fusionada es nums1. Similar a la subsecuencia de la cadena anterior, la diferencia es que no podemos atravesar y comparar los dos elementos de la matriz desde el principio, porque la comparación se almacena en nums1 en este momento, por lo que es posible que sea necesario desplazar el elemento original de nums1 hacia atrás uno bit, similar a La complejidad de tiempo de la operación de suma de la matriz es O (N).Si el elemento más pequeño de nums2 es más grande que el elemento más grande de nums1, entonces el elemento de nums1 se ha movido hacia atrás y el número de elementos de nums2 se realiza para operaciones O(N), que serán O(N^2), ¿cómo optimizar?

La razón por la que nos encontramos con el problema anterior es que comenzamos desde el final donde se han almacenado datos válidos. En este momento, el movimiento afectará definitivamente a los elementos izquierdo y derecho. Entonces cambiamos nuestra forma de pensar y comenzamos la comparación desde la cola, que es decir, se puede comparar el número más grande. , almacenado al final de nums1.

el código se muestra a continuación:

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
//l指向nums1尾部元素,r指向nums2尾部元素 ,index指向nums1要存储比较后数据的位置
     int l = m  - 1, r = n -1 , index = m + n - 1;
                          
             while(l >= 0 && r >= 0){
           //如果l指向的元素值大 左移l
                  if(nums1[l] > nums2[r]){

                      nums1[index] = nums1[l];
                      --l;     
                  }else{
                  //如果r指向的元素值大 左移r
                      nums1[index] = nums2[r];
                      --r; 
                  }
            //存储一个元素,左移index指向下一个待存储元素的位置
                   --index;
             }
              // 如果此时 nums2还有元素没有比较,就全部依次存储在nums1中
              while(r >= 0) nums1[index--] = nums2[r--];
    }
}

4. Resumen

El puntero doble es una de las bases comunes para resolver problemas aritméticos complejos, y es ampliamente utilizado.Es relativamente simple examinar problemas de puntero doble solo. Como base importante del algoritmo, el doble puntero no solo es un método para reducir la complejidad temporal del algoritmo, sino que también puede proporcionar una nueva idea para resolver el problema. Para dominar los punteros dobles de manera competente, no solo es necesario comprender los modelos comunes de punteros dobles, sino también practicar problemas de algoritmos relacionados y aplicar el conocimiento aprendido en la práctica.

Supongo que te gusta

Origin blog.csdn.net/qq_52595134/article/details/121385996
Recomendado
Clasificación