Notas para el cepillado de preguntas con Likou - retroceso

Este artículo son las notas de estudio registradas por el código al azar , puedes buscar en la cuenta oficial para aprender por ti mismo

Tabla de contenido

problema de combinacion

Número de teléfono Alfabeto

 cadena dividida

131. Partición de cadenas de palíndromos: LeetCode https://leetcode.cn/problems/palindrome-partitioning/

Segmentación de direcciones IP

Problema de subconjunto

Subconjunto

78. Subconjuntos - LeetCode https://leetcode.cn/problems/subsets/

 subsecuencia creciente

reprogramar

n problema de la reina


Backtracking es un algoritmo de búsqueda de fuerza bruta que resuelve los siguientes problemas:

Por qué es una búsqueda violenta, porque la idea de este método es enumerar todos los resultados posibles y extraer los resultados que cumplan con los requisitos del tema.

Por lo tanto, retroceder no es eficiente, y no es eficiente, pero no significa que sea inútil, solo podemos usar el retroceso para resolver algunos problemas.

Las siguientes preguntas son ejemplos:

  1. Problema de combinación: encontrar un conjunto de k números en N números de acuerdo con ciertas reglas
  2. Pregunta de segmentación: ¿De cuántas maneras se puede cortar una cuerda según ciertas reglas?
  3. Subconjunto: ¿Cuántos subconjuntos calificados hay en un conjunto de N números?
  4. Arreglo: los números N se ordenan de acuerdo con ciertas reglas, hay varios métodos de arreglo
  5. Problema de tablero de ajedrez: N Queens, Resolviendo Sudoku
  6. otro

El retroceso es un subproducto de la recursión. Mientras haya recursión, habrá retroceso. La esencia del retroceso es exhaustiva.

El tamaño de la colección determina el ancho del árbol de retroceso y la profundidad de la recursividad determina la profundidad del árbol de retroceso.

La formula de la pregunta es la siguiente

void backtracking(参数)
{
    if(终止条件){
        存放结果;
        return;
    }

    for(选择:本层集合中的元素){
        处理节点;        
        backtracking(参数)
        回溯撤销结果
    }
}

problema de combinacion

77. Combinaciones - LeetCode https://leetcode.cn/problems/combinations/

class Solution {
public:
    vector<vector<int>> res;//用于存放结果
    vector<int> path;//用来存放符合条件结果
    vector<vector<int>> combine(int n, int k) {
          
        backtracking(n,k,1);
        return res;
    }

    void backtracking(int n,int k,int index){
        if(path.size()==k){
             res.push_back(path);
             return;
         }
        for(int i=index;i<=n;i++){
            path.push_back(i);//处理节点
            backtracking(n,k,i+1);//递归
            path.pop_back();//回溯
        }
    }
    
};

Optimización de poda:

path.size() son los datos seleccionados

k-path.size() son los datos seleccionados que aún se necesitan

Luego, el índice comienza como máximo n-(k-path.size())+1

+1 para el cierre izquierdo ya que nuestra indexación comienza en uno

Modificar el resultado:

for(int i=index;i<n-(k-path.size())+1;i++){

}

Árbol recursivo:

 El camino recorrido menos la posición roja

216. Combinación Suma III - LeetCode https://leetcode.cn/problems/combination-sum-iii/

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> combinationSum3(int k, int n) {
        backtracking(k,n,1);
        return res;
    }


    void backtracking(int k,int sum,int index){
        if(sum==0&&path.size()==k){
            res.push_back(path);
            return;
        } 
        if(sum<0||path.size()>k){
            return;
        }

        for(int i=index;i<=9;i++){
            //对于节点的处理
            path.push_back(i);
            sum-=i;

            backtracking(k,sum,i+1);

            sum+=i;
            path.pop_back();
        }
    }
};

39. Suma de combinación - LeetCode (LeetCode) https://leetcode.cn/problems/combination-sum/ La principal diferencia entre este problema y el problema anterior es que los elementos se pueden seleccionar repetidamente y no hay límite para el número.

Considere los parámetros de la función de retroceso:

  • formación
  • valor objetivo
  • índice de inicio índice de inicio
  • La suma de los números en la ruta (se puede obtener directamente a través del objetivo)

Condición de terminación:

if(sum==target){ res.push_back(path); return;}
if(sum>target) return;

 código de ac:

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates,target,0,0);
        return res;
    }


    void backtracking(vector<int>& candidates,int target,int sum,int startindex){
        if(sum==target){ res.push_back(path); return;}
        if(sum>target) return;


        for(int i=startindex;i<candidates.size();i++){
            //对于本节点的处理
            sum+=candidates[i];
            path.push_back(candidates[i]);
            //递归
            backtracking(candidates,target,sum,i);

            //回溯
            sum-=candidates[i];
            path.pop_back();
        }
    }
};

