バックトラッキングアルゴリズムとは何ですか?
バックトラッキング アルゴリズムは、バックトラッキング検索アルゴリズムとも呼ばれます。バックトラッキングは再帰の「副産物」です。バックトラッキングの本質は網羅的であり、必要なデータを選択します。バックトラッキング自体は特に効率的なアルゴリズムではありませんが、次のことができます。 「枝刈り」によって最適化します。
バックトラッキングアルゴリズムを理解する
バックトラッキング アルゴリズムの解法は、ツリー構造をシミュレートできます。バックトラッキング メソッドは、セット内のサブセットを再帰的に検索するプロセスを解決し、セットのサイズがツリーの幅を構成し、再帰の深さがツリーの深さを構成するためです。木。
バックトラッキング アルゴリズム テンプレート
- バックトラッキング アルゴリズムの戻り値とパラメーターを決定します (通常は最初にロジックを作成し、次に必要なパラメーターを追加します)。
- バックトラッキング機能の終了条件を決定する
- バックトラッキング検索の走査プロセスを決定する
void BackTracking(参数)
{
if (终止条件)
{
处理结果
return;
}
for (选择:本层集合中的元素(树中节点孩子的数量就是集合的大小))
{
处理节点
BackTracking(路径,选择列表);//递归
回溯,撤销处理结果
}
}
組み合わせ
問題:
2 つの整数 n と k が与えられた場合、範囲 [1, n] 内の k 個の数値の可能なすべての組み合わせを返します。
回答は任意の順序で返すことができます。
出典: LeetCodeポートフォリオ
アイデア 1: この種の問題は、k==2 の場合など、for ループを使用してループをカバーすると、一目で考えることができます。
int n = 4;
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n; j++)
{
//处理结果
}
}
しかし、k がますます大きくなる場合、適用するサイクルはますます多くなり、この暴力的な解決策は間違いなく非現実的です。
アイデア 2: 前に述べたように、ツリー構造はバックトラックと再帰のプロセスをシミュレートするために使用できます。
たとえば、n=4 k=2
ツリーの初期セットは [1,2,3,4] で、次から取得されます。左から右へ、取得 渡された数値は取得されなくなり、コレクションから要素が選択されるたびに、オプションの範囲が徐々に狭まります。図から、n はツリーの幅に相当し、k はツリーの深さに相当することがわかります。テンプレートを使用して最終コードを記述してみましょう。
class Solution {
public:
vector<vector<int>> arr;//存放符合条件的集合
vector<int> _arr;//用来存放符合条件的单一数据
void BackTracking(int n, int k, int begin)
{
if (_arr.size() == k)//递归终止条件
{
arr.push_back(_arr);//单一数据存放至总集合里
return;
}
for (int i = begin; i <= n; i++)//控制树的横向遍历
{
_arr.push_back(i);//处理节点
BackTracking(n, k, i + 1);//递归,控制树的纵向遍历,即深度
_arr.pop_back();//回溯,撤销处理的节点
}
}
vector<vector<int>> combine(int n, int k) {
BackTracking(n, k, 1);
return arr;
}
};
枝刈りの最適化
次の状況が発生した場合:
n=4 k=4
、for ループの最初の層では、要素 2 から始まる走査は、k を満たす数が十分ではないため意味がありません。したがって、図からわかるように、交差した場所は最適化できます
最適化プロセスを削除します。
- 選択された要素の数: _arr.size()
- まだ必要な要素の数: k - _arr.size()
最適化されたコード:
class Solution {
public:
vector<int> _arr;
vector<vector<int>> arr;
void BackTracking(int n, int k, int begin)
{
if (_arr.size() == k)
{
arr.push_back(_arr);
return;
}
for (int i = begin; i <= n-(k-_arr.size())+1; i++)//剪枝优化
{
_arr.push_back(i);
BackTracking(n, k, i + 1);
_arr.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
BackTracking(n, k, 1);
return arr;
}
};
ポートフォリオサムIII
質問:
合計が n となる k 個の数値の組み合わせをすべて見つけ、次の条件を満たします。
- 1 ~ 9 の数字のみを使用してください
- 各番号は最大 1 回使用してください
考えられるすべての有効な組み合わせのリストを返します。リストには同じ組み合わせを 2 回含めることはできず、組み合わせは任意の順序で返されます。
出典: LeetCodeポートフォリオ Sum III
アイデア: 前の質問と比較すると、この質問は k がツリーの深さであり、セットが 1 から 9 に固定されている、つまりツリーの幅が 9 であるということです。たとえば、k=2 には 2 つしかかかりません
。数字
コード:
class Solution {
public:
vector<vector<int>> arr;
vector<int> _arr;
void BackTracking(int k,int n,int begin,int sum)
{
if(_arr.size()==k)//终止条件
{
if(sum==n)//满足题意
{
arr.push_back(_arr);
}
return;
}
for(int i=begin;i<=9;i++)//横向遍历
{
sum+=i;//收集元素总和
_arr.push_back(i);//收集元素
BackTracking(k,n,i+1,sum);//递归,纵向遍历
sum-=i;//回溯
_arr.pop_back();//回溯
}
}
vector<vector<int>> combinationSum3(int k, int n) {
BackTracking(k,n,1,0);
return arr;
}
};
枝刈りの最適化
選択した要素の合計が既に n より大きい場合、後で走査する合計も n より大きくなければならず、走査を続ける意味はありません。要素の数に関しては、前の質問と同様に最適化を続けることができます。
最適化:
class Solution {
public:
vector<vector<int>> arr;
vector<int> _arr;
void BackTracking(int k,int n,int begin,int sum)
{
if(sum>n)//剪枝条件
{
return;
}
if(_arr.size()==k)
{
if(sum==n)
{
arr.push_back(_arr);
}
return;
}
for(int i=begin;i<=9-(k-_arr.size())+1;i++)//元素个数的优化
{
sum+=i;
_arr.push_back(i);
BackTracking(k,n,i+1,sum);
sum-=i;
_arr.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
BackTracking(k,n,1,0);
return arr;
}
};