After reading this article, you not only learned the algorithm routines, but you can also go to LeetCode to win the following topics:

78. Subset (Medium)

90. Subset II (Medium)

77. Combination (Medium)

39. Combined Sum (Medium)

40. Combined Sum II (Medium)

216. Combined Sum III (Medium)

46. Full array (medium)

47. Full permutation II (medium)

-----------

Although these problems have been learned in high school, if you want to write algorithms to solve these kinds of problems, it is still a very test of computer thinking. This article will talk about the core ideas of programming to solve these problems, and what variants will there be in the future? You can also grasp it at your fingertips and respond to changes without changing.

Whether it is a permutation, combination or subset problem, simply put, it is nothing more than letting you take a number of elements from the sequence nums with a given rule. There are mainly the following variants:

Form 1. Elements are non-repetitive and cannot be checked, that is, the elements in nums are unique, and each element can only be used once at most, which is also the most basic form .

Taking the combination as an example, if you enter nums = [2,3,6,7] , the combination with a sum of 7 should only be [7] .

Form 2. Elements can be repeated but not checked, that is, elements in nums can be repeated, and each element can only be used once at most .

Taking the combination as an example, if you enter nums = [2,5,2,1,2] , the combination with the sum of 7 should have two types [2,2,2,1] and [5,2] .

Form 3. There is no duplicate element to be selected, that is, the elements in nums are unique, and each element can be used several times .

Taking the combination as an example, if you enter nums = [2,3,6,7] , the combination with the sum of 7 should have two types [2,2,3] and [7] .

Of course, it can also be said that there is a fourth form, that is, elements can be repeated and checked. But since elements can be checked, why should there be duplicate elements? After the element is deduplicated, it is equivalent to form three, so this situation is not considered.

The above example uses the combination problem, but the permutation, combination, and subset problems can all have these three basic forms, so there are 9 variations in total.

In addition, various restrictions can also be added to the question, such as a combination that allows you to sum target and the number of elements is k , then a bunch of variants can be derived, no wonder the interview is often tested in the written test to the basic question type of permutation and combination.

But no matter how the form changes, its essence is to exhaustively list all solutions, and these solutions have a tree structure, so the backtracking algorithm framework is reasonably used, and a slight of the code framework can solve these problems in one go.

Specifically, you need to read and understand the core routine of the backtracking algorithm above, , and then remember the backtracking tree of the following subset problem and permutation problem, you can solve all permutation and combination subset-related problems:

Why should just memorizing these two tree structures solve all related problems?

First of all, the combination problem and the subset problem are actually equivalent, which will be discussed later; as for the three variations mentioned above, it is nothing more than cutting off or adding some branches to the two trees.

So, then we start exhaustively, go through all 9 forms of permutation/combination/subsetting problems, and learn how to use backtracking algorithm to take them all away.

Subset (elements have no duplicates and cannot be selected)

The "subset" of Likou Question 78 is this question:

The question gives you an array nums with no repeating elements, where each element is used at most once, please return all subsets of nums .

The function signature is as follows:

List<List<Integer>> subsets(int[] nums)

For example, if you enter nums = [1,2,3] , the algorithm should return the following subset:

[ [],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3] ]

Well, let's not consider how to implement it with code for the time being, let's recall our high school knowledge first, how to push all subsets by hand?

First, generate a subset with 0 elements, that is, the empty set [] , which I call S_0 for convenience.

Then, based on S_0 , generate all subsets with 1 element, which I call S_1 :

Next, we can derive S_1 based on S_2 , that is, all subsets with 2 elements:

Why does the set [2] only need to add 3 instead of the preceding 1 ?

Because the elements in the set do not need to consider the order, [1,2,3] in 2 is only followed by 3 . If you consider 1 forward, then [2,1] will be repeated with the previously generated subset [1,2] .

In other words, we prevent duplicate subsets by keeping the relative order between elements unchanged.

Next, we can push S_3 S_2 in fact S_3 is only one set [1,2,3] in 06224c575148a0, which is pushed through [1,2] .

The whole derivation process is such a tree:

Note the properties of this tree:

If the root node is taken as the 0th layer, and the elements on the branch between each node and the root node are taken as the value of the node, then all the nodes in the n layer are all subsets of size n .

