枚举去重子序列

本文介绍了如何使用二进制枚举和递归枚举方法解决LeetCode上的子序列去重问题。通过二进制表示选择数组元素,结合哈希算法(Rabin-Karp编码)去重,以及递归枚举子序列的通用模板,实现了找到所有非递减子序列。文章强调了学习新算法的重要性,并鼓励不断探索和总结。
摘要由CSDN通过智能技术生成

枚举去重子序列

    今天刷力扣遇到了一道题,要枚举所有的子序列并去重,这让我一下没了思路,平时根本没遇到过啊,然后话不多说直接看题解,两种办法:一是二进制枚举,二是递归枚举,直接给我打开了新世界的的大门!这里记录一下~
题目:491. 递增子序列https://leetcode.cn/problems/non-decreasing-subsequences/
参考题解就是官解还有评论区各位大佬们的讨论了


二进制枚举

因为之前没接触过二进制枚举,而且之前遇到二进制的问题直接就复制粘贴,今天下定决心啃一下,发现好像确实没那么难~
假设我们有一个数组[1,2,3,4,5,6]一共六个数,然后用二进制表示选与不选:1表示选,0表示不选
举个例子:选[1,2,3]的话那么得出来的二进制序列就是

数据123456
二进制111000

那么我们该怎么用代码写出来呢,力扣官解给出了一个方案:

class Solution {
public:
    vector<int> temp;
    vector<vector<int>> ans;
    unordered_set<int> s;
    int n;

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

    int getHash(int base, int mod) {
    	//(x+101)是因为数据范围是-100~100,保证都是正数
    	//思路与平时计算进制转化时相反,从左往右进行计算
        int hashValue = 0;
        for (const auto &x: temp) {
            hashValue = 1LL * hashValue * base % mod + (x + 101);
            hashValue %= mod;
        }
        return hashValue;
    }

    vector<vector<int>> findSubsequences(vector<int>& nums) {
        n = nums.size();
        for (int i = 0; i < (1 << n); ++i) {
            findSubsequences(i, nums);
            int hashValue = getHash(263, int(1E9) + 7);
            if ( s.find(hashValue) == s.end()) {
                ans.push_back(temp);
                s.insert(hashValue);
            }
        }
        return ans;
    }
};
  • 如果我们要构造子序列是不是要将000000111111的数都表示一遍,那么就有2^n个数所以用代码表示的话就是1 << n(今天刚学到,之前都是用pow函数),然后调用find函数判断(1加入0不加入)要将数组的哪一个元素加入到数组中进行下一步操作
  • 看代码用到了&,意思就是最右边的那一位与1进行与&运算,结果为真才移入数组,学过逻辑运算的应该都知道只有1&1才是1其他情况都是0。
  • 然后将二进制数右移,即将最右边第二位与1进行与运算重复n次

可是现在只是构造出了所有的子序列,我们的目的是寻找所有的去重子序列,下面就要介绍一个算法:哈希算法(即 Rabin-Karp 编码)见题解https://leetcode.cn/problems/longest-happy-prefix/

    Rabin-Karp 字符串编码是一种将字符串映射成整数的编码方式,可以看成是一种哈希算法。具体地,假设字符串包含的字符种类不超过 ∣Σ∣(其中Σ 表示字符集),那么我们选一个大于等于∣Σ∣ 的整数 base就可以将字符串看成 base 进制的整数,将其转换成十进制数后,就得到了字符串对应的编码。
    例如给定字符串 s =“abca”,其包含的字符种类为 3(即 abc三种)。我们取base=9,将字符串 ss 看成九进制数(0120) ,转换为十进制为 99,也就是说字符串 abca 的编码为 99。

    这样做的好处是什么?我们可以发现一个结论:两个字符串 s 和 t 相等,当且仅当它们的长度相等且编码值相等。对于长度为 k 的所有字符串,我们会将它们编码成位数为 k(包含前导零)的base 进制数,这是一个单射,因此结论成立。
    这在实现的过程中,我们发现这个哈希值可能非常大,我们可以将它模一个大素数 PP,将这个值映射到intint 的范围内。所以实际上这里的哈希函数是:

                        在这里插入图片描述

    玛卡巴卡说了这么多总结一下就是:选择一个大于元素种类的数base,将字符串写成base进制然后转化为十进制,进而判断两个数是否一样,计算过程直接套公式就可以了,因为有数据规模可能太大所以要取模控制一下
代码如下:

    int getHash(int base, int mod) {
        int hashValue = 0;
        for (const auto &x: temp) {
            hashValue = 1LL * hashValue * base % mod + (x + 101);
            hashValue %= mod;
        }
        return hashValue;
    }

    至此就解决完了~是不是感觉挺复杂的?下面介绍一个简单的!

递归枚举

有了前面的基础,直接放代码

vector<vector<int>> ans;
vector<int> temp;
void dfs(int cur, vector<int>& nums) {
    if (cur == nums.size()) {
        // 判断是否合法,如果合法判断是否重复,将满足条件的加入答案
        if (isValid() && notVisited()) {//这里可以改成自己的条件约束
            ans.push_back(temp);
        }
        return;
    }
    // if 如果选择当前元素
    temp.push_back(nums[cur]);
    dfs(cur + 1, nums);
    temp.pop_back();
    // if 如果不选择当前元素
    dfs(cur + 1, nums);
}

    这是一个递归枚举子序列的通用模板,即用一个临时数组temp 来保存当前选出的子序列,使用cur 来表示当前位置的下标,在 dfs(cur, nums) 开始之前,[0,cur−1] 这个区间内的所有元素都已经被考虑过,而[cur,n] 这个区间内的元素还未被考虑。在执行 dfs(cur, nums) 时,我们考虑cur 这个位置选或者不选,如果选择当前元素,那么把当前元素加入到temp 中,然后递归下一个位置,在递归结束后,应当把 temp 的最后一个元素删除进行回溯;如果不选当前的元素,直接递归下一个位置。

学会就好了~


耗费了一个下午解决了一个困难题和一个中等题,越学越发现自己不会的东西还有很多,还有时间,多做总结变成自己的东西,稳健!🍅

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值