Une explication détaillée de l'algorithme de permutation et de combinaison le plus complet de l'histoire et un résumé des routines sont présentés dans un article

Adresse github du projet: bitcarmanlee easy-algorithm-interview-and-practice envoie
souvent des messages privés aux étudiants ou laissent des messages pour poser des questions connexes, numéro V bitcarmanlee. Les camarades de classe de star sur github, dans la limite de mes capacités et de mon temps, je ferai de mon mieux pour vous aider à répondre aux questions connexes et à progresser ensemble.

1. Problème de permutation et de combinaison

La permutation et la combinaison sont un problème d'algorithme classique, et le contenu associé a été appris au collège. Avant de parler de l'implémentation de l'algorithme, passons brièvement en revue les définitions associées de permutation et de combinaison.
Permutation, le nom anglais est Permutation, ou P pour faire court. Supposons qu'il y ait un tableau {1, 2, 3, 4, 5}, nous devons trier tous les éléments du tableau, puis à la première position, nous pouvons choisir l'un des cinq nombres, il y a 5 choix. En deuxième position, vous pouvez choisir l'un des quatre nombres restants. Il y a 4 choix au total. En troisième position, vous pouvez choisir l'un des trois nombres restants. Il y a 3 choix. En quatrième position, vous pouvez choisir l'un des deux nombres restants. Il y a 2 choix. La dernière position, car il ne reste qu'un seul numéro, il n'y a pas de choix, il n'y a qu'un seul choix. Alors le nombre total de permutations dans le tableau est 5 ∗ 4 ∗ 3 ∗ 2 ∗ 1 = 120 5 * 4 * 3 * 2 * 1 = 12054321=1 2 0 espèces.
Si les éléments du tableau ne sont pas répétés et que le nombre d'éléments est N, selon la dérivation ci-dessus, il est facile d'obtenir que le nombre de toutes les permutations du tableau estN! N!N !, C'est-à-direP (N) = N! P (N) = N!P ( N )=N !

Souvent, nous ne faisons pas toutes les permutations. Par exemple, s'il y a 5 éléments, il suffit d'en prendre 3 pour le tri. D'après l'analyse précédente, il est facile de savoir que le nombre de permutations est de 5 ∗ 4 ∗ 3 = 60 5 * 4 * 3 = 60543=.6 0 espèces, ces dernières2 1 2 * 1 *21 Les deux cas ont été rejetés. Par conséquent, en choisissant k éléments parmi N éléments pour la permutation, la formule est également facile à écrire:P (N, k) = N! (N - k)! P (N, k) = \ frac {N!} {(Nk )!}P ( N ,k )=( N - k ) !N !

Combination, le nom anglais est Combination, ou C pour faire court. En supposant le même tableau {1, 2, 3, 4, 5}, nous devons sélectionner 3 éléments du tableau, alors combien de façons existe-t-il?
D'après la dérivation précédente, on peut savoir que si 3 éléments sont sélectionnés parmi 5 éléments, l'agencement est P (5, 3) = 5! (5 - 3)! = 60 P (5, 3) = \ frac {5 !} {(5-3)!} = 60P ( 5 ,3 )=( 5 - 3 ) !5 !=6 0 espèces. Mais lors de la combinaison, il n'est pas sensible à l'ordre. Par exemple, on choisit 1, 2, 3 et 1, 3, 2. Bien qu'il y ait deux arrangements, c'est la même situation dans la combinaison. La disposition totale de 3 éléments totalise3! = 6 3! = 63 !=6 types, donc la formule combinée estC (N, K) = N! (N - k)! K! C (N, K) = \ frac {N!} {(Nk)! K!}C ( N ,K )=( N - k ) ! k !N !

Il y a aussi le théorème binomial:
C (n, 0) + C (n, 1) + C (n, 2) + ...... + C (n, n) = 2 n C (n, 0) + C (n, 1) + C (n, 2) + \ cdots + C (n, n) = 2 ^ nC ( n ,0 )+C ( n ,1 )+C ( n ,2 )++C ( n ,n )=2n

