Code random recording training camp day29| 491. Incremental subsequence 46. Full permutation 47. Full permutation II

@TOC


foreword

Code Random Record Algorithm Training Camp day29


1. Leetcode 491. Incrementing subsequence

1. Topic

Given an array of integers nums, find and return all distinct increasing subsequences in that array that have at least two elements. You can return answers in any order.

An array may contain repeated elements, such as two integers being equal, can also be regarded as a special case of an increasing sequence.

Example 1:

Input: nums = [4,6,7,7] Output: [[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7] ,7],[6,7],[6,7,7],[7,7]]

Example 2:

Input: nums = [4,4,3,2,1] Output: [[4,4]]

hint:

1 <= nums.length <= 15
-100 <= nums[i] <= 100

Source: LeetCode Link: https://leetcode.cn/problems/non-decreasing-subsequences

2. Problem-solving ideas

Method 1: Binary enumeration + hash

Ideas and Algorithms

We can take the simplest idea, that is, enumerate all subsequences, and then judge whether the current subsequence is non-strictly increasing. So what method can we use to enumerate all subsequences? We need to select some numbers from the original sequence to form a new sequence, which is a subsequence of the original sequence. For each number in the original sequence, there are two possible states, namely selected or not selected. If we use 11 to represent being selected and 00 to represent not being selected, assuming a sequence length is 33, then the selected subsequence corresponds to the following eight states:

[0,0,0][0,0,0]
[0,0,1][0,0,1]
[0,1,0][0,1,0]
[0,1,1][0,1,1]
[1,0,0][1,0,0]
[1,0,1][1,0,1]
[1,1,0][1,1,0]
[1,1,1][1,1,1]

It can be seen that there are 2n2n situations in the sequence selector sequence with a length of nn, and these 2n2n situations are the binary representations of all integers in the interval [0, 2n−1][0, 2n−1]. We can enumerate each number in the interval [0, 2n−1][0, 2n−1], and then split it into binary digits, we will get a 0/10/1 sequence, and then we can construct this The subsequence corresponding to the 0/10/1 sequence, and then check whether this sequence is non-strictly increasing.

Of course, we also need to solve the problem of subsequence deduplication. For sequence deduplication, we can use the string hash algorithm (that is, Rabin-Karp encoding, students who do not understand here can refer to "Official Solution - 1392. The longest happy prefix"), that is, for a sequence a0,a1,⋯,an −1a0​,a1​,⋯,an−1​, we can think of it as a max⁡{ai}+1max{ai​}+1 base number, the value of this number is equal to (record b=max⁡{ai }+1b=max{ai​}+1):

f(a⃗)=∑i=0n−1bi×aif(a

)=i=0∑n−1​bi×ai​

Every time we find a legal sequence, we calculate the hash value of the sequence, and use a hash table to record the existing hash value. If the value already appears in the hash table, discard the sequence. Otherwise add the sequence to the answer.

In the process of implementation, we found that this hash value may be very large, we can modulo a large prime number PP, and map this value to the range of intint. So actually the hash function here is:

f(a⃗)=∑i=0n−1bi×ai(modP)f(a

)=i=0∑n−1​bi×ai​(modP)

And the bb here is not necessarily max⁡{ai}+1max{ai​}+1, it can arbitrarily choose a number greater than max⁡{ai}+1max{ai​}+1.

3. Code implementation

```java class Solution { List temp = new ArrayList(); List> ans = new ArrayList>(); Set set = new HashSet(); int n;

public List<List<Integer>> findSubsequences(int[] nums) {
    n = nums.length;
    for (int i = 0; i < (1 << n); ++i) {
        findSubsequences(i, nums);
        int hashValue = getHash(263, (int) 1E9 + 7);
        if (check() && !set.contains(hashValue)) {
            ans.add(new ArrayList<Integer>(temp));
            set.add(hashValue);
        }
    }
    return ans;
}

public void findSubsequences(int mask, int[] nums) {
    temp.clear();
    for (int i = 0; i < n; ++i) {
        if ((mask & 1) != 0) {
            temp.add(nums[i]);
        }
        mask >>= 1;
    }
}

public int getHash(int base, int mod) {
    int hashValue = 0;
    for (int x : temp) {
        hashValue = hashValue * base % mod + (x + 101);
        hashValue %= mod;
    }
    return hashValue;
}

public boolean check() {
    for (int i = 1; i < temp.size(); ++i) {
        if (temp.get(i) < temp.get(i - 1)) {
            return false;
        }
    }
    return temp.size() >= 2;
}

}

```

