代码随想录算法训练营第二十九天 | 491. 递增子序列、46. 全排列、47. 全排列 II

491. 递增子序列

视频讲解

主要思路:

因为数据范围比较小,所以可以开一个数组,每轮递归利用这个数组记录元素是否使用过,如果使用过,就可以像40. 组合总和 II这题一样剪枝减掉,因为重复的话前面肯定就会遍历过所有可能了,但本题一个陷阱就是不能先对原数组排序,所以如果发现循环遍历的元素小于最后压入path的元素,就说明不能构成递增,就continue

易错点:

(1)如何处理重复元素:如果想用数组下标记录元素,一定要+100,否则下标无法表示负数

(2)如何保证path里递增

代码实现:

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    
    void backtracking(vector<int>& nums, int startIndex) {
        if (path.size() >= 2) { //递归终止条件
            result.push_back(path);
        }
        int used[201] = {0};  // 这里使用数组来进行去重操作,题目说数值范围[-100, 100]
        for (int i = startIndex; i < nums.size(); i++) {
            //将nums[i]与最后压入path的比较实现path内递增,妙啊
            if ((!path.empty() && nums[i] < path.back())    
                    || used[nums[i] + 100] == 1) {  //咳咳,这里其实是ACM常见技巧,num[i] + 100相当于移动范围,从-100~100到0~200,由哈希表一一映射可知结果不变
                    continue;
            }
            used[nums[i] + 100] = 1; // 记录这个元素在本层用过了,本层后面不能再用了
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums, 0);
        return result;
    }
};

46. 全排列

视频讲解

主要思路:

上一题其实就相当于这一题的预热,也要用一个数组记录当前遍历的元素是否使用过,不同的是该数组设在全局变量里,且要有相应回溯

代码实现:

class Solution {
public:
    vector<vector<int>>result;
    vector<int>path;
    int used[21];
    void backtracking(vector<int>& nums) {
        if(path.size() == nums.size()) {
            result.push_back(path);
            return;
        }

        for(int i = 0; i < nums.size(); i++) {
            int tmp = nums[i] + 10;
            if(used[tmp] == 1) continue;
            else used[tmp] = 1;
            path.push_back(nums[i]);
            backtracking(nums);
            used[tmp] = 0;
            path.pop_back();
        }
        return;
    }
    vector<vector<int>> permute(vector<int>& nums) {
        backtracking(nums);
        return result;
    }
};

第一次写错误 

(1)忘记有负数平移范围了

47. 全排列 II

视频讲解

主要思路:

因为有重复元素,所以不仅有树枝去重,还有树层去重

代码实现:

class Solution {
public:
    vector<vector<int>>result;
    vector<int>path;
    int branchUsed[10];
    void backtracking(vector<int>& nums) {
        if(path.size() == nums.size()) {
            result.push_back(path);
            return;
        }

        int layerUsed[21] = {0};
        for(int i = 0; i < nums.size(); i++) {
            int tmp = nums[i] + 10;
            if(layerUsed[tmp] == 1 || branchUsed[i] == 1) continue;  //先树层去重,在树枝去重
            branchUsed[i] = 1;
            layerUsed[tmp] = 1;
            path.push_back(nums[i]);
            backtracking(nums);
            branchUsed[i] = 0;
            path.pop_back();
        }
        return;
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        backtracking(nums);
        return result;
    }
};

第一次写错误 

树枝去重与树层去重都continue就行