2. Tous les sous-ensembles

Tout d'abord, regardons la situation de trouver tous les sous-ensembles: en supposant que le tableau a maintenant trois éléments distincts {1, 2, 3}, trouvez tous les sous-ensembles du tableau.
Selon le théorème binomial, il n'est pas difficile de trouver que le nombre de tous les sous-ensembles du tableau est C (3, 0) + C (3, 1) + C (3, 2) + C (3, 3) = 2 3 = 8 C (3, 0) + C (3, 1) + C (3, 2) + C (3, 3) = 2 ^ 3 = 8C ( 3 ,0 )+C ( 3 ,1 )+C ( 3 ,2 )+C ( 3 ,3 )=23=8

Mis à part toute autre chose, accédez d'abord au code, puis analysez les idées spécifiques plus tard.

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;

public class SubSet {

    public static int[] nums = {1, 2, 3};
    public static ArrayList<ArrayList<Integer>> result = new ArrayList<>();

    public static void subset(ArrayList<Integer> inner, int start) {
        for(int i=start; i<nums.length; i++) {
            inner.add(nums[i]);
            result.add(new ArrayList<>(inner));
            subset(inner, i+1);
            inner.remove(inner.size()-1);
        }
    }

    public static void main(String[] args) {
        ArrayList<Integer> inner = new ArrayList<>();
        result.add(inner);
        subset(inner, 0);
        for(ArrayList<Integer> each: result) {
            System.out.println(StringUtils.join(each, ","));
        }
    }
}

La sortie du code ci-dessus est


1
1,2
1,2,3
1,3
2
2,3
3

Il y a exactement 8 cas, et en regardant les résultats de sortie, ils sont en ligne avec nos attentes.

La solution ci-dessus est une solution classique de retour en arrière. Analysez les idées spécifiques: tout d'
abord, réfléchissez à la manière dont nous pouvons constituer les trois sous-ensembles {1}, {1,2}, {1,2,3}? Le parcours commence à partir de l'index = 0. À ce stade, l'élément 1 est ajouté à l'intérieur et l'intérieur est ajouté au résultat, de sorte que le sous-ensemble {1} est ajouté au résultat. Ensuite, appelez la méthode du sous-ensemble de manière récursive, changez simplement l'index à 0 + 1 = 1. À ce stade, l'intérieur ajoute 2 à {1,2}, et l'intérieur est ajouté au résultat en même temps, de sorte que { 1,2} correspond aux résultats de la jointure du sous-ensemble. Par analogie, le prochain appel récursif ajoutera {1,2,3} au résultat.

Principalement pour analyser comment obtenir {1,3} à
partir du sous-ensemble de {1,2,3} : Après avoir obtenu le sous-ensemble de {1,2,3}, à ce moment, nous appelons récursivement subset (inner, 3), qui n'est pas satisfaite La condition de i <nums.length dans la boucle for, l'appel se termine. À ce stade, revenez à la scène d'empilement lorsque start = 2, exécutez d'abord inner.remove (inner.size () - 1); cette phrase supprimera le dernier élément 3 de inner à ce moment, et inner est {1, 2 }. Ensuite, il retournera à la scène d'empilement lorsque start = 1. A ce moment, le dernier élément 2 de l'intérieur sera supprimé, et à ce moment, seul le dernier élément 1 de l'intérieur sera laissé. Lorsque le début initial = 0, l'appel au sous-ensemble (1) dans la boucle for est terminé et l'exécution du sous-ensemble (2) dans la boucle for est lancée, l'élément 3 sera ajouté et le paramètre interne devient {1,3 }. Par analogie, tous les sous-ensembles seront éventuellement obtenus.

Le processus d'analyse ci-dessus est en fait le processus dans lequel les fonctions du code continuent à pousser la pile, puis à revenir en arrière sur l'appel. Il est recommandé aux étudiants de déboguer et d'examiner le processus d'exécution du code, et ils auront une meilleure compréhension.