Optimización de poda:

Si el valor de suma + candidatos[i] significa que la suma de la siguiente ronda es mayor que el objetivo, no es necesario ingresar la siguiente ronda de recursividad y agregar condiciones en el bucle for
suma + candidatos[i] <= objetivo

40. Combination Sum II - LeetCode https://leetcode.cn/problems/combination-sum-ii/ La pregunta 40 es similar a la pregunta anterior, pero hay diferencias sutiles

  • Los elementos de una colección dada se pueden repetir
  • Las combinaciones no se pueden repetir en el resultado final.

Para estos dos problemas, necesitamos deduplicar los resultados, que son los mismos elementos que se han utilizado en esta capa de recursividad.

 Luego usamos una matriz para identificar si los elementos de la matriz se han usado , usamos una matriz de identificación del mismo tamaño que la matriz dada y creamos una matriz usada para indicar si se ha accedido a un elemento.

Pensamiento de parámetros:

  • Los parámetros dados por la función de título.
  • sum representa la suma de los elementos actuales
  • startindex representa la posición actual donde la recursividad debe comenzar
  • used es una matriz que identifica si el elemento ha sido utilizado

La condición final es la misma que la anterior.

El código de ac es el siguiente

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size());
        sort(candidates.begin(), candidates.end());
        backtracking(candidates,target,0,0,used);
        
        return res;
    }
    

    void backtracking(vector<int>& candidates,int target,int sum,int startindex,    vector<bool>& used){
        if(sum==target){ res.push_back(path); return;}
        if(sum>target) return;

        
        for(int i=startindex;i<candidates.size() && sum+candidates[i]<=target;i++){
            //相对于同层之前已经使用过的不能再次使用
           
            //对于本节点的处理
            sum+=candidates[i];
            path.push_back(candidates[i]);
            used[i]=true;
            //递归
            backtracking(candidates,target,sum,i+1,used);

            //回溯
            used[i]=false;
            sum-=candidates[i];
            path.pop_back();
        }
    }
};

  if(i>0 && candidatos[i]==candidatos[i-1] && usados[i-1]==falso) continuar;

Significa que el elemento en la posición i es el mismo que el elemento en la posición i-1, pero el elemento en la posición i (falso) ya se usó, si existe ese camino, lo podaremos para evitar que el resultado sea repetido.

used[i - 1] == true , lo que indica que se han utilizado los mismos candidatos de rama de árbol [i - 1]
used[i - 1] == false , lo que indica que se han utilizado candidatos [i - 1] en el mismo nivel de árbol

Número de teléfono Alfabeto

17. Combinaciones de letras de números de teléfono: LeetCode https://leetcode.cn/problems/letter-combinations-of-a-phone-number/ Piense en qué parámetros deben transmitirse:

  • path.size() determina el número de claves que representan letras
  • índice de posición del botón
  • El número total de llaves n

 Para referencia:

Se encuentra que el parámetro n no se puede pasar como parámetro, pero no tiene efecto.

class Solution {
public:
    vector<string> res;
    vector<char> path;
    vector<string> letterCombinations(string digits) {
        if(digits.size()==0) return res;
        backtracking(digits.size(),0,digits);
        return res;
    }


    void backtracking(int n,int index,string digits){
        //终止条件
        if(n==path.size()){
            string s="";
            for(auto i:path){
                s+=i;
            }
            res.push_back(s);
            return ;
        }

        int sum=0;
        int flag=digits[index]-'0';
        if(flag==9 || flag==7) sum=4;
        else sum=3;

        for(int i=0;i<sum;i++){
            //特殊处理
            int offset=flag>7?1:0;
            char c=(flag-2)*3+offset+i+'a';

            path.push_back(c);

            backtracking(n,index+1,digits);

            path.pop_back();
        }

    }
};

 cadena dividida

131. Particionamiento de cadenas Palindrome - LeetCode https://leetcode.cn/problems/palindrome-partitioning/

 Esta pregunta necesita agregar varias formas de dividir en cadenas de palíndromo a la matriz de resultados final, y tomar la posición dividida como índice es similar al problema de combinación.

Piensa en pasar parámetros:

  1. cuerda s
  2. índice de corte índice

Piense en la condición de terminación:
si la posición de corte alcanza la última posición de la cadena, salga del ciclo y agregue el número en la ruta a la matriz de resultados.

 El código ac es el siguiente:

