首页 > 编程语言 >算法随想Day26【回溯算法】| LC491-递增子序列、LC46-全排列、LC47-全排序Ⅱ

算法随想Day26【回溯算法】| LC491-递增子序列、LC46-全排列、LC47-全排序Ⅱ

时间:2023-03-01 23:00:12浏览次数:39  
标签:temp nums Day26 back pop LC47 算法 vector result

跟“去重”相关的题目:

  • 三数之和
  • 组合之和Ⅱ
  • 子集Ⅱ
  • 递增子序列

在回溯算法题目中,去重问题分为“树层去重”和“树枝去重”

  • 之前组合之和、子集中的去重使用的方法都是先排序,使用prev_pop记录每次在temp中pop出的值,解决的是“树层去重”。因为题目要求的是在当次temp中,是允许有重复元素的(即树枝无需去重)。

LC491. 递增子序列

递增子序列,这道题很明显不能排序。因为对[4, 7, 6, 7]来说,排序后会出现[4, 6, 6, 7]这样的数组,不符合题意。

关于去重的问题,在第一次编码时,就想到了:

  • 如果是1、1、2、2、2、5,去重方法跟之前的“子集Ⅱ”一致,先排序,记录pop出值
  • 如果是4、6、7、6、7,开始的想法不太正确,看了Carl的视频后,就通透了。访问过第一个6和7后,凡是之后遇到重复的元素,都直接跳过(树层去重),因为在第一个6的递归搜索中一定会把第二个6后面的元素全部访问一遍了,所以到了第二个6的时候,不需要再劳烦访问后面的,直接跳过即可。
    • 为了实现上述的去重,可以使用哈希表(这里用unordered_set比较合适)
    • 注意定义的uset,是只记录本层元素是否重复使用,新的一层uset都会重新定义(清空),这就是理解“树层去重”的重点所在。

开始掉坑上去了,这里的终止条件仅仅为if (temp.size() >= 2),对例子[4, 4, 3, 2, 1],temp中为[4, 4]时,往后访问3、2、1都不符合>=4,但temp[4, 4]都满足终止条件,所以多次被压入。自己想到的解决办法有:

  • 记录上次temp中元素的个数和temp.back(),如果这次两者都相等,则说明出现上述情况,做不压入处理

Carl讲解:自己在做的时候,思维和想法受之前“组合”和“子集”题目的限制,代码中间对nums[i]值的判断,如果 < temp.back()的话,递归的深度还是停留在该层,然后接着遵循for循环去访问下标 i 之后的元素。这样导致的问题是,当前nums[i]不满足>=temp.back(),就去访问了i之后的元素,等到这层递归结束,回溯到上层时,又去访问刚刚的"i"的之后的元素,于是就造成了重复。

所以综上,对例子[4, 4, 3, 2, 1]这种情况,是自己写的代码中 if 处理不够考虑周全。而像Carl哥的题解,是一旦遇到nums[i] < path.back()的情况,直接continue,不再访问该层递归。

////自己的解法
int prev = INT_MIN;
vector<int> temp;
vector<vector<int>> result;
void findSubsequencesLoop(vector<int>& nums, int left)
{
    if (temp.size() >= 2)
    {
        result.emplace_back(temp);

    }
    for (int i = left; i < nums.size(); i++)
    {
        bool push_flag = false;
        if (prev == nums[i])
        {
            continue;
        }
        if (temp.empty() == true || nums[i] >= temp.back())
        {
            push_flag = true;
            temp.emplace_back(nums[i]);
        }
        findSubsequencesLoop(nums, i + 1);
        if (push_flag == true)
        {
            prev = temp.back();
            temp.pop_back();
        }
    }
}
vector<vector<int>> findSubsequences(vector<int>& nums)
{
    findSubsequencesLoop(nums, 0);
    return result;
}

Carl哥题解:

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();
    }
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
    result.clear();
    path.clear();
    backtracking(nums, 0);
    return result;
}

LC46. 全排列

