python - leetcode - 78. Sous-ensemble et 90. Sous-ensemble II [Solution au problème classique - Algorithme de retour en arrière]

1. Sujet : Sous-ensemble

78.
Description du sous-ensemble :
Vous recevez un tableau de nombres entiers. Les éléments du tableau sont différents les uns des autres. Renvoie tous les sous-ensembles possibles (ensembles de puissances) de ce tableau.

L'ensemble de solutions ne peut pas contenir de sous-ensembles en double. Vous pouvez renvoyer les ensembles de solutions dans n’importe quel ordre.
Exemple 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

Exemple 2 :

输入:nums = [0]
输出:[[],[0]]

indice:

  • 1 <= nombres.longueur <= 10
  • -10 <= nums[i] <= 10 tous les éléments en nums sont distincts les uns des autres

2. Idées de résolution de problèmes

Le backtracking est une idée algorithmique, tandis que la récursion est une méthode de programmation. Le backtracking peut être implémenté à l'aide de la récursivité.

L'idée générale de la méthode de backtracking est la suivante : recherchez chaque chemin, et chaque retour en arrière concerne un chemin spécifique. Lors de la recherche de zones inexplorées sous le chemin de recherche actuel, deux situations peuvent se produire :

  • Si la zone actuellement non recherchée répond à la condition de fin, enregistrez le chemin actuel et quittez la recherche en cours ;
  • Si la zone actuellement non explorée doit continuer la recherche, tous les choix actuellement possibles seront parcourus : Si le choix répond aux exigences, le choix actuel sera ajouté au chemin de recherche actuel et la recherche de nouvelles zones inexplorées se poursuivra.

La zone non recherchée mentionnée ci-dessus fait référence à la zone non recherchée lors de la recherche d'un certain chemin, et non à la zone globale non recherchée.

Le modèle de recherche de toutes les solutions réalisables à l'aide de la méthode de backtracking ressemble généralement à ceci :

res = []
path = []

def backtrack(未探索区域, res, path):
    if path 满足条件:
        res.add(path) # 深度拷贝
        # return  # 如果不用继续搜索需要 return
    for 选择 in 未探索区域当前可能的选择:
        if 当前选择符合要求:
            path.add(当前选择)
            backtrack(新的未探索区域, res, path)
            path.pop()

La signification de backtrack est : tous les chemins possibles vers la condition finale dans la zone inexplorée. La variable path stocke un chemin et la variable res stocke tous les chemins recherchés. Ainsi, lorsque « la zone inexplorée répond à la condition finale », le chemin doit être mis dans la résolution résultante.
Que signifie path.pop() ?
C'est une exigence dans l'implémentation de la programmation, c'est-à-dire que nous n'utilisons qu'un seul chemin variable du début à la fin, donc lors de l'ajout d'une sélection au chemin et du retour en arrière, la sélection actuelle doit être effacée pour éviter d'affecter la recherche d'autres chemins.

Écriture formelle

Pour le sous-ensemble 78., recherchez tous les sous-ensembles du tableau sans nombres répétés. Selon le modèle, notre idée devrait être la suivante :

  • Zone inexplorée : numéros de tableau restants non recherchés [index : N - 1] ;
  • Si chaque chemin répond aux conditions de la question : tout chemin est un sous-ensemble, tous les chemins remplissent les conditions et ils doivent être placés dans res ;
  • Lorsque le chemin actuel remplit les conditions, s'il faut continuer la recherche : Oui, après avoir trouvé le sous-ensemble dans nums[0:index-1], l'ajout de nums[index] à l'ancien chemin
    formera un nouveau sous-ensemble.
  • Les choix actuellement possibles dans la zone inexplorée : chaque sélection peut sélectionner 1 caractère de s, c'est-à-dire nums[index] ;
  • La sélection actuelle répond aux exigences : tout nums[index] est qualifié et placé directement dans le chemin ;
  • Nouvelle zone inexplorée : nums La chaîne restante après index, nums[index + 1 : N - 1] .
class Solution(object):
    def subsets(self, nums):
        res, path = [], []
        self.dfs(nums, 0, res, path)
        return res
    
    def dfs(self, nums, index, res, path):
        res.append(copy.deepcopy(path))
        for i in range(index, len(nums)):
            path.append(nums[i])
            self.dfs(nums, i + 1, res, path)
            path.pop()

Écriture simplifiée

Ce qui précède est la méthode formelle de retour en arrière. Si vous voulez être paresseux, vous pouvez créer une nouvelle variable de chemin à chaque recherche au lieu de réutiliser le chemin global. Le code peut alors être plus rationalisé.

Comme écrit ci-dessous, chaque fois que vous recherchez un nouveau sous-ensemble, un nouveau chemin est créé, car path + [nums[i]] renvoie une nouvelle liste, qui est placée dans les paramètres de la fonction. tout nouveau, donc aucune copie complète n'est nécessaire lorsque res.append(path).

class Solution(object):
    def subsets(self, nums):
        res = []
        self.dfs(nums, 0, res, [])
        return res
    
    def dfs(self, nums, index, res, path):
        res.append(path)
        for i in xrange(index, len(nums)):
            self.dfs(nums, i + 1, res, path + [nums[i]])

Moins de méthode d'écriture de paramètres

