[Estructura de datos y algoritmo] Hay K combinaciones posibles de N números

Dados dos números enteros n y k, devuelve todas las combinaciones posibles de k números en 1 ... n.

1. Requisitos de la asignatura
	输入: n = 4, k = 2
	输出:
	[
	  [2,4],
	  [3,4],
	  [2,3],
	  [1,2],
	  [1,3],
	  [1,4],
	]
Dos, algoritmo de ejemplo
Rápido
  • Debido a que el conjunto de resultados se ordena de pequeño a pequeño, no es necesario considerar la repetición, por lo que el proceso se vuelve mucho más simple;
  • Utilice una matriz t para registrar los datos a los que se accede actualmente. Si el número de elementos de la matriz es igual a k, significa que se ha encontrado y almacenado un conjunto de respuestas en res;
  • Cuando se registran datos en cada paso, el elemento inicial es el último +1 en la matriz t, y se juzga si los datos de los datos restantes no recorridos son suficientes;
  • Si no es suficiente, no es necesario seguir atravesando y regresar directamente.
	var res = [[Int]]()
    func bt(_ n:Int, _ count:Int, _ t:[Int]){
    
    
        let tLen = t.count
        if tLen == count {
    
    
            res.append(t);
            return;
        }
        
        let minT = t.count > 0 ? t.last!+1 : 1
        if minT > n || n-minT+1 < count-tLen {
    
    
        	return;
        }
        var tmp = t
        
        for i in minT ... n {
    
    
            tmp.append(i)
            bt(n,count,tmp)
            tmp.removeLast()
        }
    }
    
    func combine(_ n: Int, _ k: Int) -> [[Int]] {
    
    
        let tmp = [Int]()
        bt(n, k, tmp)
        return res
    }
Java
  • Implementación de Java: dado que es un cruce de profundidad en el problema del árbol, primero dibuje la estructura del árbol. Por ejemplo, ingrese: n = 4, k = 2, podemos encontrar la siguiente estructura recursiva:
    • Si hay 1 en la combinación, entonces necesita encontrar otro número en [2, 3, 4];
    • Si hay 2 en la combinación, entonces necesita encontrar otro número en [3, 4].
    • Nota: 1 no se puede considerar aquí, porque la combinación que contiene 1 ya está incluida en el primer caso.
    • Por analogía (se omite la última parte), la estructura recursiva incorporada en la descripción anterior es: en la matriz candidata que termina en n, seleccione varios elementos. Dibuja la estructura recursiva como se muestra a continuación:

Inserte la descripción de la imagen aquí

  • Descripción:
    • La información del nodo hoja está incorporada en la ruta desde el nodo raíz al nodo hoja, por lo que se requiere una ruta variable que represente la ruta, que es una lista, en particular, la ruta es una pila;
    • Cada nodo está haciendo lo mismo de forma recursiva, la diferencia está en el punto de inicio de la búsqueda, por lo que se necesita un inicio variable para representar una combinación de varios números seleccionados en el intervalo [inicio, n].
  • Algoritmo uno de Java:
	public List<List<Integer>> combine(int n, int k) {
    
    
        List<List<Integer>> res = new ArrayList<>();
        if (k <= 0 || n < k) {
    
    
            return res;
        }
        // 从 1 开始是题目的设定
        Deque<Integer> path = new ArrayDeque<>();
        dfs(n, k, 1, path, res);
        return res;
    }

    private void dfs(int n, int k, int begin, Deque<Integer> path, List<List<Integer>> res) {
    
    
        // 递归终止条件是:path 的长度等于 k
        if (path.size() == k) {
    
    
            res.add(new ArrayList<>(path));
            return;
        }

        // 遍历可能的搜索起点
        for (int i = begin; i <= n; i++) {
    
    
            // 向路径变量里添加一个数
            path.addLast(i);
            // 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
            dfs(n, k, i + 1, path, res);
            // 重点理解这里:深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
            path.removeLast();
        }
    }
  • Algoritmo dos de Java:
	public List<List<Integer>> combine(int n, int k) {
    
    
        List<List<Integer>> res = new ArrayList<>();
        if (k <= 0 || n < k) {
    
    
            return res;
        }
        Deque<Integer> path = new ArrayDeque<>();
        dfs(n, k, 1, path, res);
        return res;
    }

    private void dfs(int n, int k, int begin, Deque<Integer> path, List<List<Integer>> res) {
    
    
        if (path.size() == k) {
    
    
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = begin; i <= n; i++) {
    
    
            path.addLast(i);
            System.out.println("递归之前 => " + path);
            dfs(n, k, i + 1, path, res);
            path.removeLast();
            System.out.println("递归之后 => " + path);
        }
    }

    public static void main(String[] args) {
    
    
        Solution solution = new Solution();
        int n = 5;
        int k = 3;
        List<List<Integer>> res = solution.combine(n, k);
        System.out.println(res);
    }
  • Optimización: analice el límite superior del punto de inicio de búsqueda para la poda
  • En el código anterior, el punto de inicio de la búsqueda atraviesa an, es decir: el siguiente fragmento de código en la función recursiva:
	// 从当前搜索起点 begin 遍历到 n
	for (int i = begin; i <= n; i++) {
    
    
	    path.addLast(i);
	    dfs(n, k, i + 1, path, res);
	    path.removeLast();
	}
  • De hecho, si n = 7 y k = 4, no tiene sentido buscar desde 5. Esto se debe a que incluso si se selecciona 5, los siguientes números son solo 6 y 7, y hay 3 candidatos en total, que no se pueden inventar. Una combinación de 4 números. Por lo tanto, el punto de inicio de la búsqueda tiene un límite superior. ¿Qué es este límite superior? Puede dar algunos ejemplos para el análisis.
  • Analizar el límite superior del punto de inicio de búsqueda es en realidad poda durante el proceso de recorrido de profundidad primero. La poda puede evitar recorridos innecesarios. Podar y podar bien puede ahorrar mucho tiempo de ejecución del algoritmo.
  • La parte verde de la imagen de abajo son las ramas y hojas cortadas. Cuando n es muy grande, muchos nodos se pueden atravesar menos, lo que ahorra tiempo.

