Diretório de artigos
ideia básica
Um algoritmo de backtracking é um algoritmo recursivo que tenta resolver um problema tentando diferentes alternativas. Sua idéia básica é iniciar a busca a partir de possíveis decisões, caso se verifique que este caminho não consegue uma resposta efetiva, volte para a camada anterior para escolher outra possível decisão, e repita os passos anteriores.
Especificamente, o algoritmo de retrocesso percorre o espaço de solução do problema por meio da busca em profundidade. No processo de busca em profundidade, quando uma determinada camada é pesquisada, é julgado se o estado do nó dessa camada satisfaz a condição de acordo com as condições de restrição do problema. Se a condição não for atendida, o estado desse nó será excluído e o nó pai do nó atual será rastreado para percorrer novamente. Se a condição for satisfeita, continue a procurar os nós da próxima camada e repita as operações acima até que uma solução qualificada seja encontrada ou todo o espaço de soluções seja percorrido.
Algoritmos de backtracking são geralmente adequados para resolver problemas combinatórios, problemas de permutação, problemas de busca, etc. Como um determinado estado precisa ser constantemente reselecionado e descartado durante o processo de busca, o algoritmo de retrocesso tem como característica uma complexidade espacial de O(N).
Como o caminho de pesquisa do algoritmo de retrocesso apresenta uma estrutura de árvore, às vezes é chamado de algoritmo de "pesquisa de retrocesso" ou "pesquisa em profundidade + revogação de estado".
Estrutura recursiva para algoritmos de backtracking
O algoritmo de backtracking é um algoritmo recursivo típico, que geralmente precisa ser implementado usando funções recursivas. A estrutura recursiva do algoritmo de retrocesso é geralmente da seguinte forma:
def backtrack(candidate, state):
if state == target: # 满足条件,输出结果
output(candidate)
return
# 选择
for choice in choices:
make_choice(choice) # 做选择
backtrack(candidate, state + 1) # 递归进入下一层状态
undo_choice(choice) # 撤销选择
Essa estrutura recursiva geralmente consiste em três partes: seleção, recursão e desmarcação. Especificamente, em cada iteração, o algoritmo escolhe um estado ou variável disponível para tentar. Em seguida, entre na próxima camada de estado e continue a recursão. Se for constatado que o estado atual não atende aos requisitos durante o processo recursivo, será necessário cancelar a seleção anterior, retornar ao estado anterior e iniciar a busca novamente.
No algoritmo de retrocesso, o foco está em como definir operações de seleção e desfazer. Essas operações geralmente estão relacionadas a problemas específicos e precisam ser definidas de acordo com as características do problema. A implementação do algoritmo de backtracking deve incluir as seguintes etapas:
- Determine se o estado de destino foi alcançado. Quando um estado que atende aos requisitos for encontrado, gere-o e finalize a pesquisa.
- Selecione o estado ou variável atual. Entre todas as sequências opcionais, filhos opcionais, etc. do estado atual, seleciona um estado ou variável que não foi pesquisado.
- Tente selecionar um novo estado. Antes de entrar no próximo estado, você precisa tentar selecionar um novo estado e concluir as operações correspondentes, como adicionar à lista de seleção.
- Insira recursivamente o próximo nível de estado. Vá para o próximo nível de estado e continue pesquisando.
- Desmarcar. Se o estado atual não atender aos requisitos, você precisará desfazer todas as seleções anteriores e operações concluídas, retornar ao estado anterior e continuar a procurar outras sequências opcionais ou nós filhos.
problema de combinação
Pergunta LeetCode 77: https://leetcode.cn/problems/combinations/
Primeiro defina duas variáveis globais para armazenar o conjunto de resultados e o conjunto de candidatos atual:
// 存放所有满足条件的结果
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 当前得到的候选集
List<Integer> temp = new ArrayList<Integer>();
- Condição de terminação recursiva: uma combinação de tamanho de subconjunto k é encontrada, ou seja
if (temp.size() == k) {
ans.add(new ArrayList<Integer>(temp));
return;
}
- escolher
for(int i = cur; i<=n;i++){
temp.add(i); // 当前元素加入候选集合
dfs(i+1,n,k); // 递归进入下一层状态
temp.remove(temp.size() - 1); // 当前元素移除候选集合
}
código completo
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);
}
}
}
soma combinada
Questão LeetCode 39: Soma combinada: https://leetcode.cn/problems/combination-sum/
Primeiro defina duas variáveis globais para armazenar o conjunto de resultados e o conjunto de candidatos atual:
// 存放所有满足条件的结果
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 当前得到的候选集
List<Integer> temp = new ArrayList<Integer>();
- Condição de terminação recursiva: a soma do conjunto candidato atual é maior que o alvo ou uma combinação cuja soma é o alvo é encontrada, ou seja
if(sum > target){
return;
}
if(sum == target){
res.add(new ArrayList<Integer>(temp));
return;
}
- escolher
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);
}
código completo
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);
}
}
}
desduplicação combinada
LeetCode Questão 40: Combinação Soma II: https://leetcode.cn/problems/combination-sum-ii/
Com base no somatório combinado da questão anterior, defina se um array usado registra se o elemento passou:
- Condição de terminação recursiva: a soma do conjunto candidato atual é maior que o alvo ou uma combinação cuja soma é o alvo é encontrada, ou seja
if(sum > target){
return;
}
if(sum == target){
res.add(new ArrayList<Integer>(temp));
return;
}
- escolher
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);
}
código completo
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;
}
}
}
Subconjunto
- Subconjuntos: 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();
}
}
}
matriz completa
- Permutações completas: 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);
}
}
}