class Solution(object):
    def subsets(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        # 写法一
        res = []
        n = len(nums)
        def helper(i, tmp):
            res.append(tmp)
            for j in range(i, n):
                helper(j + 1,tmp + [nums[j]] )
        helper(0, [])
        return res 

		# 写法二
        res, path = [], []
        def dfs(index, res, path):
            res.append(copy.deepcopy(path))
            for i in range(index, len(nums)):
                path.append(nums[i])
                dfs(i + 1, res, path)
                path.pop()
        dfs(0, res, path)
        return res

3. Autres solutions

Idée 1. Fonctions de la bibliothèque

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []
        for i in range(len(nums)+1):
            for tmp in itertools.combinations(nums, i):
                res.append(tmp)
        return res

Idée 2 : Itération, un parcours (simulation), pas besoin de retour en arrière

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = [[]]
        for i in range(len(nums)):
            new_subsets = [subset + [nums[i]] for subset in res]
            res = new_subsets + res
        return res

Écriture simplifiée

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = [[]]
        for num in nums:
            res = res + [[num] + i for i in res]
        return res

Idée 3 : Récursivité (algorithme de retour en arrière)

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []
        n = len(nums)
       
        def helper(i, tmp):
            res.append(tmp)
            for j in range(i, n):
                helper(j + 1,tmp + [nums[j]] )
        helper(0, [])
        return res 

4. Transformation des questions : sous-ensemble II

90. Sous-ensemble II Recherche la valeur d'un tableau contenant des éléments répétés.
Vous recevez un tableau numérique entier, qui peut contenir des éléments répétés. Veuillez renvoyer tous les sous-ensembles possibles (ensembles de puissance) du tableau.
L'ensemble de solutions ne peut pas contenir de sous-ensembles en double. Dans l’ensemble de solutions renvoyé, les sous-ensembles peuvent être disposés dans n’importe quel ordre.

Exemple 1:

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

Exemple 2 :

输入:nums = [0]
输出:[[],[0]]

indice:

  • 1 <= nombres.longueur <= 10
  • -10 <= nombres[i] <= 10

Écriture formelle

Si vous comprenez le retour en arrière ci-dessus, alors le sous-ensemble d'un tableau contenant des éléments en double n'est qu'une petite amélioration.

Par exemple, si vous souhaitez trouver le sous-ensemble de nums = [1,2,2], alors vous avez sélectionné les 2 premiers pour le sous-ensemble [1,2], alors vous ne pouvez pas sélectionner le second 2 pour former [1,2 ]. . Par conséquent, le changement à ce stade est de trier en premier. Avant d'ajouter chaque élément nums[i] au chemin, déterminez si nums[i] est égal à nums[i - 1]. S'il est égal, il ne sera pas ajouté au chemin. chemin.

class Solution(object):
    def subsetsWithDup(self, nums):
        res, path = [], []
        nums.sort()
        self.dfs(nums, 0, res, path)
        return res
        
    def dfs(self, nums, index, res, path):
        res.append(copy.deepcopy(path))
        for i in range(index, len(nums)):
            if i > index and nums[i] == nums[i - 1]:
                continue
            path.append(nums[i])
            self.dfs(nums, i + 1, res, path)
            path.pop()

Moins de méthode d'écriture de paramètres

class Solution(object):
    def subsetsWithDup(self, nums):
        nums.sort()
        res = []
        def back_tracking(start, temp):
            res.append(temp[:])
            for i in range(start, len(nums)):
                if i > start and nums[i] == nums[i-1]:
                    continue
                temp.append(nums[i])
                back_tracking(i+1, temp)
                temp.pop()
        back_tracking(0, [])
        return res

Écriture simplifiée

class Solution(object):
    def subsetsWithDup(self, nums):
        res = []
        nums.sort()
        self.dfs(nums, 0, res, [])
        return res
        
    def dfs(self, nums, index, res, path):
        if path not in res:
            res.append(path)
        for i in range(index, len(nums)):
            if i > index and nums[i] == nums[i - 1]:
                continue
            self.dfs(nums, i + 1, res, path + [nums[i]])

Moins de méthode d'écriture de paramètres

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []
        nums.sort()
        n = len(nums)
        def helper(i, tmp):
            if tmp not in res:
                res.append(tmp)
            for j in range(i, n):
                if i > j and nums[i] == nums[i - 1]:
                    continue
                helper(j + 1, tmp + [nums[j]])
        helper(0, [])
        return res

Autres solutions

Idée 1 : un parcours (simulation), pas besoin de retour en arrière

class Solution(object):
    def subsetsWithDup(self, nums):
        res = [[]]
        nums.sort()
        for i in range(len(nums)):
            if i >= 1 and nums[i] == nums[i-1]:
                new_subsets = [subset + [nums[i]] for subset in new_subsets]
            else:
                new_subsets = [subset + [nums[i]] for subset in res]
            res = new_subsets + res
        return res

Écriture simplifiée

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        res=[[]]
        for n in nums:
            res+=[i+[n] for i in res if i+[n] not in res]
        return res

Idée 2 : Comptez la fréquence de chaque nombre

Pas besoin de supprimer les doublons ou de trier

# 刚开始我们只有空集一个答案,循环所有可能的数字,
# 每次循环我们对当前答案的每一种情况考虑加入从1到上限次该数字并更新答案即可
class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        dic = {
    
    }
        for i in nums:
            dic[i] = dic.get(i, 0) + 1
        res = [[]]
        for i, v in dic.items():
            temp = res.copy()
            for j in res:
                temp.extend(j+[i]*(k+1) for k in range(v))
            res = temp
        return res

Je suppose que tu aimes

Origine blog.csdn.net/qq_43030934/article/details/131642480
conseillé
Classement