For example, a subset of size 2 is the value of this layer of nodes:

PS: Note that the "value of the node" mentioned later in this article refers to the element on the branch between the node and the root node, and the root node is considered to be the 0th layer .

Then go further, if you want to calculate all the subsets, just traverse this multi-fork tree and collect the values of all nodes?

Look directly at the code:

List<List<Integer>> res = new LinkedList<>();
// 记录回溯算法的递归路径
LinkedList<Integer> track = new LinkedList<>();

// 主函数
public List<List<Integer>> subsets(int[] nums) {
    backtrack(nums, 0);
    return res;
}

// 回溯算法核心函数,遍历子集问题的回溯树
void backtrack(int[] nums, int start) {

    // 前序位置,每个节点的值都是一个子集
    res.add(new LinkedList<>(track));
    
    // 回溯算法标准框架
    for (int i = start; i < nums.length; i++) {
        // 做选择
        track.addLast(nums[i]);
        // 通过 start 参数控制树枝的遍历,避免产生重复的子集
        backtrack(nums, i + 1);
        // 撤销选择
        track.removeLast();
    }
}

Readers who have read the backtracking algorithm core framework above should easily understand this code. We use the start parameter to control the growth of branches to avoid duplicate subsets, and use track to record the value of the path from the root node to each node, and at the same time The path value of each node is collected at the preorder position, and all subsets are collected after the traversal of the backtracking tree is completed:

Finally, the backtrack function seems to have no base case at the beginning, will it enter infinite recursion?

Actually not, when start == nums.length , the value of the leaf node will be loaded into res , but the for loop will not be executed, which ends the recursion.

Combination (elements have no duplicates and cannot be selected)

If you can successfully generate all the duplicate-free subsets, then you can generate all the duplicate-free combinations with a slight code change.

You say, let you take 2 elements in nums = [1,2,3] to form all combinations, how do you do it?

If you think about it for a while, you will find that all combinations of size 2 are not subsets of all sizes of 2.

So I say combination and subset are the same: combination of size k is subset k of size .

For example, the 77th question "Combination":

Given two integers n and k , return all possible combinations of k numbers in the range [1, n] .

The function signature is as follows:

List<List<Integer>> combine(int n, int k)

For example, the return value of combine(3, 2) should be:

[ [1,2],[1,3],[2,3] ]

This is the standard combinatorial problem, but I'll translate it into a subset problem for you:

gives you an array nums = [1,2..,n] and a positive integer k , please generate all subsets k

Take nums = [1,2,3] as an example. Just now asking you to find all subsets is to collect the values of all nodes; Now you only need to collect the nodes of the second layer (the root node is regarded as the 0th layer), that is, the size is All combinations of 2 :

Reflected on the code, it is only necessary to change the base case slightly, and the control algorithm only collects the value of the k layer node:

List<List<Integer>> res = new LinkedList<>();
// 记录回溯算法的递归路径
LinkedList<Integer> track = new LinkedList<>();

// 主函数
public List<List<Integer>> combine(int n, int k) {
    backtrack(1, n, k);
    return res;
}

void backtrack(int start, int n, int k) {
    // base case
    if (k == track.size()) {
        // 遍历到了第 k 层,收集当前节点的值
        res.add(new LinkedList<>(track));
        return;
    }
    
    // 回溯算法标准框架
    for (int i = start; i <= n; i++) {
        // 选择
        track.addLast(i);
        // 通过 start 参数控制树枝的遍历,避免产生重复的子集
        backtrack(i + 1, n, k);
        // 撤销选择
        track.removeLast();
    }
}

In this way, the standard subset problem is also solved.

Arrangement (elements without duplicates cannot be selected)

The permutation problem was mentioned in the core framework of the backtracking algorithm so I will briefly explain it here.

The 46th "full permutation" of Likou is the standard permutation problem:

Given an array nums of without the repeated number , return all possible full permutations of .

The function signature is as follows:

List<List<Integer>> permute(int[] nums)

For example, if you enter nums = [1,2,3] , the return value of the function should be:

[
    [1,2,3],[1,3,2],
    [2,1,3],[2,3,1],
    [3,1,2],[3,2,1]
]

The combination/subset problem just mentioned uses the start variable to ensure that only the elements in nums[start] appear after the element nums[start+1..] , and the relative positions of the elements are fixed to ensure that no duplicate subsets appear.