我的做法是用unordered_set记录这个数组访问过的元素。

unordered_set<int> uset;
vector<int> temp;
vector<vector<int>> result;
void permuteLoop(vector<int>& nums)
{
    if (temp.size() == nums.size())
    {
        result.emplace_back(temp);
        return;
    }
    for (int i = 0; i < nums.size(); ++i)
    {
        bool push_flag = false;
        if (uset.find(nums[i]) == uset.end())
        {
            push_flag = true;
            temp.emplace_back(nums[i]);
            uset.emplace(nums[i]);
        }
        if (push_flag == true)
        {
            permuteLoop(nums);
            temp.pop_back();
            uset.erase(nums[i]);
        }
    }
}
vector<vector<int>> permute(vector<int>& nums)
{
    permuteLoop(nums);
    return result;
}

本题,Carl哥中used数组的思想(记录元素的下标即可--因为没排序)

////Carl题解
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;
}

官方做法:

void backtrack(vector<vector<int>>& res, vector<int>& output, int first, int len){
    // 所有数都填完了
    if (first == len) {
        res.emplace_back(output);
        return;
    }
    for (int i = first; i < len; ++i) {
        // 动态维护数组
        swap(output[i], output[first]);
        // 继续递归填下一个数
        backtrack(res, output, first + 1, len);
        // 撤销操作
        swap(output[i], output[first]);
    }
}
vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int> > res;
    backtrack(res, nums, 0, (int)nums.size());
    return res;
}

LC47. 全排序Ⅱ

这道题不能像上一道题自己的做法一样,不能直接使用unordered_set判断是否跟nums[i]的值有重复的。同时还要进行“树层去重” 。

  • 先对nums进行排序

  • 使用used数组,代替unordered_set用来记录已经使用过的元素的下标

  • 使用之前的prev_pop方式进行“树层去重” ,但跟之前的“组合之和Ⅱ”和“子集Ⅱ”有点不同,这个表现为两个方面:

    • 例子:[1,1,2],因为之前的for循环中int i = left,left在往深层递归时是不断往右移,而“全排序”问题,每次的 i 都是从0开始的,所以如果像开始那样仅仅判断if (prev == nums[i])的话导致只出现预期结果[[1,1,2],[1,2,1],[2,1,1]]中的第一项,因为[1, 1, 2]弹出第二个"1"时,保存的prev_pop是1,下次递归temp = [1, 2]时想判断下一位"1"时,因为跟prev_pop冲突,所以[1,2,1]就不会被放入结果集。所以加多一个pop_flag标志位来判断出现冲突时,石佛普
    • 例子:[0,3,3,3], if (pop_flag == true && prev == nums[i]) { pop_flag = false; continue; } pop_flag = false; 以上第一版这样写,会导致“树层去重”得不彻底,所以要像下面修改后的,对3个或3个以上的重复元素,连续去重,直到遇到一个不再重复的。
vector<vector<int>> result;
vector<int> temp;
int prev = INT_MIN;
bool pop_flag = false;
void permuteLoop(vector<int>& nums, vector<bool>& used)
{
    if (temp.size() == nums.size())
    {
        result.emplace_back(temp);
        return;
    }
    for (int i = 0; i < nums.size(); ++i)
    {
        if (pop_flag == true && prev == nums[i])
        {
            continue;
        }
        else if (prev != nums[i])
        {
            pop_flag = false;
        }
        if (used[i] == true)
        {
            continue;
        }
        temp.emplace_back(nums[i]);
        used[i] = true;
        permuteLoop(nums, used);
        used[i] = false;
        prev = temp.back();
        temp.pop_back();
        pop_flag = true;
    }
}
vector<vector<int>> permuteUnique(vector<int>& nums)
{
    vector<bool> used(nums.size(), false);
    sort(nums.begin(), nums.end());
    permuteLoop(nums, used);
    return result;
}

标签:temp,nums,Day26,back,pop,LC47,算法,vector,result
From: https://www.cnblogs.com/Mingzijiang/p/17170235.html

相关文章