Algorithm -- backtracking + pruning

Posted by wescrock on Thu, 20 Jan 2022 12:22:56 +0100

After a day of backtracking algorithm, now to sum up, the basic problem-solving ideas still have a certain routine.

Backtracking algorithm

Using the idea of trial and error, it tries to solve a problem step by step. In the process of solving the problem step by step, when it finds that the existing step-by-step answers can not get effective and correct answers, it will cancel the calculation of the previous step or even the previous steps, and then try to find the answer again through other possible step-by-step answers. Backtracking is usually implemented by the simplest recursive method. After repeating the above steps repeatedly, there may be two situations:

Find a correct answer that may exist;
After trying all possible step-by-step methods, declare that there is no answer to the question.

Backtracking algorithm is essentially a recursive algorithm, which is basically used to solve the traversal and enumeration search of a certain set. It can be pruned according to some rules in a pre sorted way to reduce the search path. Generally, the search of a tree starts from the root node and starts from the first child node that meets the conditions, and then continues to search with the current child node as the root node. If the solution that meets the meaning of the problem is found, it will be recorded. If it is not found, it will return to the parent node of the previous level from the current child node and search from the second feasible child node of the parent node. Therefore, the most important key points to solve the backtracking algorithm are:

1. Draw the whole search tree, what is the root node and what are the optional child nodes of each node.

2. Search downward from the root node. If the conditions are met, record the result set. If they are not met, continue the search. If they are not met, go back to the previous node from this node to carry out the process. Therefore, it should be clear what conditions the search process can stop. One is to find the solution that meets the meaning of the problem, and the other is to traverse to the critical conditions.

3. Whether to process the set to be searched in advance, such as sorting, etc.

4. Whether it is necessary to record the status of each element of the set to be searched to avoid repeated search, etc.

1. Subset

https://leetcode-cn.com/problems/subsets/

Give you an integer array nums. The elements in the array are different from each other. Returns all possible subsets (power sets) of the array.

The solution set {cannot contain duplicate subsets. You can {return the solution set in any order.

Take [1,2,3] as an example, the result is [] [1] [1,2] [1,2,3] [1,3] [2] [2,3] [3]

The whole search tree starts from the root node (empty). Consider that the optional child nodes of the root node are 1,2,3, and then first consider that the optional child nodes of child node 1 are 2,3, and then consider that the optional child nodes of 2 are 3, and the optional child nodes of 3 are empty. Then find that the subset is empty. 1,1,2,1,2,3. At this time, go back to the empty place of the root node and start the search from the second child node 2,...

/**
     * Give you an integer array nums. The elements in the array are different from each other. Returns all possible subsets (power sets) of the array.
     * The solution set cannot contain duplicate subsets. You can return the solution set in any order.
     * */
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        if (nums.length == 0) {
            return result;
        }
        // First, add an empty set
        result.add(new ArrayList<>());
        backTrace(result, new ArrayList<>(), nums, 0);
        return result;
    }

    private void backTrace(List<List<Integer>> result, List<Integer> path, int[] nums, int index) {
        // If the search position = array length, it is returned directly
        if (index == nums.length) return;
        // Add the result set from the index position
        for (int i=index; i<nums.length; i++) {
            path.add(nums[i]);
            result.add(new ArrayList<>(path));
            // Try to add the next location number after adding the current location
            backTrace(result, path, nums, i+1);
            // Backtrack the current position and cancel the attempt to index+1 position
            path.remove(path.size()-1);
        }
    }

Index is used to mark the position of the current search array. The condition for stopping the search is index = = num length

Starting from the node index, the range of optional child nodes is [index + 1, num.length], because it is a subset. When the current element has been processed, subsequent processing cannot be continued.

2. Subset II

https://leetcode-cn.com/problems/subsets-ii/

Give you an integer array nums, which may contain duplicate elements. Please return all possible subsets (power sets) of the array.

The solution set cannot contain duplicate subsets. Subsets of the returned solution set can be arranged in any order.

In this topic, because the elements are repeated, the repetition of subsets should be considered.

The idea to solve the duplication problem is to sort the set first, and establish a state variable to record whether each element has been searched. In the search process, we should consider whether the current node and the previous node are directly equal and whether there will be a duplicate solution.

Take subset [1,2,2] as an example. There are 1,2,2 optional child nodes starting from the root node. There are 2 optional child nodes starting from 1. Because the two child nodes are repeated, when the first child node 2 is processed, the latter one does not need to be processed. The condition is that after the first child node is processed, the next child node is the same as the first child node, and the next child node can be skipped directly.

 /**
     * Give you an integer array nums, which may contain duplicate elements. Please return all possible subsets (power sets) of the array.
     * The solution set cannot contain duplicate subsets. Subsets of the returned solution set can be arranged in any order.
     * */
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        if (nums.length == 0) {
            return result;
        }
        // First, add an empty set
        result.add(new ArrayList<>());
        Arrays.sort(nums);
        boolean[] used = new boolean[nums.length];
        backTrace2(result, new ArrayList<>(), nums, 0, used);
        return result;
    }

    private void backTrace2(List<List<Integer>> result, List<Integer> path, int[] nums, int index, boolean[] used) {
        if (index == nums.length) return;
        for (int i=index; i<nums.length; i++) {
            // If the current element is equal to the previous element and the previous element has not been used (actually used and recovered by backtracking), the element can be skipped directly
            if (i>0 && nums[i]==nums[i-1] && !used[i-1]) continue;
            // If the current element has not been used
            if (!used[i]) {
                path.add(nums[i]);
                used[i] = true;
                result.add(new ArrayList<>(path));
                backTrace2(result, path, nums, i+1, used);
                path.remove(path.size()-1);
                used[i] = false;
            }
        }
    }