class Solution {
public:
    vector<vector<string>> result;
    vector<string> path;
    vector<vector<string>> partition(string s) {
        backtracking(s,0);
        return result;
    }
    bool ishuiwen(const string& s,int start,int end){
        for(int i=start,j=end;i<j;i++,j--){
            if(s[i]==s[j]){
                continue;
            }else{
                return false;
            }
        }
        return true;
    }
    void backtracking(const string& s,int index){
        if(index>=s.length()){
            result.push_back(path);
            return;
        }

        for(int i=index;i<s.length();i++){
            if(ishuiwen(s,index,i)){
                string str=s.substr(index,i-index+1);
                path.push_back(str);
            }
            else{
                continue;
            }

            backtracking(s,i+1);

            path.pop_back();
        }



    }
};

Segmentación de direcciones IP

93. Restauración de direcciones IP: LeetCode https://leetcode.cn/problems/restore-ip-addresses/ Similar al problema anterior de dividir cadenas, este problema también puede usar el retroceso, usando la dirección dividida como índice, agregando puntos No ., eliminado al retroceder.

Parámetros de consideración

void backtracking(string& s,int pointNum,int startindex);

s: cadena

pointNum: la cantidad de puntos que se han agregado, cuando el cuerpo es 3, cumple las condiciones de devolución

startindex: el índice para comenzar a seleccionar en la siguiente posición dividida

terminar la condición de retorno

  1. añadió tres puntos
  2. El cuarto párrafo de datos cumple con los requisitos 

Agregue la función isvalid para juzgar la cadena cortada:

Si la cadena entre el inicio y el final cumple con los requisitos 

class Solution {
public:
    vector<string> res; 
    vector<string> restoreIpAddresses(string s) {
        res.clear();
        if(s.size()<4 || s.size()>12) return res;
        backtracking(s,0,0);
        return res;
    }

    bool isvalid(const string& s,int start,int end){

        if (start > end) {
            return false;
            }
      
        if (s[start] == '0' && start != end) { // 0开头的数字不合法
                        return false;
        }

        int num = 0;
        for (int i = start; i <= end; i++) {
                if (s[i] > '9' || s[i] < '0') { // 遇到⾮数字字符不合法
                return false;
            }
            num = num * 10 + (s[i] - '0');
            if (num > 255) { // 如果⼤于255了不合法
            return false;
            }
        }
        return true;
    }
    void backtracking(string& s,int sum,int startindex){
        if(sum==3){
            if(isvalid(s,startindex,s.size()-1)){
                res.push_back(s);
            }
            return;
        }

        for(int i=startindex;i<s.size();i++){

            if(isvalid(s,startindex,i)){
                s.insert(s.begin()+i+1,'.');
                backtracking(s,sum+1,i+2);
                s.erase(s.begin() + i + 1);
            }else break;

        }   
    }
};

Problema de subconjunto

Subconjunto

78. Subconjuntos - LeetCode https://leetcode.cn/problems/subsets/

 Dividir y combinar es encontrar el valor de los nodos hoja del árbol de retroceso, mientras que el problema del subconjunto es recopilar todos los nodos en el árbol.

El recorrido de for comienza desde startindex hasta el final de la matriz

class Solution {
public:
    vector<vector<int>> res;
    vector<int> vec;
    vector<vector<int>> subsets(vector<int>& nums) {

        backtracking(nums,0);
        return res;
    }

    void backtracking(vector<int>& nums,int index){
      
        res.push_back(vec);
        

        for(int i=index;i<nums.size();i++){
            vec.push_back(nums[i]);
            backtracking(nums,i+1);
            vec.pop_back();
        }
    }
};

 subsecuencia creciente

491. Subsecuencias crecientes - LeetCode https://leetcode.cn/problems/non-decreasing-subsequences/Esta pregunta también es para encontrar subsecuencias, pero lo que se necesita es aumentar las subsecuencias, por lo que debe juzgarse y al mismo tiempo No se permite la duplicación, por lo que se deduplica el resultado y se agrega un conjunto para deduplicar los elementos de cada capa.

Parámetros de consideración

void backtracking(vector<int>& nums,int index);

índice es la posición del índice atravesada

Condición de terminación

El título requiere dos elementos o más, y los dos elementos repetidos se usan como una secuencia especial 

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        res.clear();
        path.clear();
        backtracking(nums, 0);
        return res;
    }


    void backtracking(vector<int>& nums,int index){
        if(path.size()>1){
            res.push_back(path);
        }
        //对每层的结果进行去重
        unordered_set<int> uset;
        for(int i=index;i<nums.size();i++){

            if ((!path.empty() && nums[i] < path.back())
                || uset.find(nums[i]) != uset.end()) {
                continue;
                }

            uset.insert(nums[i]);
            path.push_back(nums[i]);

            backtracking(nums,i+1);

            path.pop_back();
        }
    }
};

reprogramar