2. Leetcode 46. Full Arrangement

1. Topic

Given an array nums without repeated numbers, return all possible full permutations of it. You can return answers in any order.

Example 1:

Input: nums = [1,2,3] Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1 ,2],[3,2,1]]

Example 2:

Input: nums = [0,1] Output: [[0,1],[1,0]]

Example 3:

Input: nums = [1] Output: [[1]]

hint:

1 <= nums.length <= 6
-10 <= nums[i] <= 10
nums 中的所有整数 互不相同

Source: LeetCode Link: https://leetcode.cn/problems/permutations

2. Problem-solving ideas

Method 1: Backtracking

ideas and algorithms

This problem can be regarded as having nn spaces arranged in a row. We need to fill in the nn numbers given in the question from left to right, and each number can only be used once. Then it is very straightforward to think of an exhaustive algorithm, that is, try to fill in a number for each position from left to right, and see if you can fill in the nn blanks. In the program, we can use the "backtracking method" to simulate this process.

We define the recursive function backtrack(first,output) backtrack(first,output) means filling from left to right to the firstfirst position, and the current arrangement is outputoutput. Then the whole recursive function is divided into two cases:

如果 first=nfirst=n,说明我们已经填完了 nn 个位置(注意下标从 00 开始),找到了一个可行的解,我们将 outputoutput 放入答案数组中,递归结束。
如果 first<nfirst<n,我们要考虑这第 firstfirst 个位置我们要填哪个数。根据题目要求我们肯定不能填已经填过的数,因此很容易想到的一个处理手段是我们定义一个标记数组 visvis 来标记已经填过的数,那么在填第 firstfirst 个数的时候我们遍历题目给定的 nn 个数,如果这个数没有被标记过,我们就尝试填入,并将其标记,继续尝试填下一个位置,即调用函数 backtrack(first+1,output)backtrack(first+1,output)。回溯的时候要撤销这一个位置填的数以及标记,并继续尝试其他没被标记过的数。

It is a very intuitive idea to use a marked array to process the filled numbers, but can this marked array be removed? After all, tagged arrays also increase the space complexity of our algorithm.

The answer is yes, we can divide the array numsnums of nn numbers given by the question into two parts: the left part represents the number that has been filled, and the right represents the number to be filled. We only need to dynamically maintain this when backtracking. Arrays will do.

Specifically, assuming we have already filled in the firstfirst position, then [0,first−1][0,first−1] in the numsnums array is the set of filled numbers, [first,n−1][first ,n−1] is the set of numbers to be filled. We must try to use the number in [first,n−1][first,n−1] to fill the firstfirst number, assuming that the subscript of the number to be filled is ii, then we will fill in the ii number Exchange with the firstfirst number, which can make the [0,first][0,first] part of the numsnums array be the filled number when filling the first+1first+1th number, [first+1,n− 1][first+1,n−1] is the number to be filled, and the undo operation can be completed by swapping back when backtracking.

To give a simple example, suppose we have [2,5,8,9,10][2,5,8,9,10] 55 numbers to be filled in, and the 33rd position has been filled in. [8,9][8,9] Two numbers, then this array is currently in the state of [8,9 ∣ 2,5,10][8,9 ∣ 2,5,10], the separator distinguishes the left and right two parts. Assuming that we want to fill in the number 1010 in this position, in order to maintain the array, we exchange 22 and 1010, that is, the array can continue to keep the number on the left of the separator has been filled, and the number on the right is to be filled [8,9,10 ∣ 2,5 ][8,9,10 ∣ 2,5] .

Of course, readers who are good at thinking must have discovered that the full array generated in this way is not stored in the answer array in lexicographical order. If the title requires output in lexicographical order, then please use the marked array or other methods.

3. Code implementation

```java class Solution { public List> permute(int[] nums) { List> res = new ArrayList>();

List<Integer> output = new ArrayList<Integer>();
    for (int num : nums) {
        output.add(num);
    }

    int n = nums.length;
    backtrack(n, output, res, 0);
    return res;
}

public void backtrack(int n, List<Integer> output, List<List<Integer>> res, int first) {
    // 所有数都填完了
    if (first == n) {
        res.add(new ArrayList<Integer>(output));
    }
    for (int i = first; i < n; i++) {
        // 动态维护数组
        Collections.swap(output, first, i);
        // 继续递归填下一个数
        backtrack(n, output, res, first + 1);
        // 撤销操作
        Collections.swap(output, first, i);
    }
}

}

```

