python – leetcode – 78. Teilmenge & 90. Teilmenge II [Klassische Problemlösung – Backtracking-Algorithmus]

1. Thema: Teilmengen

78. Subset-
Beschreibung:
Sie erhalten ein ganzzahliges Array mit Zahlen. Die Elemente im Array unterscheiden sich voneinander. Gibt alle möglichen Teilmengen (Potenzmengen) dieses Arrays zurück.

Die Lösungsmenge darf keine doppelten Teilmengen enthalten. Sie können die Lösungssätze in beliebiger Reihenfolge zurückgeben.
Beispiel 1:

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

Beispiel 2:

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

Hinweis:

  • 1 <= Anzahl.Länge <= 10
  • -10 <= nums[i] <= 10 alle Elemente in nums sind voneinander verschieden

2. Ideen zur Problemlösung

Backtracking ist eine algorithmische Idee, während Rekursion eine Programmiermethode ist. Backtracking kann durch Rekursion implementiert werden.

Die Gesamtidee der Backtracking-Methode besteht darin , jeden Pfad zu durchsuchen, und jeder Backtracking bezieht sich auf einen bestimmten Pfad. Bei der Suche nach unerforschten Gebieten im aktuellen Suchpfad können zwei Situationen auftreten:

  • Wenn der derzeit nicht durchsuchte Bereich die Endbedingung erfüllt, speichern Sie den aktuellen Pfad und beenden Sie die aktuelle Suche.
  • Wenn das derzeit nicht durchsuchte Gebiet weiter durchsucht werden muss, werden alle derzeit möglichen Auswahlmöglichkeiten durchlaufen: Wenn die Auswahl die Anforderungen erfüllt, wird die aktuelle Auswahl zum aktuellen Suchpfad hinzugefügt und die Suche nach neuen unerforschten Gebieten wird fortgesetzt.

Der oben erwähnte nicht durchsuchte Bereich bezieht sich auf den nicht durchsuchten Bereich bei der Suche nach einem bestimmten Pfad, nicht auf den globalen nicht durchsuchten Bereich.

Die Vorlage zur Suche aller möglichen Lösungen mithilfe der Backtracking-Methode sieht im Allgemeinen wie folgt aus:

res = []
path = []

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

Die Bedeutung von Backtrack ist: alle möglichen Pfade bis zum Endzustand im unerforschten Bereich. Die Pfadvariable speichert einen Pfad und die Res-Variable speichert alle gesuchten Pfade. Wenn also „das unerforschte Gebiet die Endbedingung erfüllt“, muss der Pfad in die Ergebnisres eingefügt werden.
Was bedeutet path.pop()?
Dies ist eine Anforderung bei der Programmierimplementierung, das heißt, wir verwenden nur einen variablen Pfad vom Anfang bis zum Ende. Wenn Sie also eine Auswahl zum Pfad hinzufügen und zurückverfolgen, muss die aktuelle Auswahl gelöscht werden, um zu verhindern, dass die Suche anderer Pfade beeinträchtigt wird.

Formelles Schreiben

Finden Sie für die 78. Teilmenge alle Teilmengen des Arrays ohne wiederholte Zahlen. Gemäß der Vorlage sollte unsere Idee so aussehen:

  • Unerforschter Bereich: verbleibendes nicht durchsuchtes Array nums[index: N - 1];
  • Ob jeder Pfad die Bedingungen der Frage erfüllt: Jeder Pfad ist eine Teilmenge, alle Pfade erfüllen die Bedingungen und müssen in res platziert werden;
  • Wenn der aktuelle Pfad die Bedingungen erfüllt, wird die Suche fortgesetzt: Ja, nachdem die Teilmenge in nums[0:index-1] gefunden wurde, wird durch Hinzufügen von nums[index] zum alten Pfad eine neue Teilmenge gebildet
    .
  • Die derzeit möglichen Auswahlmöglichkeiten im unerforschten Bereich: Jede Auswahl kann 1 Zeichen von s auswählen, also nums[index];
  • Die aktuelle Auswahl erfüllt die Anforderungen: Alle nums[index] werden qualifiziert und direkt im Pfad platziert.
  • Neuer unerforschter Bereich: nums Die verbleibende Zeichenfolge nach dem 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()

Vereinfachtes Schreiben

Das Obige ist die formale Backtracking-Methode. Wenn Sie faul sein möchten, können Sie bei jeder Suche eine neue Pfadvariable erstellen, anstatt den globalen Pfad wiederzuverwenden. Dann kann der Code rationalisiert werden.

Wie unten beschrieben, wird jedes Mal, wenn Sie nach einer neuen Teilmenge suchen, ein neuer Pfad erstellt, da Pfad + [nums[i]] eine neue Liste zurückgibt, die in den Parametern der Funktion platziert wird. Bei jeder Übergabe sind sie es Alles neu, daher ist bei res.append(path) keine tiefe Kopie erforderlich.

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]])

Übergeben Sie weniger Parameter

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. Andere Lösungen

Idee 1. Bibliotheksfunktionen

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

Idee 2: Iteration, ein Durchlauf (Simulation), kein Backtracking erforderlich

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

Vereinfachtes Schreiben

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

Idee 3: Rekursion (Backtracking-Algorithmus)

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. Fragentransformation: Teilmenge II

90. Subset II Ermittelt den Wert eines Arrays, das wiederholte Elemente enthält.
Sie erhalten ein ganzzahliges Array nums, das wiederholte Elemente enthalten kann. Bitte geben Sie alle möglichen Teilmengen (Potenzmengen) des Arrays zurück.
Die Lösungsmenge darf keine doppelten Teilmengen enthalten. Im zurückgegebenen Lösungssatz können die Teilmengen in beliebiger Reihenfolge angeordnet werden.

Beispiel 1:

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

Beispiel 2:

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

Hinweis:

  • 1 <= Anzahl.Länge <= 10
  • -10 <= nums[i] <= 10

Formelles Schreiben

Wenn Sie das oben beschriebene Backtracking verstehen, ist die Unterteilung eines Arrays mit doppelten Elementen nur eine kleine Verbesserung.

Wenn Sie beispielsweise die Teilmenge von nums = [1,2,2] finden möchten, dann haben Sie die ersten 2 für die Teilmenge [1,2] ausgewählt, dann können Sie die zweiten 2 nicht auswählen, um [1,2] zu bilden ]. . Daher besteht die Änderung zu diesem Zeitpunkt darin, zuerst zu sortieren. Bevor Sie jedes Element nums[i] zum Pfad hinzufügen, stellen Sie fest, ob nums[i] gleich nums[i - 1] ist. Wenn es gleich ist, wird es nicht zum Pfad hinzugefügt Weg.

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()

Übergeben Sie weniger Parameter

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

Vereinfachtes Schreiben

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]])

Übergeben Sie weniger Parameter

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

andere Lösungen

Idee 1: Eine Durchquerung (Simulation), kein Zurückverfolgen erforderlich

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

Vereinfachtes Schreiben

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

Idee 2: Zählen Sie die Häufigkeit jeder Zahl

Keine Deduplizierung oder Sortierung

# 刚开始我们只有空集一个答案,循环所有可能的数字,
# 每次循环我们对当前答案的每一种情况考虑加入从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

おすすめ

転載: blog.csdn.net/qq_43030934/article/details/131642480