3. Sélectionnez k combinaisons parmi n éléments

La deuxième partie consiste à trouver tous les sous-ensembles. Si nous limitons le nombre d'éléments du sous-ensemble, c'est-à-dire que nous sélectionnons k combinaisons d'éléments parmi n éléments, ce qui est le commun C (n, k) C (n, k)C ( n ,k ) Question.

L'idée de solution est fondamentalement la même que ci-dessus, regardez d'abord le code.

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;

public class SelectK {

    public static int[] nums = {1, 2, 3};
    public static ArrayList<ArrayList<Integer>> result = new ArrayList<>();

    public static void select(ArrayList<Integer> inner, int start, int k) {
        for(int i=start; i<nums.length; i++) {
            inner.add(nums[i]);
            if (inner.size() == k) {
                result.add(new ArrayList<>(inner));
                inner.remove(inner.size()-1);
                continue;
            }
            select(inner, i+1, k);
            inner.remove(inner.size()-1);
        }
    }

    public static void main(String[] args) {
        ArrayList<Integer> inner = new ArrayList<>();
        int k = 2;
        select(inner, 0, k);
        for(ArrayList<Integer> each: result) {
            System.out.println(StringUtils.join(each, ","));
        }
    }
}

Le résultat est:

1,2
1,3
2,3

La différence par rapport à la recherche de tous les sous-ensembles est que ce n'est que lorsque le nombre d'éléments dans inner est k, inner est ajouté au résultat. En même temps, après l'ajout, supprimez d'abord le dernier élément, puis vous pouvez directement continuer à terminer ce cycle.

4. Disposition complète de n éléments

D'après notre analyse précédente, il y a n éléments non répétés, et la permutation totale est n! N!n ! Gentil. En supposant le tableau {1, 2, 3}, il y a 6 cas en permutation totale.

Toujours coder en premier

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;

public class PermutationN {

    public static int[] nums = {1, 2, 3};
    public static ArrayList<ArrayList<Integer>> result = new ArrayList<>();

    public static void permuation(ArrayList<Integer> inner) {
        if (inner.size() == nums.length) {
            result.add(new ArrayList<>(inner));
            return;
        }

        for(int i=0; i<nums.length; i++) {
            if (inner.contains(nums[i])) {
                continue;
            }
            inner.add(nums[i]);
            permuation(inner);
            inner.remove(inner.size()-1);
        }
    }

    public static void main(String[] args) {
        ArrayList<Integer> inner = new ArrayList<>();
        permuation(inner);
        for(ArrayList<Integer> each: result) {
            System.out.println(StringUtils.join(each, ","));
        }
    }
}

Analysons également l'idée du code:
1. Si la taille de l'intérieur répond à la condition, ajoutez-la au résultat et retournez.
2. Démarrez le cycle à partir du premier élément:
2.1 Si l'élément est à l'intérieur, cela signifie que l'élément a été visité, et ce cycle continue.
2.2 Si l'élément n'est pas en interne, ajoutez interne.
2.3 Appelez la méthode de permutation de manière récursive.
2.4 Après l'appel de cette méthode de permutation, supprimez le dernier élément de inner.

La réflexion est-elle assez claire? De même, si cela semble un peu étourdi, je vous suggère d'aller au débogage dans l'EDI et d'observer l'ensemble du processus d'appel récursif de la fonction.

La fonction de inner.contains (nums [i]) est de déterminer si l'élément a été visité. En pratique, une autre façon d'écrire plus courante consiste à utiliser un tableau de visite pour enregistrer la visite de l'élément. Ci-dessous, nous utilisons visit Let's montre comment écrire un tableau.

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;

public class PermutationN {

    public static int[] nums = {1, 2, 3};
    public static ArrayList<ArrayList<Integer>> result = new ArrayList<>();
    public static boolean[] visit = new boolean[nums.length];

