LeetCode -- 352场周赛

 思路:动态规划

首先计算原数组的条件数组,及所有的元素都%2

f[i]表示从零到i中选,且以第i项为结尾的最长奇偶子数组。

class Solution {
public:
    int longestAlternatingSubarray(vector<int>& nums, int threshold) {
        int n = nums.size();
        vector<int> d(n + 5, 0);
        vector<int> f(n + 5, 0);
        for(int i = 0; i < n; i ++ ) d[i] = nums[i] % 2;

        for(int i = 0; i< n; i ++ ) {
            if(nums[i] > threshold) continue;
            if(d[i] == 0) f[i] = 1;
            if(i && nums[i - 1] <= threshold && d[i] == d[i - 1] ^ 1 ) {
                f[i] = max(f[i], f[i - 1] + 1);
            }
        }

        int res = 0;
        for(int i = 0; i < n; i ++ ) res = max(res, f[i]);
        return res;
    }
};

 

 

 

 不间断子数组即为:某子数组中,最大值与最小值的差 <= 2

利用滑动窗口 + 平衡树可进行操作

在 c ++ 中set底层利用了红黑平衡树,里面的元素是有序的,默认情况下 *s.begin()为最小元素,*s.rbegin()为最大元素。

class Solution {
public:
    long long continuousSubarrays(vector<int>& nums) {
        long long res = 0;
        multiset<int> s;
        int left = 0, n = nums.size();
        for(int right = 0; right < n; right ++ ) {
            s.insert(nums[right]);
            while(*s.rbegin() - *s.begin() > 2) {
                s.erase(s.find(nums[left ++ ]));
            }
            res += right - left + 1;
        }
        return res;
    }
};  

 

 

 

 本题可以利用线性筛和哈希表进行处理。

先用线性筛筛出0 - n中所有的质数,再用哈希表快速查找质数与其相对于n的补数是否合法。

注:在lc平台函数尽量写成匿名函数形式,不然总是出点意料之外的错误。

class Solution {
public:
    vector<vector<int>> findPrimePairs(int n) {
        int priems[1000005] = {0}, cnt = 0;
        bool st[1000005] = {0};

        auto get_priems = [&]() {
            st[0] = st[1] = true;
            for(int i = 2; i <= n; i ++ ) {
                if(!st[i]) priems[cnt ++ ] = i;
                for(int j = 0; priems[j] * i <= n; j ++ ) {
                    st[priems[j] * i] = true;
                    if(i % priems[j] == 0) break;
                }
            }
        };

        get_priems();

        vector<vector<int>> res;
        for(int i = 0; i < cnt; i ++ ) {
            if(!st[priems[i]]) {
                if(!st[n - priems[i]]) {
                    res.push_back({priems[i], n - priems[i]});
                    st[priems[i]] = st[n - priems[i]] = true;
                }
            }
        }

        return res;
    }
};

 

 

 方法一:枚举

O(n**2)的枚举方法,固定左端点,枚举右端点。利用哈希表快速查找子数组中是否含有nums[j - 1] 和 nums[j + 1]在对答案进行加和。

class Solution {
public:
    int sumImbalanceNumbers(vector<int>& nums) {
        const int n = nums.size();
        bool st[n + 2];
        int res = 0;

        for(int i = 0; i < n; i ++ ) {
            memset(st, 0, sizeof st);
            st[nums[i]] = true;
            int cnt = 0;
            for(int j = i + 1; j < n; j ++ ) {
                int k = nums[j];
                if(!st[k]) {
                    cnt += 1 - st[k - 1] - st[k + 1];
                    st[k] = true;
                }
                res += cnt;
            }
        }
        return res;
    }
};

 

方法二:贡献法(基本就是预处理 + 枚举)

优化枚举,对于每个nums[i],它产生贡献的子数组是nums[i + 1]没有出现的范围。另外,当有相同的数字出现时,我们规定只有第一个数字有贡献,避免重复问题。

因此,对每个数字v我们找到其之前最近的v + 1出现的下标p;找到其之后最近的v或v + 1的下标s。可产生贡献的子数组个数为 (i - p) * (s - i)

由于我们找的是v 或 v + 1的下标,这个是不会进行贡献的,所以要减去子数组个数。

class Solution {
public:
    int sumImbalanceNumbers(vector<int>& nums) {
        int n = nums.size();
        vector<int> prefix(n, -1), suffix(n, n);

        {
            unordered_map<int, int> mp;
            for(int i = 0; i < n; i ++ ) {
                int v = nums[i];
                mp[v] = i;
                auto itor = mp.find(v + 1);
                if(itor != mp.end()) {
                    prefix[i] = itor -> second;
                }
            }
        }


        {
            unordered_map<int, int> mp;
            for(int i = n - 1; i >= 0; i -- ) {
                int v = nums[i];
                auto itor = mp.find(v + 1);
                if(itor != mp.end()) {
                    suffix[i] = itor -> second;
                }
                itor = mp.find(v);
                if(itor != mp.end()) {
                    suffix[i] = min(suffix[i], itor -> second);
                }
                mp[v] = i;
            }
        }
        
        int res = 0;
        for(int i = 0; i < n; i ++ ) {
            res += (i - prefix[i]) * (suffix[i] - i);
        }

        return res - n * (n + 1) / 2;

    }
};

 

方法三:还是贡献法

方法二中,我们使用了unordered_map进行贡献数组的初始化,实在是慢,下面参考灵神的代码,利用手开数组哈希的方法进行快速初始化。

class Solution {
public:
    int sumImbalanceNumbers(vector<int>& nums) {
        const int n = nums.size();
        int right[n], idx[n + 1];
        fill(idx, idx + n + 1, n);

        for(int i = n - 1; i >= 0; i -- ) {
            int x = nums[i];
            right[i] = min(idx[x], idx[x - 1]);
            idx[x] = i;
        }

        int res = 0;
        memset(idx, -1, sizeof(idx));

        for(int i = 0; i < n; i ++ ) {
            int x = nums[i];
            res += (i - idx[x - 1]) * (right[i] - i);
            idx[x] = i;
        }

        return res - n * (n + 1) / 2;

    }
};

 

最后一题总结:边遍历边插入,这种遍历方式感觉就像权值线段树求逆序对,从前往后或者从后往前遍历,遍历到某个点,就将某个点的贡献加入答案。只不过,权值线段树求逆序对的预处理过程可以用O(logn)的时间复杂度直接算得,而本题则需要预处理。

posted @ 2023-07-10 22:53  深渊之巅  阅读(6)  评论(0编辑  收藏  举报