Link original: Problema de combinação
————————————————
1. Materiais de referência
Pensamentos aleatórios sobre código - Problemas de combinação
leetcode - Questão 77. Combinação
2. Introdução ao Backtracking
Recursão e retrocesso são uma família, e ambos, retrocesso e recursão, andam de mãos dadas.
Backtracking é um subproduto da recursão, enquanto houver recursão haverá retrocesso。
1. Introdução à pergunta
Alguns problemas só podem ser pesquisados por força bruta, e não há outra solução mais eficiente, então o algoritmo de retrocesso é escolhido.
2. Cenários aplicáveis do método backtracking
O método de retrocesso geralmente pode resolver os seguintes problemas:
- Problema de combinação: Encontre um conjunto de k números em N números de acordo com certas regras;
- Problema de corte: Existem várias maneiras de cortar uma corda de acordo com certas regras;
- Problema do subconjunto: Quantos subconjuntos elegíveis existem em um conjunto de N números;
- Problema de arranjo: N números são arranjados de acordo com certas regras, e existem vários métodos de arranjo;
- Problemas de tabuleiro: N Queens, resolvendo Sudoku e muito mais.
3. Essência do retrocesso
A essência do método de retrocesso é enumerar exaustivamente todas as possibilidades e, em seguida, selecionar a resposta que queremos. Portanto, o desempenho do método de retrocesso não é alto. Para melhorar a eficiência do método de retrocesso, algumas operações de poda podem ser adicionadas.
4. Entenda o retrocesso
Os problemas resolvidos pelo método backtracking podem ser abstraídos em uma estrutura de árvore, porque o método backtracking trata de procurar recursivamente por subconjuntos na coleção . O tamanho da coleção constitui a largura da árvore e a profundidade da recursão constitui a profundidade da árvore.
A abordagem básica do método de retrocesso é a busca em profundidade, que é um algoritmo de busca exaustiva bem organizado que pode evitar buscas repetidas desnecessárias.
algoritmo de retrocessoideia básicaÉ: avance por um caminho, avance se puder, recue se não puder e tente outro caminho.
5. Processo de algoritmo de retrocesso
5.1 Determinar os parâmetros e o valor de retorno da função de retrocesso
- Nome da função, no algoritmo de retrocesso, o nome da função geralmente é backtracking();
- Valor de retorno, o valor de retorno da função no algoritmo de retrocesso é geralmente nulo.
- parâmetro,Os parâmetros exigidos pelo algoritmo de retrocesso não são tão fáceis de determinar de uma só vez quanto a recursão da árvore binária, então a lógica geralmente é escrita primeiro e, em seguida, quaisquer que sejam os parâmetros necessários, basta preencher os parâmetros。
O pseudocódigo da função de retrocesso é o seguinte:
void backtracking(参数)
5.2 Determinando as condições de término do método de retrocesso
De um modo geral, quando um nó folha é pesquisado, uma resposta que satisfaz as condições é encontrada e a resposta é armazenada e a recursão dessa camada termina.
O pseudocódigo da condição de término da função de retrocesso é o seguinte:
if (终止条件) {
存放结果;
return;
}
5.3 Processo transversal de busca retroativa
Normalmente, o método de retrocesso é pesquisar recursivamente na coleção, o tamanho da coleção constitui a largura da árvore e a profundidade da recursão constitui a profundidade da árvore.
O pseudocódigo do processo de travessia da função de retrocesso é o seguinte:
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
Como pode ser visto na figura,O loop for é usado para travessia horizontal e o backtracking (recursivo) é usado para travessia vertical, para que toda a árvore possa ser percorrida. De um modo geral, procurar nós de folha é encontrar um dos resultados.
Depois de analisar o processo, a estrutura do modelo de algoritmo de retrocesso é a seguinte:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
3. Exemplo de análise
1. Exemplo de código-fonte
Para obter o código-fonte, consulte: Random Thoughts on Code - Question 77. Combination
class Solution {
private:
vector<vector<int>> result; // 存放符合条件结果的集合
vector<int> path; // 用来存放符合条件结果
void backtracking(int n, int k, int startIndex) {
if (path.size() == k) {
result.push_back(path);
return;
}
for (int i = startIndex; i <= n; i++) {
path.push_back(i); // 处理节点
backtracking(n, k, i + 1); // 递归
path.pop_back(); // 回溯,撤销处理的节点
}
}
public:
vector<vector<int>> combine(int n, int k) {
result.clear(); // 可以不写
path.clear(); // 可以不写
backtracking(n, k, 1);
return result;
}
};