index is used to indicate the location of the search array, and used array is used to indicate whether the element has been searched. The above repeated skip condition is that after the current child node is processed, the next child node is the same as the current child node, and the next child node can be skipped directly

if (i>0 && nums[i]==nums[i-1] && !used[i-1]) continue;

After the first child node is processed, its used=false due to backtracking; If the previous element used=true, the current node cannot be skipped. The situation is 1,2,2. That is, the previous result is being used, and the current node can also be added to the result set.

3. Full arrangement

https://leetcode-cn.com/problems/permutations/

Given an array of  nums without duplicate numbers, all possible permutations of  are returned. You can} return answers in any order

Given 1,2,3

Starting from the root node, the optional child nodes are 1,2,3

Considering node 1, its optional child nodes are 2,3, considering node 2, its optional child nodes are 3, considering 3, there is no optional child node, then a feasible solution is found.

Consider node 2. Its optional child nodes are 1,3...

Consider node 3, and its optional child nodes are 1,2...

The search stop condition is index = = num length

For the index location, the optional child node is [0, num.length], and there is no node on the previous search path. The used array is used to record the nodes used in the search.

/**
     * Given an array num without duplicate numbers, returns all possible permutations. You can return answers in any order.
     * */
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        if (nums.length == 0) {
            return result;
        }
        boolean[] used = new boolean[nums.length];
        backTrace(result, new ArrayList<>(), 0, nums, used);
        return result;
    }

    private void backTrace(List<List<Integer>> result, List<Integer> path, int index, int[] nums, boolean[] used) {
        if (index == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }
        // For the location index number, try to explore from 0
        for (int i=0; i<nums.length; i++) {
            // If the number has not been used, skip if it has been used
            if (!used[i]) {
                path.add(nums[i]);
                used[i] = true;
                // Continue to look for the number at index+1
                backTrace(result, path, index+1, nums, used);
                path.remove(path.size() - 1);
                used[i] = false;
            }
        }
    }

For the location index, try to search the desired possibility from 0, but exclude the currently used elements

4. Full arrangement II

https://leetcode-cn.com/problems/permutations-ii/

Given a sequence of {nums that can contain repeated numbers,} returns all non repeated full permutations in any order.

