Combinaciones (combinaciones) de la revisión retrospectiva de Liuliu

"¡Continúe creando, acelere el crecimiento! Este es el quinto día de mi participación en el "Nuggets Daily New Plan · June Update Challenge", haga clic para ver los detalles del evento "

prefacio

Antes, Xiao Liuliu siempre sintió que su algoritmo era relativamente malo y que era una deficiencia. En el pasado, en realidad era una pesca de tres días, dos juegos de redes y unos días de cepillado, y luego se detuvo lentamente, así que este segundo, con la ayuda de las actividades de la plataforma, planeo comenzar a cepillarme lentamente, y también resumiré las preguntas de cepillado, hablaré sobre algunos de mis propios pensamientos y mis propias ideas, etc. Espero que pueda ser útil para mi amigos. También pueden aprovechar esta oportunidad para compensar sus deficiencias. Espero que puedan mantenerla.

tema

Dadas dos sumas enteras  n ,  kdevuelve  [1, n] todas las  k combinaciones posibles de números en el rango.

Puede   devolver las respuestas en cualquier orden .

输入: n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
复制代码

análisis de tema

De hecho, esta pregunta es un poco como un punto de conocimiento en nuestras matemáticas.La permutación y la combinación son similares. Pero aunque esta pregunta es exhaustiva, no puede resolverla con un bucle for. Debido a que el tamaño de n no está confirmado, si asume que n=4, k=2, en realidad puede hacer esto

int n = 4;
for (int i = 1; i <= n; i++) {
    for (int j = i + 1; j <= n; j++) {
        for (int u = j + 1; u <= n; n++) {
           for (int f = j + 1; f <= n; f++) {
            }
        }
    }
}
复制代码

Pero desafortunadamente, no podemos usarlo como una forma general de escribir, porque cuando n y k son muy grandes, probablemente quieras morir, así que usa el retroceso

¿Qué es un algoritmo de retroceso?

De hecho, la solución a este problema es retroceder, entonces, ¿qué es retroceder?

El método de retroceso (método de exploración y retroceso) es un tipo de método de búsqueda óptima, también conocido como método heurístico, que busca hacia adelante de acuerdo con las condiciones óptimas para lograr el objetivo. Sin embargo, cuando la exploración llega a cierto paso y se descubre que la elección original no es óptima o no logra el objetivo, dará un paso atrás y hará una nueva elección.

En el método de retroceso, cada vez que se expande la solución parcial actual, se enfrenta un conjunto de estados opcionales y se construye una nueva solución parcial seleccionando de este conjunto. La estructura de dicho conjunto de estados es un árbol de múltiples bifurcaciones, cada nodo del árbol representa una posible solución parcial y sus hijos son otras soluciones parciales generadas sobre su base. La raíz del árbol es el estado inicial, y dicho conjunto de estados se denomina árbol de espacio de estados.

回溯法对任一解的生成,一般都采用逐步扩大解的方式。每前进一步,都试图在当前部分解的基础上扩大该部分解。它在问题的状态空间树中,从开始结点(根结点)出发,以深度优先搜索整个状态空间。这个开始结点成为活结点,同时也成为当前的扩展结点。在当前扩展结点处,搜索向纵深方向移至一个新结点。这个新结点成为新的活结点,并成为当前扩展结点。如果在当前扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应往回移动(回溯)至最近的活结点处,并使这个活结点成为当前扩展结点。回溯法以这种工作方式递归地在状态空间中搜索,直到找到所要求的解或解空间中已无活结点时为止。

回溯法与穷举法有某些联系,它们都是基于试探的。穷举法要将一个解的各个部分全部生成后,才检查是否满足条件,若不满足,则直接放弃该完整解,然后再尝试另一个可能的完整解,它并没有沿着一个可能的完整解的各个部分逐步回退生成解的过程。而对于回溯法,一个解的各个部分是逐步生成的,当发现当前生成的某部分不满足约束条件时,就放弃该步所做的工作,退到上一步进行新的尝试,而不是放弃整个解重来。

回溯法解题的关键要素

确定了问题的解空间结构后,回溯法将从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。开始结点成为活结点,同时也成为扩展结点。在当前的扩展结点处,向纵深方向搜索并移至一个新结点,这个新结点就成为一个新的活结点,并成为当前的扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前的扩展结点就成为死结点。此时应往回移动(回溯)至最近的一个活结点处,并使其成为当前的扩展结点。回溯法以上述工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已无活结点时为止。