But the essence of the arrangement problem is to enumerate the positions of the elements. After nums[i] , the elements to the left of nums[i] can also appear, so the previous set does not work, and an additional used array is needed to mark which elements can be selected .

The standard full permutation can be abstracted into the following binary tree:

We use the used array to mark the elements already on the path to avoid repeated selection, and then collect the values on all leaf nodes, which is the result of all permutations:

List<List<Integer>> res = new LinkedList<>();
// 记录回溯算法的递归路径
LinkedList<Integer> track = new LinkedList<>();
// track 中的元素会被标记为 true
boolean[] used;

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
public List<List<Integer>> permute(int[] nums) {
    used = new boolean[nums.length];
    backtrack(nums);
    return res;
}

// 回溯算法核心函数
void backtrack(int[] nums) {
    // base case,到达叶子节点
    if (track.size() == nums.length) {
        // 收集叶子节点上的值
        res.add(new LinkedList(track));
        return;
    }

    // 回溯算法标准框架
    for (int i = 0; i < nums.length; i++) {
        // 已经存在 track 中的元素,不能重复选择
        if (used[i]) {
            continue;
        }
        // 做选择
        used[i] = true;
        track.addLast(nums[i]);
        // 进入下一层回溯树
        backtrack(nums);
        // 取消选择
        track.removeLast();
        used[i] = false;
    }
}

In this way, the full permutation problem is solved.

But if the question does not let you count the full arrangement, but lets you count the arrangement with the number of elements k , how to count it?

It is also very simple. Change the base case of the backtrack function and only collect the node values of the k layer:

// 回溯算法核心函数
void backtrack(int[] nums, int k) {
    // base case,到达第 k 层
    if (track.size() == k) {
        // 第 k 层节点的值就是大小为 k 的排列
        res.add(new LinkedList(track));
        return;
    }

    // 回溯算法标准框架
    for (int i = 0; i < nums.length; i++) {
        // ...
        backtrack(nums, k);
        // ...
    }
}

Subset/Combination (elements can be repeated but not selected)

The input nums of the standard subset problem just mentioned has no repeated elements, but if there are repeated elements, how to deal with it?

Likou Question 90 "Subset II" is such a question:

Given an integer array nums , which may contain duplicate elements, please return all possible subsets of the array.

The function signature is as follows:

List<List<Integer>> subsetsWithDup(int[] nums)

For example, enter nums = [1,2,2] and you should output:

[ [],[1],[2],[1,2],[2,2],[1,2,2] ]

Of course, it stands to reason that sets should not contain duplicate elements, but since the question is asked like this, let's ignore this detail and think carefully about how to do this question.