3. Leetcode 47. Full Arrangement II

1. Topic

Given a sequence nums that may contain repeating numbers, return all non-repeating full permutations in any order.

Example 1:

Input: nums = [1,1,2] Output: [[1,1,2], [1,2,1], [2,1,1]]

Example 2:

Input: nums = [1,2,3] Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1 ,2],[3,2,1]]

hint:

1 <= nums.length <= 8
-10 <= nums[i] <= 10

Source: LeetCode Link: https://leetcode.cn/problems/permutations-ii

2. Problem-solving ideas

Method 1: Search Backtracking

ideas and algorithms

This question is an advanced step of "46. Full Permutation". The sequence contains repeated numbers, and we are required to return a non-repeated full permutation. Then we can still choose to use the method of search and backtracking.

We regard this question as having nn spaces arranged in a row, and we need to fill in the nn numbers given in the question from left to right, and each number can only be used once. Then it is very straightforward to think of an exhaustive algorithm, that is, try to fill in a number for each position from left to right, and see if you can fill in the nn blanks. In the program, we can use the "backtracking method" to simulate this process.

We define the recursive function backtrack(idx, perm) backtrack(idx, perm) to indicate that the current arrangement is permperm, and the next position to be filled is the idxidx position (the subscript starts from 00). Then the whole recursive function is divided into two cases:

如果 idx=nidx=n,说明我们已经填完了 nn 个位置,找到了一个可行的解,我们将 permperm 放入答案数组中,递归结束。
如果 idx<nidx<n,我们要考虑第 idxidx 个位置填哪个数。根据题目要求我们肯定不能填已经填过的数,因此很容易想到的一个处理手段是我们定义一个标记数组 visvis 来标记已经填过的数,那么在填第 idxidx 个数的时候我们遍历题目给定的 nn 个数,如果这个数没有被标记过,我们就尝试填入,并将其标记,继续尝试填下一个位置,即调用函数 backtrack(idx+1,perm)backtrack(idx+1,perm)。搜索回溯的时候要撤销该个位置填的数以及标记,并继续尝试其他没被标记过的数。

However, the solution to the question does not meet the requirement of "full arrangement without repetition". In the above recursive function, we will generate a large number of repeated arrangements, because for the position of idxidx, if there is a repeated number ii, we will use it every time Duplicate numbers are refilled and keep trying to result in a duplicate of the final answer, so we need to handle this case.

To solve the repetition problem, we only need to set a rule to ensure that the repeated number will only be filled once when filling in the idxidx number. In this problem solution, we choose to sort the original array to ensure that the same numbers are adjacent, and then the number filled in each time must be the number in the repeated number set where the number is located. "The first one from left to right that has not been filled The number of ", that is, the following judgment conditions:

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

This judgment condition guarantees that the set of repeated numbers must be filled one by one from left to right.

Suppose we have 33 repeated numbers that are sorted and adjacent to each other, then we must ensure that each time we take the first unfilled number from left to right, that is, the state of the entire array is actually guaranteed [not filled in , Not Filled, Not Filled] [Not Filled, Not Filled, Not Filled] to [Filled, Not Filled, Not Filled] [Filled, Not Filled, Not Filled], to [fill in, fill in, not fill in] [fill in, fill in, not fill in], finally to [fill in, fill in, fill in] [fill in, fill in, fill in] process, so The goal of deduplication can be achieved.

3. Code implementation

```java class Solution { boolean[] vis;

public List<List<Integer>> permuteUnique(int[] nums) {
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> perm = new ArrayList<Integer>();
    vis = new boolean[nums.length];
    Arrays.sort(nums);
    backtrack(nums, ans, 0, perm);
    return ans;
}

public void backtrack(int[] nums, List<List<Integer>> ans, int idx, List<Integer> perm) {
    if (idx == nums.length) {
        ans.add(new ArrayList<Integer>(perm));
        return;
    }
    for (int i = 0; i < nums.length; ++i) {
        if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) {
            continue;
        }
        perm.add(nums[i]);
        vis[i] = true;
        backtrack(nums, ans, idx + 1, perm);
        vis[i] = false;
        perm.remove(idx);
    }
}

}

```

Guess you like

Origin blog.csdn.net/HHX_01/article/details/131285409