バックトラッキングアルゴリズムの基本的な考え方と実装

基本的な考え方

バックトラッキング アルゴリズムは、さまざまな代替案を試して問題を解決しようとする再帰的アルゴリズムです。その基本的な考え方は、可能な決定から検索を開始することです。このパスで有効な答えが得られないことが判明した場合は、前の層に戻って別の可能な決定を選択し、上記の手順を繰り返します。

具体的には、バックトラッキング アルゴリズムは、深さ優先探索を通じて問題の解空間を横断します。深さ優先探索の処理では、ある層を探索する際に、その層のノードの状態が問題の制約条件に従って条件を満たすかどうかが判断されます。条件が満たされない場合、このノードの状態は除外され、現在のノードの親ノードがトレースバックされて再度横断されます。条件が満たされる場合、次の層のノードの検索を続け、適切な解が見つかるまで、または解空間全体を横断するまで上記の操作を繰り返します。

バックトラッキング アルゴリズムは、通常、組み合わせ問題、順列問題、検索問題などを解決するのに適しています。検索プロセス中に特定の状態を常に再選択および破棄する必要があるため、バックトラッキング アルゴリズムは O(N) の空間複雑さの特性を備えています。

バックトラッキング アルゴリズムの検索パスはツリー構造を表すため、「バックトラッキング検索」または「深さ優先検索 + 状態取り消し」アルゴリズムと呼ばれることもあります。

バックトラッキングアルゴリズムのための再帰的フレームワーク

バックトラッキング アルゴリズムは典型的な再帰アルゴリズムであり、通常は再帰関数を使用して実装する必要があります。バックトラッキング アルゴリズムの再帰的フレームワークは通常、次の形式になります。

def backtrack(candidate, state):
    if state == target:                        # 满足条件,输出结果
        output(candidate)
        return
    # 选择
    for choice in choices:
        make_choice(choice)                    # 做选择
        backtrack(candidate, state + 1)        # 递归进入下一层状态
        undo_choice(choice)                    # 撤销选择

この再帰的なフレームワークは通常、選択、再帰、選択解除の 3 つの部分で構成されます。具体的には、各反復で、アルゴリズムは試行可能な状態または変数を選択します。次に、次の状態層に入り、再帰を続けます。再帰処理中に現在の状態が要件を満たしていないことが判明した場合は、前の選択をキャンセルし、前の状態に戻り、再度検索を開始する必要があります。

バックトラッキング アルゴリズムでは、選択と元に戻す操作をどのように定義するかに焦点が当てられます。これらの操作は通常、特定の問題に関連しているため、問題の特性に従って定義する必要があります。バックトラッキング アルゴリズムの実装には、次の手順が含まれる必要があります。

  1. 目標状態に到達したかどうかを判断します。条件を満たす状態が見つかったら出力して検索を終了します。
  2. 現在の状態または変数を選択します。現在の状態のすべてのオプションのシーケンス、オプションの子などの中から、検索されていない状態または変数を選択します。
  3. 新しい状態を選択してみてください。次の状態に入る前に、新しい状態を選択し、選択リストへの追加など、対応する操作を完了する必要があります。
  4. 再帰的に次のレベルの状態に入ります。次のレベルの状態に移動し、検索を続けます。
  5. 選択を解除します。現在の状態が要件を満たしていない場合は、以前の選択と完了した操作をすべて元に戻し、前の状態に戻り、他のオプションのシーケンスまたは子ノードの検索を続ける必要があります。

組み合わせ問題

LeetCode 質問 77: https://leetcode.cn/problems/combinations/
ここに画像の説明を挿入

まず、結果セットと現在の候補セットを保存する 2 つのグローバル変数を定義します。

// 存放所有满足条件的结果
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 当前得到的候选集
List<Integer> temp = new ArrayList<Integer>();
  1. 再帰的終了条件: サブセット サイズ k の組み合わせが見つかります。つまり、
if (temp.size() == k) {
    
    
    ans.add(new ArrayList<Integer>(temp));
    return;
}
  1. 選ぶ
for(int i = cur; i<=n;i++){
    
    
    temp.add(i);	// 当前元素加入候选集合
    dfs(i+1,n,k);	// 递归进入下一层状态
    temp.remove(temp.size() - 1);	// 当前元素移除候选集合
}

完全なコード

class Solution {
    
    
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();

    public List<List<Integer>> combine(int n, int k) {
    
    
        dfs(1, n, k);
        return ans;
    }

    public void dfs(int cur, int n, int k) {
    
    
    		// 剪枝:可选的元素不足K个
        if (temp.size() + (n - cur + 1) < k) {
    
    
            return;
        }
        if (temp.size() == k) {
    
    
            ans.add(new ArrayList<Integer>(temp));
            return;
        }

        for(int i = cur; i<=n;i++){
    
    
            temp.add(i);
            dfs(i+1,n,k);
            temp.remove(temp.size() - 1);
        }
    }
}

合計

LeetCode 質問 39: 組み合わせの合計: https://leetcode.cn/problems/combination-sum/
ここに画像の説明を挿入

まず、結果セットと現在の候補セットを保存する 2 つのグローバル変数を定義します。