Remove duplicates  sort the array  if the current element = the previous element and the previous element is already under the current search path, the current element can be skipped

 /**
     * Given a sequence nums that can contain repeated numbers, return all non repeated full permutations in any order.
     * */
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        if (nums.length == 0) {
            return result;
        }
        Arrays.sort(nums);
        boolean[] used = new boolean[nums.length];
        backTrace2(result, new ArrayList<>(), 0, nums, used);
        return result;
    }

    private void backTrace2(List<List<Integer>> result, List<Integer> path, int index, int[] nums, boolean[] used) {
        if (index == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i=0; i<nums.length; i++) {
            // Because the array is ordered, if the current element and the previous element are the same and the previous element has not been used (actually used and recovered by backtracking), skip
            if (i > 0 && nums[i] == nums[i-1] && !used[i-1]) continue;
            if (!used[i]) {
                path.add(nums[i]);
                used[i] = true;
                backTrace2(result, path, index+1, nums, used);
                path.remove(path.size()-1);
                used[i] = false;
            }
        }
    }

5. Combined summation

https://leetcode-cn.com/problems/combination-sum/

Given an array of non repeating elements , candidates , and a target number , target, find all combinations in , candidates , that can make the sum of numbers , target. The number in candidates , can be selected repeatedly without limit.

Starting from the root node, the optional child nodes are the complete set of the array, and traverse each location in turn. index records the sum of the search paths.

For the current position index, its optional child node is [index, num.length], because numbers can be reused.

The search stop condition is index = = num Length or sum(path)=target

If the array is sorted in advance, if the current element is greater than the target, the search can be stopped in advance, because all inputs are positive, the subsequent sum(path) will be greater than the target, and you can only backtrack and try other solutions.

 /**
     * Given an array with no duplicate elements, {candidates} and a target number, {target,
     * Find all combinations in , candidates , that can make the sum of numbers , target.
     *
     * candidates The numbers in can be selected repeatedly without limit.
     * */
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(candidates);
        backTrack(result, new ArrayList<>(), candidates, target, 0);
        return result;
    }

    private void backTrack(List<List<Integer>> result, List<Integer> path, int[] candidates,
            int target, int index) {
        // If before the current path = the target value, put the path into the result set
        if (target == 0) {
            // Here's an assignment
            result.add(new ArrayList<>(path));
            return;
        }
        // Try from the index position if
        for (int i=index; i<candidates.length; i++) {
            // Because the array is ordered, stop searching if the current number is greater than target
            if (candidates[i] > target) break;
            // Add current number to path
            path.add(candidates[i]);
            // Since the array can be reused, try the next number from i
            backTrack(result, path, candidates, target-candidates[i], i);
            // If the current number is not satisfied, go back to the state of not adding the number and continue to try the next number
            path.remove(path.size()-1);
        }
    }

6. Combined summation II

https://leetcode-cn.com/problems/combination-sum-ii/

Given an array of , candidates , and a target number , target, find all combinations in , candidates , that can make the sum of numbers , target.

Each number in candidates , can only be used once in each combination.

 /**
     * Given an array , candidates , and a target number , target,
     * Find all combinations in , candidates , that can make the sum of numbers , target.
     * candidates Each number in can only be used once in each combination.
     *
     * */
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        if (candidates.length == 0) {
            return result;
        }
        Arrays.sort(candidates);
        backTrace2(result, new ArrayList<>(), target, 0, candidates);
        return result;
    }

    private void backTrace2(List<List<Integer>> result, List<Integer> path, int target, int index, int[] candidates) {
        if (target == 0) {
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i=index; i<candidates.length; i++) {
            if (candidates[i] > target) break;
            // After avoiding repeated sorting, skip if the current element is the same as the previous one
            if(i > index && candidates[i] == candidates[i-1]) continue;
            path.add(candidates[i]);
            backTrace2(result, path, target-candidates[i], i+1, candidates);
            path.remove(path.size() - 1);
        }
    }

Because the numbers in the array may be repeated, the array can be sorted. In the search process, if the current element is equal to the previous element, it can be skipped directly;

Since the number in each array can only be used once, the optional child node set of position index is [index + 1, num.length]

7. Valid brackets

https://leetcode-cn.com/problems/generate-parentheses/

The number n , represents the logarithm of the generated parentheses. Please design a function to generate all possible and effective parenthesis combinations.

Example 1:

Input: n = 3
Output: ["(())", "(())", "(()) ()", "() () ()", "() () ()"]
Example 2:

Input: n = 1
Output: ["()"]

One of the conditions for an efficient solution is the length of the bracket string = n*2

The final valid string must have number of left parentheses = number of right parentheses = n