Take nums = [1,2,2] as an example. In order to distinguish two 2 as different elements, we will write nums = [1,2,2'] .

Draw the tree structure of the subset according to the previous idea. Obviously, two adjacent branches with the same value will be repeated:

[ 
    [],
    [1],[2],[2'],
    [1,2],[1,2'],[2,2'],
    [1,2,2']
]

Therefore, we need to prune. If a node has multiple adjacent branches with the same value, only the first one is traversed, and the rest are pruned, and do not traverse:

reflected in the code. It needs to be sorted first to make the same elements close together. If nums[i] == nums[i-1] is found, skip :

List<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> track = new LinkedList<>();

public List<List<Integer>> subsetsWithDup(int[] nums) {
    // 先排序,让相同的元素靠在一起
    Arrays.sort(nums);
    backtrack(nums, 0);
    return res;
}

void backtrack(int[] nums, int start) {
    // 前序位置,每个节点的值都是一个子集
    res.add(new LinkedList<>(track));
    
    for (int i = start; i < nums.length; i++) {
        // 剪枝逻辑,值相同的相邻树枝,只遍历第一条
        if (i > start && nums[i] == nums[i - 1]) {
            continue;
        }
        track.addLast(nums[i]);
        backtrack(nums, i + 1);
        track.removeLast();
    }
}

This code is almost the same as the previous standard subset problem, with the addition of sorting and pruning logic.

As for why pruning is necessary, it should be easy to understand in combination with the previous figure, so that the problem of subsets with repeated elements is also solved.

We said that the combination problem and the subset problem are equivalent to , so let's look at a combination problem directly. This is the 40th question "Combination Sum II":

Given you input candidates and a target sum target , find all combinations from candidates that sum to target .

candidates may have duplicate elements, each of which can be used at most once.

It is said that this is a combination problem, but in fact, it becomes a subset problem in another way: please calculate all the subsets in candidates whose sum is target .

So how to do this?

To compare the solution to the subset problem, just use an additional trackSum variable to record the sum of the elements on the backtracking path, and then change the base case to solve this problem:

List<List<Integer>> res = new LinkedList<>();
// 记录回溯的路径
LinkedList<Integer> track = new LinkedList<>();
// 记录 track 中的元素之和
int trackSum = 0;

public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    if (candidates.length == 0) {
        return res;
    }
    // 先排序,让相同的元素靠在一起
    Arrays.sort(candidates);
    backtrack(candidates, 0, target);
    return res;
}

// 回溯算法主函数
void backtrack(int[] nums, int start, int target) {
    // base case,达到目标和,找到符合条件的组合
    if (trackSum == target) {
        res.add(new LinkedList<>(track));
        return;
    }
    // base case,超过目标和,直接结束
    if (trackSum > target) {
        return;
    }

    // 回溯算法标准框架
    for (int i = start; i < nums.length; i++) {
        // 剪枝逻辑,值相同的树枝,只遍历第一条
        if (i > start && nums[i] == nums[i - 1]) {
            continue;
        }
        // 做选择
        track.add([i]);
        trackSum += nums[i];
        // 递归遍历下一层回溯树
        backtrack(nums, i + 1, target);
        // 撤销选择
        track.removeLast();
        trackSum -= nums[i];
    }
}

Arrangement (elements can be repeated but not checked)

If there are repetitions in the input of the permutation problem, it is a little more complicated than the subset/combination problem. Let's take a look at the 47th question "Full permutation II":

Given you input a sequence nums that can contain repeated numbers, please write an algorithm that returns all possible full permutations. The function signature is as follows:

List<List<Integer>> permuteUnique(int[] nums)

For example, input nums = [1,2,2] , the function returns:

[ [1,2,2],[2,1,2],[2,2,1] ]

First look at the solution code:

List<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> track = new LinkedList<>();
boolean[] used;

public List<List<Integer>> permuteUnique(int[] nums) {
    // 先排序,让相同的元素靠在一起
    Arrays.sort(nums);
    used = new boolean[nums.length];
    backtrack(nums, track);
    return res;
}

void backtrack(int[] nums) {
    if (track.size() == nums.length) {
        res.add(new LinkedList(track));
        return;
    }

    for (int i = 0; i < nums.length; i++) {
        if (used[i]) {
            continue;
        }
        // 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置
        if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
            continue;
        }
        track.add(nums[i]);
        used[i] = true;
        backtrack(nums);
        track.removeLast();
        used[i] = false;
    }
}

If you compare the previous standard full permutation solution code, this solution code has only two differences:

1. nums .

2. Added an extra pruning logic.

Analogy to the subset/combination problem where the input contains duplicate elements, you should probably understand that this is done to prevent duplicate results.

But pay attention to the pruning logic of the arrangement problem, which is slightly different from the pruning logic of the subset/combination problem: the logical judgment of !used[i - 1] has been added.

This place needs some skill to understand, and let me come slowly. For the convenience of research, the superscript ' is still used to distinguish the same elements.

Assuming the input is nums = [1,2,2'] , the standard full permutation algorithm will give the following answer:

[
    [1,2,2'],[1,2',2],
    [2,1,2'],[2,2',1],
    [2',1,2],[2',2,1]
]

Obviously, there are duplicates in this result, for example [1,2,2'] and [1,2',2] should only be counted as the same permutation, but are counted as two different permutations.

So the key now is, how to design the pruning logic to remove this duplication?

answer is that the relative position of the same element in the arrangement is guaranteed to remain the same .

For example, in the example of nums = [1,2,2'] , I keep 2' in front of 2 in the arrangement.

In this case, you can only pick 3 permutations from the above 6 permutations that meet this condition:

[ [1,2,2'],[2,1,2'],[2,2',1] ]

This is the correct answer.

Further, if it is nums = [1,2,2',2''] , I only need to ensure that the relative position of the repeated element 2 is fixed, such as 2 -> 2' -> 2'' , and I can also get a full arrangement result without repetition.

Thinking about it carefully, it should be easy to understand the principle:

The reason why the standard full permutation algorithm is repeated is that the permutation sequences formed by the same elements are regarded as different sequences, but in fact they should be the same; and if the sequence order formed by the same elements is fixed, of course, repetition is avoided. .

Then reflected on the code, you should pay attention to this pruning logic:

// 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
    // 如果前面的相邻相等元素没有用过,则跳过
    continue;
}
// 选择 nums[i]

When there are duplicate elements, such as entering nums = [1,2,2',2''] , 2' will only be selected if 2 has been used, and 2'' will only be selected if 2' has been used, which ensures that the same element is in the arrangement. The relative position is guaranteed to be fixed .

Well, the permutation problem involving repeated input is also solved.

Subset/Combination (elements without duplicates can be checked)

Finally the last type: the input array has no repeating elements, but each element can be used an infinite number of times.

Look directly at the 39th question "Combination Sum":

Given a non-repeating integer array candidates and a target sum target , find all combinations in candidates that make the sum of the numbers the target number target . Each number in candidates can be chosen in unlimited repetitions.

The function signature is as follows:

List<List<Integer>> combinationSum(int[] candidates, int target)

For example, if you enter candidates = [1,2,3], target = 3 , the algorithm should return:

[ [1,1,1],[1,2],[3] ]

This question is said to be a combination problem, but it is actually a subset problem: Which subsets of candidates sum to target ?

To solve this type of problem, we have to go back to the backtracking tree, , we might as well think about it first, how does the standard subset/combination problem ensure that the element is not reused?

The answer lies in the parameters entered when backtrack recursively:

// 回溯算法标准框架
for (int i = start; i < nums.length; i++) {
    // ...
    // 递归遍历下一层回溯树,注意参数
    backtrack(nums, i + 1, target);
    // ...
}

This i starts from start , then the next level of backtracking tree starts from start + 1 , thus ensuring that the element nums[start] will not be reused:

Then in turn, if I want each element to be reused, I just change i + 1 to i :

// 回溯算法标准框架
for (int i = start; i < nums.length; i++) {
    // ...
    // 递归遍历下一层回溯树
    backtrack(nums, i, target);
    // ...
}

This is equivalent to adding a branch to the previous backtracking tree, and an element can be used an infinite number of times during the traversal of the tree:

Of course, this backtracking tree will grow forever, so our recursive function needs to set the appropriate base case to end the algorithm, that is, when the path sum is greater than target , there is no need to traverse any further.

The solution code for this problem is as follows:

List<List<Integer>> res = new LinkedList<>();
// 记录回溯的路径
LinkedList<Integer> track = new LinkedList<>();
// 记录 track 中的路径和
int trackSum = 0;

public List<List<Integer>> combinationSum(int[] candidates, int target) {
    if (candidates.length == 0) {
        return res;
    }
    backtrack(candidates, 0, target);
    return res;
}

// 回溯算法主函数
void backtrack(int[] nums, int start, int target) {
    // base case,找到目标和,记录结果
    if (trackSum == target) {
        res.add(new LinkedList<>(track));
        return;
    }
    // base case,超过目标和,停止向下遍历
    if (trackSum > target) {
        return;
    }

    // 回溯算法标准框架
    for (int i = start; i < nums.length; i++) {
        // 选择 nums[i]
        trackSum += nums[i];
        track.add(nums[i]);
        // 递归遍历下一层回溯树
        // 同一元素可重复使用,注意参数
        backtrack(nums, i, target);
        // 撤销选择 nums[i]
        trackSum -= nums[i];
        track.removeLast();
    }
}

Arrangement (elements without repetition can be checked)

There is no similar topic on the force button. Let's think about it first. What arrangements will there be when the elements in the nums array are non-repetitive and can be checked?

For example, if you enter nums = [1,2,3] , then there are 3^3 = 27 full permutations under this condition:

[
  [1,1,1],[1,1,2],[1,1,3],[1,2,1],[1,2,2],[1,2,3],[1,3,1],[1,3,2],[1,3,3],
  [2,1,1],[2,1,2],[2,1,3],[2,2,1],[2,2,2],[2,2,3],[2,3,1],[2,3,2],[2,3,3],
  [3,1,1],[3,1,2],[3,1,3],[3,2,1],[3,2,2],[3,2,3],[3,3,1],[3,3,2],[3,3,3]
]

standard full permutation algorithm uses the used array for pruning to avoid reusing the same element. If it is allowed to reuse elements, just let go of yourself and remove the pruning logic of all used arrays .

Then the problem is simple, the code is as follows:

List<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> track = new LinkedList<>();

public List<List<Integer>> permuteRepeat(int[] nums) {
    backtrack(nums);
    return res;
}

// 回溯算法核心函数
void backtrack(int[] nums) {
    // base case,到达叶子节点
    if (track.size() == nums.length) {
        // 收集叶子节点上的值
        res.add(new LinkedList(track));
        return;
    }

    // 回溯算法标准框架
    for (int i = 0; i < nums.length; i++) {
        // 做选择
        track.add(nums[i]);
        // 进入下一层回溯树
        backtrack(nums);
        // 取消选择
        track.removeLast();
    }
}

So far, the nine variations of the permutation/combination/subsetting problem are all finished.

final summary

Let's review the code differences between the three forms of the permutation/combination/subsetting problem.

Since the subset problem and the combination problem are essentially the same, it is just that there are some differences in the base case, so let's look at these two problems together.

Form 1. Elements are not duplicated and cannot be selected, that is, the elements in nums are unique, and each element can only be used once at most , backtrack core code is as follows:

/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
    // 回溯算法标准框架
    for (int i = start; i < nums.length; i++) {
        // 做选择
        track.addLast(nums[i]);
        // 注意参数
        backtrack(nums, i + 1);
        // 撤销选择
        track.removeLast();
    }
}

/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        // 剪枝逻辑
        if (used[i]) {
            continue;
        }
        // 做选择
        used[i] = true;
        track.addLast(nums[i]);

        backtrack(nums);
        // 取消选择
        track.removeLast();
        used[i] = false;
    }
}

Form 2. Elements can be repeated but not re-selectable, that is, elements in nums can be repeated, and each element can only be used once at most . The key lies in sorting and pruning. The core code of backtrack is as follows:

Arrays.sort(nums);
/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
    // 回溯算法标准框架
    for (int i = start; i < nums.length; i++) {
        // 剪枝逻辑,跳过值相同的相邻树枝
        if (i > start && nums[i] == nums[i - 1]) {
            continue;
        }
        // 做选择
        track.addLast(nums[i]);
        // 注意参数
        backtrack(nums, i + 1);
        // 撤销选择
        track.removeLast();
    }
}


Arrays.sort(nums);
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        // 剪枝逻辑
        if (used[i]) {
            continue;
        }
        // 剪枝逻辑,固定相同的元素在排列中的相对位置
        if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
            continue;
        }
        // 做选择
        used[i] = true;
        track.addLast(nums[i]);

        backtrack(nums);
        // 取消选择
        track.removeLast();
        used[i] = false;
    }
}

Form 3. Elements can be checked without duplication, that is, the elements in nums are unique, and each element can be used several times , just delete the backtrack logic.

/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
    // 回溯算法标准框架
    for (int i = start; i < nums.length; i++) {
        // 做选择
        track.addLast(nums[i]);
        // 注意参数
        backtrack(nums, i);
        // 撤销选择
        track.removeLast();
    }
}


/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        // 做选择
        track.addLast(nums[i]);

        backtrack(nums);
        // 取消选择
        track.removeLast();
    }
}

As long as you think from the perspective of the tree, these problems seem complicated and changeable, but you can solve them by changing the base case, which is why I am in Learning Algorithms and Data Structure Framework Thinking and Brushing the Binary Tree (Programme) Emphasize the reasons for the importance of tree-type topics.

If you can see this, I really have to applaud you. I believe that if you encounter all kinds of messy algorithm problems in the future, you can also see their essence at a glance, and adapt to changes without change.

_____________

Click on my avatar see more high-quality algorithm articles, and I will take you by the hand to make the algorithm clear! My algorithm tutorial has won 100k stars, welcome to like it!

labuladong
63 声望37 粉丝