// 存放所有满足条件的结果
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 当前得到的候选集
List<Integer> temp = new ArrayList<Integer>();
  1. 再帰的終了条件: 現在の候補セットの合計がターゲットより大きい、または合計がターゲットとなる組み合わせが見つかる、つまり
if(sum > target){
    
    
    return;
}
if(sum == target){
    
    
    res.add(new ArrayList<Integer>(temp));
    return;
}
  1. 選ぶ
for(int i=index;i<candidates.length;i++){
    
    
    temp.add(candidates[i]);	// 当前元素加入候选集合
    sum+=candidates[i];			// 当前候选集总和
    dfs(candidates, target, i);	// 递归进入下一层状态
    // 当前元素移除候选集合
    sum -= temp.get(temp.size()-1);
    temp.remove(temp.size()-1);
}

完全なコード

class Solution {
    
    
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    int sum = 0;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
    
    
        dfs(candidates, target, 0);
        return res;
    }

    public void dfs(int[] candidates, int target, int index){
    
    
        if(sum > target){
    
    
            return;
        }
        if(sum == target){
    
    
            res.add(new ArrayList<Integer>(temp));
            return;
        }
        for(int i=index;i<candidates.length;i++){
    
    
            temp.add(candidates[i]);
            sum+=candidates[i];
            dfs(candidates, target, i);
            sum -= temp.get(temp.size()-1);
            temp.remove(temp.size()-1);
        }
    }
}

組み合わせ重複排除

LeetCode 質問 40: 組み合わせ合計 II: https://leetcode.cn/problems/combination-sum-ii/
ここに画像の説明を挿入

前の質問の合計に基づいて、使用される配列が要素が渡されたかどうかを記録するかどうかを定義します。

  1. 再帰的終了条件: 現在の候補セットの合計がターゲットより大きい、または合計がターゲットとなる組み合わせが見つかる、つまり
if(sum > target){
    
    
    return;
}
if(sum == target){
    
    
    res.add(new ArrayList<Integer>(temp));
    return;
}
  1. 選ぶ
for(int i=cur;i<candidates.length;i++){
    
    
	//当前元素是否有效
    if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){
    
    
        continue;
    }
    temp.add(candidates[i]);	// 当前元素加入候选集合
    used[i] = true;		// 标记当前元素已使用
    sum += candidates[i];		// 当前候选集总和
    dfs(candidates,target,i+1,sum,used);		// 递归进入下一层状态
    // 当前元素移除候选集合
    sum -= temp.get(temp.size() - 1);
    temp.remove(temp.size() - 1);
    used[i] = false;
}
for(int i=index;i<candidates.length;i++){
    
    
    temp.add(candidates[i]);	// 当前元素加入候选集合
    sum+=candidates[i];			// 当前候选集总和
    dfs(candidates, target, i);	// 递归进入下一层状态
    // 当前元素移除候选集合
    sum -= temp.get(temp.size()-1);
    temp.remove(temp.size()-1);
}

完全なコード

class Solution {
    
    
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    
    
        Arrays.sort(candidates);
        boolean[] used = new boolean[candidates.length];
        dfs(candidates, target, 0, 0, used);
        return res;
    }

    public void dfs(int[] candidates, int target, int cur, int sum, boolean[] used){
    
    
        if(sum > target){
    
    
            return;
        }
        if(sum == target){
    
    
            res.add(new ArrayList<Integer>(temp));
            return;
        }

        for(int i=cur;i<candidates.length;i++){
    
    
            if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){
    
    
                continue;
            }
            temp.add(candidates[i]);
            used[i] = true;
            sum += candidates[i];
            dfs(candidates,target,i+1,sum,used);
            sum -= temp.get(temp.size() - 1);
            temp.remove(temp.size() - 1);
            used[i] = false;
        }
    }
}

サブセット

  1. サブセット: https://leetcode.cn/problems/subsets/
    ここに画像の説明を挿入
class Solution {
    
    
    List<List<Integer>> res = new ArrayList<List<Integer>>();

    Deque<Integer> temp = new ArrayDeque<Integer>();
    public List<List<Integer>> subsets(int[] nums) {
    
    

        dfs(nums, 0);

        return res;
    }

    public void dfs(int[] nums, int index){
    
    
        res.add(new ArrayList<Integer>(temp));

        for(int i=index;i<nums.length;i++){
    
    
            temp.addLast(nums[i]);
            dfs(nums, i+1);
            temp.removeLast();
        }
    }
}

フルアレイ

  1. 完全な順列: https://leetcode.cn/problems/permutations/
    ここに画像の説明を挿入
class Solution {
    
    
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    public List<List<Integer>> permute(int[] nums) {
    
    
        boolean[] used = new boolean[nums.length];
        dfs(nums, used, 0);
        return res;
    }

    public void dfs(int[] nums, boolean[] used, int index){
    
    
        if(temp.size() == nums.length){
    
    
            res.add(new ArrayList<Integer>(temp));
            return;
        }
        for(int i=0;i<nums.length;i++){
    
    
            if(used[i]){
    
    
                continue;
            }
            used[i] = true;
            temp.add(nums[i]);
            dfs(nums, used, i+1);
            used[i] = false;
            temp.remove(temp.size()-1);
        }
    }
}

おすすめ

転載: blog.csdn.net/weixin_43598687/article/details/131233780