Starting from a node, if the number of left parentheses is less than N, the left parentheses will be placed first. If the number of left parentheses is greater than the right parentheses, the right parentheses will be placed

/**
     * The number n represents the logarithm of the generated bracket. Please design a function to generate all possible and effective bracket combinations
     *
     * */
    public List<String> generateParenthesis(int n) {
        List<String> result = new ArrayList<>();
        backTrack(result, new StringBuilder(), 0, 0, n);
        return result;
    }

    private void backTrack(List<String> result, StringBuilder sb, int open, int close, int max) {
        // If the match is successful, the length of the result must be max*2
        if (sb.length() == max * 2) {
            result.add(sb.toString());
        }
        // If the number of left parentheses is less than max, the left parentheses are placed first
        if (open < max) {
            sb.append("(");
            backTrack(result, sb, open+1, close, max);
            sb.deleteCharAt(sb.length()-1);
        }
        // Here must be the number of left parentheses > = max. at this time, the right parenthesis should be placed to ensure the balance of the string
        if (open > close) {
            sb.append(")");
            backTrack(result, sb, open, close+1, max);
            sb.deleteCharAt(sb.length()-1);
        }
    }

8. Possible phone numbers

https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/

Given a string containing only the numbers , 2-9 , returns all the letter combinations it can represent. The answers can be returned in any order.

The number to letter mapping is given as follows (the same as the telephone key). Note that 1 does not correspond to any letter.

Given a string 234, use index to record the current position

The search stop condition is index = num length

Starting from the following node, the optional child node is 2, and the optional value is abc. The optional child node of 2 is 3 and 4. The optional child node of 3 is 4

/**
     * Given a string containing only the numbers , 2-9 , returns all the letter combinations it can represent. The answers can be returned in any order.
     *
     * The number to letter mapping is given as follows (the same as the telephone key). Note that 1 does not correspond to any letter.
     * */
    public List<String> letterCombinations(String digits) {
        List<String> result = new ArrayList<>();
        if (digits.length() == 0) {
            return result;
        }
        Map<Character, List<Character>> map = new HashMap<>();
        map.put('2', Arrays.asList('a','b','c'));
        map.put('3', Arrays.asList('d','e','f'));
        map.put('4', Arrays.asList('g','h','i'));
        map.put('5', Arrays.asList('j','k','l'));
        map.put('6', Arrays.asList('m','n','o'));
        map.put('7', Arrays.asList('p','q','r','s'));
        map.put('8', Arrays.asList('t','u','v'));
        map.put('9', Arrays.asList('w','x','y','z'));
        backTrack(digits, 0, map, result, new StringBuilder());
        return result;
    }

    private void backTrack(String str, int index, Map<Character, List<Character>> map, List<String> result, StringBuilder sb) {
        // If there are no characters after it, the result is directly added to the result set
        if (index == str.length()) {
            result.add(sb.toString());
            return;
        }
        // Gets the character array represented by the current character
        char ch = str.charAt(index);
        List<Character> list = map.get(ch);
        for (char c: list) {
            // Add this character to
            sb.append(c);
            backTrack(str, index+1, map, result, sb);
            // Fallback this character
            sb.deleteCharAt(sb.length()-1);
        }
    }

Summary of ideas

Use path to record the search path. If path is a feasible solution, add it to the result

Enter the set to be searched nums # and the search location index, and analyze the search stop condition as index = = nums length

Starting from a node, consider the list of feasible child nodes. If you need to remove duplication, consider sorting the set nums first, and then use the used array to record whether each element in the search path has been pruned.

For the current position index, there are n optional positions , starting from the child node 0 , adding the child node 0 to the path , continuing to recurse the optional child nodes of the current node 0, and then removing the current child node 0 from the path; Starting from child node 1.... Until the child node n.

Other classic topics:

https://leetcode-cn.com/problems/flood-fill/

 https://leetcode-cn.com/problems/number-of-islands/

https://leetcode-cn.com/problems/surrounded-regions/

https://leetcode-cn.com/problems/word-search/

The difference is that for the range of optional child nodes of a node, the above questions are one-dimensional sequential search of the array. These are optional exploration in the up, down, left, right and left directions of the two-dimensional array, which has been in essence.

Reference address:

https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/

Topics: Algorithm