Inserte la descripción de la imagen aquí

  • Es fácil de saber: el punto de partida de la búsqueda está relacionado con el número de números que deben seleccionarse actualmente, y el número de números que deben seleccionarse actualmente está relacionado con el número de números que se han seleccionado, es decir, relacionado con la longitud del camino. Dé algunos ejemplos para analizar:

  • Por ejemplo: n = 6 y k = 4:

    • Cuando path.size () == 1, seleccione 3 números a continuación, el punto de inicio de búsqueda máximo es 4 y la última combinación seleccionada es [4, 5, 6];
    • Cuando path.size () == 2, seleccione 2 números a continuación. El punto de inicio de búsqueda máximo es 5, y la última combinación seleccionada es [5, 6];
    • Cuando path.size () == 3, seleccione 1 número a continuación, el punto de inicio de búsqueda máximo es 6 y la última combinación seleccionada es [6];
  • Otro ejemplo: n = 15, k = 4:

    • Cuando path.size () == 1, seleccione 3 números a continuación. El punto de inicio de búsqueda máximo es 13, y el último seleccionado es [13, 14, 15];
    • Cuando path.size () == 2, seleccione 2 números a continuación. El punto de inicio de búsqueda máximo es 14, y el último seleccionado es [14, 15];
    • Cuando path.size () == 3, seleccione 1 número a continuación, el punto de inicio de búsqueda máximo es 15 y el último seleccionado es [15];
  • Conclusión: El límite superior del punto de inicio de la búsqueda + el número de elementos a seleccionar siguiente-1 = n.

  • El número de elementos que se seleccionarán a continuación = k-path.size (), ordenados:

	搜索起点的上界 = n - (k - path.size()) + 1
  • Por lo tanto, nuestro proceso de poda es: cambiar i <= n a i <= n- (k-path.size ()) + 1:
	public List<List<Integer>> combine(int n, int k) {
    
    
        List<List<Integer>> res = new ArrayList<>();
        if (k <= 0 || n < k) {
    
    
            return res;
        }
        Deque<Integer> path = new ArrayDeque<>();
        dfs(n, k, 1, path, res);
        return res;
    }

    private void dfs(int n, int k, int index, Deque<Integer> path, List<List<Integer>> res) {
    
    
        if (path.size() == k) {
    
    
            res.add(new ArrayList<>(path));
            return;
        }

        // 只有这里 i <= n - (k - path.size()) + 1 与参考代码 1 不同
        for (int i = index; i <= n - (k - path.size()) + 1; i++) {
    
    
            path.addLast(i);
            dfs(n, k, i + 1, path, res);
            path.removeLast();
        }
    }

Supongo que te gusta

Origin blog.csdn.net/Forever_wj/article/details/108633586
Recomendado
Clasificación