1. トピック
組み合わせの合計
説明:
繰り返し要素のない整数配列の候補とターゲット整数ターゲットを指定すると、数値の合計がターゲットの数値になるような候補内のさまざまな組み合わせをすべて見つけ、それをリスト形式で返します。これらの組み合わせは任意の順序で返すことができます。
同じ候補数を無制限に繰り返し選択できます。少なくとも 1 つの数字の選択された数字が異なる場合、2 つの組み合わせは異なります。
特定の入力について、ターゲットとなるさまざまな組み合わせの合計が 150 未満であることが保証されます。
例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
例 3:
输入: candidates = [2], target = 1
输出: []
ヒント:
- 1 <= 候補.長さ <= 30
- 2 <= 候補者[i] <= 40
- 候補のすべての要素は互いに異なります 1 <= ターゲット <= 40
2. 問題解決アイデアの分析
バックトラッキングのアイデア:
バックトラッキング手法の重要な考え方は、列挙を通じてすべての可能性をたどることです。ただし、列挙の順番はダークサイドまで行くことであり、ダークサイドを発見した後は一歩下がって、まだ通っていない道に挑戦してください。すべての道が試されるまで。したがって、後戻りの方法は次のように簡単に理解できます。 できないときに一歩後退する列挙方法は後戻りの方法と呼ばれます。ここでのリトレースメントポイントは回想ポイントとも呼ばれます。
重要なポイントを遡ってみる
分析の結果、バックトラッキング手法によって実現される重要な技術ポイントは次の 3 つであることがわかりました。
- 黒への道
- あとずさりする
- 別の方法を見つける
重要なポイントの実現
では、上記の 3 つの重要な点をコードでどのように実現できるのでしょうか?
- for ループ
- 再帰
以下で説明します
for ループの役割は、別の方法を見つけることです。forループを使用して、現在のノードの下で考えられるすべての分岐パスを 1 つずつ選択できるパス セレクターの機能を実装できます。
例: 今、あなたはノード a に来ました。a は交差点のようなもので、上から a に到達し、さらに下に降りることができます。現時点で i 通りの方法がある場合は、すべての i 方法を 1 つずつ試さなければなりません。for の機能は、繰り返されたり欠落したりしていないすべての下向きの i パスを 1 つずつ試行できるようにすることです。
再帰では、黒へのパスと 1 ステップ戻ることができます。
黒へのパス: 再帰とは、for で指定されたパスを 1 ステップ下に進み続けることを意味します。再帰を for ループ内に置くと、パスを指定した後に for ループが再帰に入るたびに、ループは下降し続けます。再帰的に終了するまで (進む方法はありません)。これが暗闇への道を達成する方法です。再帰が再帰を終了すると、一歩後退します。
したがって、for ループと再帰を連携させることで、再帰が再帰出口から出たときのバックトラッキングを実現できます。前のレベルの for ループは引き続き実行されます。for ループを継続すると、現在のノードの下で次の実行可能なパスが得られます。次に、再帰呼び出しは、これまでに通過したことのないこのパスに沿ってさらに一歩進みます。これはエピローグです
そうは言っても、バックトラッキングの通常のテンプレートは何でしょうか? 再帰と for はどのように連携しますか?
コードテンプレートをバックトラッキングする
def backward():
if (回朔点):# 这条路走到底的条件。也是递归出口
保存该结果
return
else:
for route in all_route_set : 逐步选择当前节点下的所有可能route
if 剪枝条件:
剪枝前的操作
return #不继续往下走了,退回上层,换个路再走
else:#当前路径可能是条可行路径
保存当前数据 #向下走之前要记住已经走过这个节点了。例如push当前节点
self.backward() #递归发生,继续向下走一步了。
回朔清理 # 该节点下的所有路径都走完了,清理堆栈,准备下一个递归。例如弹出当前节点
ここでいう剪定とは、問題によっては歩き続けていくうちに、ある状況が起こってしまうと、もうこれ以上は進めない、もうこれ以上進んでも意味がない、ということを指します。この状況を枝刈り条件と呼びます。
DFS は、バックトラッキングの最も典型的なアプリケーションの 1 つです。
3. 解決策
バックトレース
class Solution(object):
def combinationSum(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
# 解法一
candidates = sorted(candidates)
ans = []
def find(s, use, remain):
for i in range(s, len(candidates)):
c = candidates[i]
if c == remain:
ans.append(use + [c])
if c < remain:
find(i, use + [c], remain - c)
if c > remain:
return
find(0, [], target)
return ans
# 解法二
res = []
def combination(candidates,target,res_list):
if target < 0:
return
if target == 0:
res.append(res_list)
for i,c in enumerate(candidates):
# 为了避免重复 (例如candiactes=[2,3,6,7],target=7,输出[[2,2,3],[3,2,2][7]])
# 传到的下一个candicate为candicates[i:]
combination(candidates[i:],target-c,res_list+[c])
combination(candidates,target,[])
return res
# 解法三
candidates.sort()
n = len(candidates)
res = []
def backtrack(i, tmp_sum, tmp):
if tmp_sum > target or i == n:
return
if tmp_sum == target:
res.append(tmp)
return
for j in range(i, n):
if tmp_sum + candidates[j] > target:
break
backtrack(j, tmp_sum + candidates[j], tmp + [candidates[j]])
backtrack(0, 0, [])
return res
動的プログラミング ソリューションを添付します。
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
dp = {
i:[] for i in range(target+1)}
# 这里一定要将candidates降序排列
for i in sorted(candidates,reverse=True):
for j in range(i,target+1):
if j==i:
dp[j] = [[i]]
else:
dp[j].extend([x+[i] for x in dp[j-i]])
return dp[target]