332. Reconstruir itinerario - LeetCode https://leetcode.cn/problems/reconstruct-itinerary/

La etiqueta de esta pregunta es búsqueda profunda, pero la búsqueda profunda contiene la idea de retroceder.

Piensa en esta pregunta:

        Primero necesitamos mapear la relación correspondiente dada en una forma similar a la lista de adyacencia, y almacenar la relación alcanzable entre ellos.

        Necesita generar resultados ordenados alfabéticamente primero

        Al retroceder, se establece la condición de retorno final (condición de terminación)

El primer problema es mapear la relación alcanzable. Un aeropuerto puede llegar a múltiples aeropuertos, por lo que puede ser de uno a uno o de uno a muchos, así que use unordered_map<string,multiset<string>> para almacenar información relacionada. también es posible usar map, map mantiene el orden de almacenamiento de datos, y también podemos usar map<string,unordered_map<string,int>> map para almacenar datos

Debido a que el mapa ordenará los aeropuertos a los que se puede llegar, se puede garantizar que los aeropuertos ordenados primero serán atravesados ​​cada vez que se atraviese.

Considere la condición del final, un aeropuerto, puede ser un lugar de partida así como un lugar de llegada

Entonces el número de aeropuertos requeridos es el número de boletos más uno

if(boletonum+1=resultado.tamaño()); 

class Solution {
public:
    vector<string> result;
    unordered_map<string,map<string,int>> targets;
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        targets.clear();
        for(const vector<string>& vec:tickets){
            targets[vec[0]][vec[1]]++;
        }
        result.push_back("JFK");
        backtracking(tickets.size());
        return result;
    }


    bool backtracking(int ticketNum){
        if(ticketNum+1==result.size()){
            return true;
        }

        //对现在到达的机场可到达的机场进行遍历
        for(pair<const string,int>& target:targets[result[result.size()-1]]){
            if(target.second>0){
                result.push_back(target.first);
                target.second--;
                if(backtracking(ticketNum)) return true;//如果找到了一条路线,就是答案,立即返回
                target.second++;
                result.pop_back();
            }
        }

        return false;
    }
};

n problema de la reina

51. N Queens - LeetCode https://leetcode.cn/problems/n-queens/

El problema de las n reinas requiere que proporcionemos todas las permutaciones posibles y retrocedamos en una matriz bidimensional, como el árbol de retroceso que se muestra a continuación.

El ancho del rastreo es la longitud de cada columna, y la profundidad es el número de filas,

La lógica para construir backtrace:
for(int col=0;col<n;col++){

        nodo de procesamiento

        atrás(); recursividad

        Retroceder, deshacer resultados de procesamiento

Piense en la condición de terminación.Si atraviesa el nodo hoja, que es la última línea, agregue el resultado y devuelva

si (fila == n) {

        result.push_back(tablero de ajedrez);

        devolver;

Cuando pensamos en cuándo, una posición puede insertar piezas de ajedrez,

Piezas en la fila anterior, no en la misma columna donde se insertarán, no en la barra inclinada superior izquierda y en la barra inclinada superior derecha 

bool isvalid(int fila,int col,vector<cadena>& tablero de ajedrez,int n){         for(int i=0;i<fila;i++){             if(tablero de ajedrez[i][col]=='Q') return FALSO;         }



        for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--){ if(tablero de ajedrez[             i][j]=='Q') return false;         }

        for(int i=row-1,j=col+1;i>=0&&j<n;i--,j++){             if(tablero de ajedrez[i][j]=='Q') return false;         }

        devolver verdadero;
    }

class Solution {
public:

    vector<vector<string>> result;
    void backtracking(int n,int row,vector<string>& chessboard){

        if(row==n){
            result.push_back(chessboard);
            return;
        }

        for(int i=0;i<n;i++){
            if(isvalid(row,i,chessboard,n)){
                chessboard[row][i] = 'Q';
                
                backtracking(n,row+1,chessboard);

                 chessboard[row][i] = '.';  
            }

        }
    }

    bool isvalid(int row,int col,vector<string>& chessboard,int n){
        for(int i=0;i<row;i++){
            if(chessboard[i][col]=='Q') return false;
        }


        for(int i=row-1,j=col-1;i>=0&&j>=0;i--,j--){
            if(chessboard[i][j]=='Q') return false;
        }

        for(int i=row-1,j=col+1;i>=0&&j<n;i--,j++){
            if(chessboard[i][j]=='Q') return false;
        }

        return true;
    }
    vector<vector<string>> solveNQueens(int n) {

        vector<string> vec(n,string(n, '.'));
        backtracking(n,0,vec);
        return result;
    }
};

Supongo que te gusta

Origin blog.csdn.net/qq_53633989/article/details/130450890
Recomendado
Clasificación