    public static void permuation(ArrayList<Integer> inner, boolean[] visit) {
        if (inner.size() == nums.length) {
            result.add(new ArrayList<>(inner));
            return;
        }
        for(int i=0; i<nums.length; i++) {
            if (visit[i]) {
                continue;
            }
            visit[i] = true;
            inner.add(nums[i]);
            permuation(inner, visit);
            inner.remove(inner.size()-1);
            visit[i] = false;
        }
    }


    public static void main(String[] args) {
        ArrayList<Integer> inner = new ArrayList<>();
        permuation(inner, visit);
        for(ArrayList<Integer> each: result) {
            System.out.println(StringUtils.join(each, ","));
        }
    }
}

Utilisez le tableau de visite pour indiquer si l'élément a été visité. Par rapport à la version précédente, il y a deux étapes supplémentaires:
1. L'élément est visité et la position dans le tableau de visite est définie sur true;
2. Lors d'un retour en arrière récursif, la position dans le tableau de visite est définie sur true. Elle est définie sur false.

5.n permutations totales avec éléments répétés

Dans l'exemple de permutation complet ci-dessus, il n'y a pas d'éléments en double dans le tableau. Que dois-je faire s'il y a des éléments en double dans un tableau, tel que le tableau, {1, 1, 2}, qui nécessite la disposition complète du tableau?
Pas grand chose à dire, commençons par le code.

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;

public class PermutationDuplicate {

    public static int[] nums = {1, 2, 1};
    public static ArrayList<ArrayList<Integer>> result = new ArrayList<>();
    public static boolean[] visit = new boolean[nums.length];

    public static void permuation(ArrayList<Integer> inner, boolean[] visit) {
        if (inner.size() == nums.length) {
            result.add(new ArrayList<>(inner));
            return;
        }

        for(int i=0; i<nums.length; i++) {
            if (visit[i]) {
                continue;
            }
            if (i > 0 && nums[i] == nums[i-1] && !visit[i-1]) {
                continue;
            }

            inner.add(nums[i]);
            visit[i] = true;
            permuation(inner, visit);
            inner.remove(inner.size()-1);
            visit[i] = false;
        }
    }

    public static void main(String[] args) {
        Arrays.sort(nums);
        ArrayList<Integer> inner = new ArrayList<>();
        permuation(inner, visit);
        for(ArrayList<Integer> each: result) {
            System.out.println(StringUtils.join(each, ","));
        }
    }
}

Concentrez-vous sur les différences par rapport à ce qui précède:
1. Triez d'abord le tableau pour garantir l'ordre.
2.

            if (i > 0 && nums[i] == nums[i-1] && !visit[i-1]) {
                continue;
            }

Cette condition peut être comprise comme suit:
après que le premier agencement 1,1,2 est enregistré, un agencement ultérieur de 1,1,2 sera généré. Le deuxième agencement de 1, 1, 2 est que le deuxième 1 est accédé en premier, et le premier 1 est à nouveau accédé. A ce moment, le drapeau de visite du premier 1 est faux, donc dans ce cas, ce cycle peut aussi continuer directement sans ajouter au résultat!

6. Résumé de routine

Après avoir résolu les cas ci-dessus un par un, résumons les routines des problèmes de permutation et de combinaison.

Examinez d'abord le problème de la disposition:

result = []
def permutation(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    for 选择 in 选择列表:
        做选择
        permutation(路径, 选择列表)
        撤销选择

Pour différentes situations, il n'y a que deux points à confirmer: les conditions de fin et comment choisir.
Le processus ci-dessus est essentiellement une méthode de retour arrière standard.

Regardez à nouveau le problème de combinaison

result = []
def permutation(路径, 选择列表):
    for 选择 in 选择列表:
        做选择
        permutation(路径, 选择列表)
        撤销选择

Les routines combinées consistent essentiellement à utiliser des méthodes rétrospectives. La différence avec l'agencement est que la condition de fin du problème combiné n'a pas besoin d'être écrite, attendez simplement le résultat de la boucle.

Je suppose que tu aimes

Origine blog.csdn.net/bitcarmanlee/article/details/114500993
conseillé
Classement