算法学习Day29递增子序列、全排列

Day29递增子序列、全排列

By HQWQF 2024/01/16

笔记


491.递增子序列

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

  • 输入: [4, 6, 7, 7]
  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

说明:

  • 给定数组的长度不会超过15。
  • 数组中的整数范围是 [-100,100]。
  • 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况

回溯法+哈希表

捕捉到信息点:

1.元素不能复用

2.有重复元素。要去重

有了上一题的经验我们容易写出这样的代码:

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int> nums, int startIndex) {
        if(path.size() > 1){result.push_back(path);}
        
        if (path.size() == nums.size()) {
            return;
        }
        for (int i = startIndex; i < nums.size(); i++) {
            // 要对同一树层使用过的元素进行跳过
            if (i > startIndex && nums[i] == nums[i - 1]) {
                continue;
            }
            if(path.size() > 0 && nums[i] < path[path.size()-1]){continue;}
            path.push_back(nums[i]);
            backtracking(nums,i + 1); 
            path.pop_back();
        }
    }    
public:
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        path.clear();
        result.clear();
        // 首先把给nums排序,让其相同的元素都挨在一起。
        backtracking(nums, 0);
        return result;
    }
};

然而这样的代码是错误的,因为我们要得出递增子序列所以不能将源序列排序,而如果不排序,nums[i] == nums[i - 1]就无法跳过重复元素了。

我们可以所以哈希表来去重:

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        if (path.size() > 1) {
            result.push_back(path);
            // 注意这里不要加return,要取树上的节点
        }
        unordered_set<int> uset; // 使用set对本层元素进行去重
        for (int i = startIndex; i < nums.size(); i++) {
            if ((!path.empty() && nums[i] < path.back())
                    || uset.find(nums[i]) != uset.end()) {
                    continue;
            }
            uset.insert(nums[i]); // 记录这个元素在本层用过了,本层后面不能再用了
            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;
    }
};

另外可以发现题目数组中的整数范围是 [-100,100]。既然范围不大,我们可以直接使用数组来作为哈希表的底层:

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        if (path.size() > 1) {
            result.push_back(path);
            // 注意这里不要加return,要取树上的节点
        }
        int uset[201] = {0}; // 使用set对本层元素进行去重, [-100,100]有201个数
        for (int i = startIndex; i < nums.size(); i++) {
            if ((!path.empty() && nums[i] < path.back())
                    || uset[100 + nums[i]] != 0) {
                    continue;
            }
            uset[100 + nums[i]] = 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.全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入: nums = [1,2,3]输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入: nums = [0,1]输出:[[0,1],[1,0]]

回溯法

捕捉到两个信息点:

1.终止条件和加入解集时机是path达到一定长度

2.排列,意味着不同顺序的数组也视为不同

3.元素不能重复利用

4.不包含重复元素

我们回想一下之前的题目中startIndex的作用:

1.给下一层的startIndex赋值i,以让同层的其他节点的后续节点不会出现[不同顺序但组成元素相同]的path

2.在第一点的基础上给下一层的startIndex赋值i + 1,避免重复利用元素

我们发现[不同顺序但组成元素相同]这个特性与我们需要的全排列想矛盾,但是避免重复利用元素又是我们需要的说明我们不能使用startIndex而需要使用其他方法避免重复利用元素

我们可以回想到曾经在组合总和2中曾经用过useing数组来确定某个元素是否在当前的path中,我们也可以这样做,只要某个元素没出现在当前的path中,就是我们当前可以选择的元素。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking (vector<int>& nums, vector<bool>& used) {
        // 此时说明找到了一组
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            if (used[i] == true) continue; // path里已经收录的元素,直接跳过
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums, used);
            path.pop_back();
            used[i] = false;
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

47.全排列 II

给定一个可包含重复数字的序列 nums按任意顺序 返回所有不重复的全排列。

示例 1:

输入: nums = [1,1,2]

输出:

[[1,1,2],

[1,2,1],

[2,1,1]]

示例 2:

输入: nums = [1,2,3]输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

回溯法

和上一题的差别在包含重复元素,因此我们需要去重。

我们发现这题的源序列的顺序不影响结果,所以我们直接用基于排序的去重。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking (vector<int>& nums, vector<bool>& useing) {
        // 此时说明找到了一组
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            if (useing[i] == true) continue; // path里已经收录的元素,直接跳过
            if (i > 0 && nums[i] == nums[i - 1]&& useing[i - 1] == false) {
                continue;
            }
            useing[i] = true;
            path.push_back(nums[i]);
            backtracking(nums, useing);
            path.pop_back();
            useing[i] = false;
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool> useing(nums.size(), false);
        sort(nums.begin(), nums.end());
        backtracking(nums, useing);
        return result;
    }
};
posted @ 2024-01-16 19:12  HQWQF  阅读(45)  评论(0)    收藏  举报