运用回溯法解题的关键要素有以下三点:

(1) 针对给定的问题,定义问题的解空间;

(2) 确定易于搜索的解空间结构;

(3) 以深度优先方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。

回溯解决的问题

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • Problema de corte: hay varias formas de cortar una cuerda de acuerdo con ciertas reglas
  • Problema de subconjuntos: ¿Cuántos subconjuntos elegibles hay en un conjunto de N números?
  • Problemas de tablero de ajedrez: N Queens, Resolver Sudoku y más

Por lo tanto, la combinación del problema anterior es para resolverlo retrospectivamente.

Echemos un vistazo, la primera versión de la solución del problema.

imagen.png

public class fourteen {
    public static void main(String[] args) {
        combine(4, 2);
    }


    public static List<List<Integer>> combine(int n, int k) {
        //这个是我用来放结果的
        List<List<Integer>> res = new ArrayList<>();
        //这个是用来方我们走过的路径的,这不好用List,可以用队列,因为我们要回退上一步
        LinkedList<Integer> temp = new LinkedList<>();
        backtracking(1, res, k, temp, n);
        return res;

    }

    private static void backtracking(int i, List<List<Integer>> res, int k, LinkedList<Integer> temp, int n) {
        //这是递归的结束条件
        if (temp.size() == k) {
            res.add(new ArrayList<>(temp));
            return;
        }
        //这里的for循环,表示我们的深入遍历,遍历到最后,然后再一步步回溯,就是把先把每个一个向下走的路走完,再走其他的分叉
        for (int j = i; j <= n; j++) {
            temp.add(j);
            back(j + 1, res, k, temp, n);
            temp.removeLast();
        }
    }
}
复制代码

De hecho, descubrimos que en realidad hay puntos de optimización, a los que llamamos poda.

Segunda edición, poda

public static List<List<Integer>> combine(int n, int k) {
    //这个是我用来放结果的
    List<List<Integer>> res = new ArrayList<>();
    //这个是用来方我们走过的路径的,这不好用List,可以用队列,因为我们要回退上一步
    LinkedList<Integer> temp = new LinkedList<>();
    backtracking(1, res, k, temp, n);
    return res;

}

private static void backtracking(int i, List<List<Integer>> res, int k, LinkedList<Integer> temp, int n) {
    //这是递归的结束条件
    if (temp.size() == k) {
        res.add(new ArrayList<>(temp));
        return;
    }
    //这里的for循环,表示我们的深入遍历,遍历到最后,然后再一步步回溯,就是把先把每个一个向下走的路走完,再走其他的分叉,减枝的地方就是在 我们遍历的条件,之前是j<=n,现在是j <= n-(k-temp.size())+1
    for (int j = i; j <= n-(k-temp.size())+1; j++) {
        temp.add(j);
        backtracking(j + 1, res, k, temp, n);
        temp.removeLast();
    }
}
复制代码

El lugar a podar es en la condición que atravesamos, antes era j <= n, ahora es j <= n-(k-temp.size())+1

Vamos a ver por qué podemos hacer esto, todos piensan, si queremos n=4, k=3, entonces de hecho, si recorremos desde 2 puntos, es posible, porque hay 3 y 4 en la parte de atrás, sí, pero si Mi índice inicial es 3. ¿Es cierto que no necesito atravesar en profundidad, porque pase lo que pase después, solo hay 4 detrás, como máximo 3, 4, y no puede llegar a k = 3? en absoluto, así que esto es podar

El número de elementos que han sido seleccionados: temp.size();

El número de elementos necesarios es: k - path.size();

En el conjunto n como máximo desde esta posición inicial: n - (k - path.size()) + 1, comienza a atravesar

Entonces, la condición de poda es j <= n-(k-temp.size())+1, puede sustituir n=4, k=3 y configurarlo

plantilla de retroceso

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

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

复制代码

Finalizar

Bien, eso es todo por hoy. En los próximos días, podemos continuar compartiendo las preguntas relacionadas con el retroceso y fortalecer la práctica.

Supongo que te gusta

Origin juejin.im/post/7103143480792186916
Recomendado
Clasificación