leetcode 6227. 下一个更大元素 IV

79 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第31天,点击查看活动详情

下一个更大元素 IV 的两种解法:set + 二分、主席树+二分

6227. 下一个更大元素 IV

给你一个下标从 0 开始的非负整数数组 nums 。对于 nums 中每一个整数,你必须找到对应元素的 第二大 整数。

如果 nums[j] 满足以下条件,那么我们称它为 nums[i] 的 第二大 整数:

  • j > i
  • nums[j] > nums[i]
  • 恰好存在 一个 k 满足 i < k < j 且 nums[k] > nums[i] 。

如果不存在 nums[j] ,那么第二大整数为 -1 。

  • 比方说,数组 [1, 2, 4, 3] 中,1 的第二大整数是 4 ,2 的第二大整数是 3 ,3 和 4 的第二大整数是 -1 。 请你返回一个整数数组 **answer ,其中 **answer[i]是 **nums[i] 的第二大整数。

数据范围

  • 1 <= nums.length <= 105
  • 0 <= nums[i] <= 109

解法1 set+二分

第一种做法更偏向于思维一点,对于当前数的下一个更大的元素的下一个更大的元素而言

其实就是在要求你维护一个区域内的东西。

可以将原数组映射一下,从大到小排个序,就可以得到一系列的下标值组成的元素

可以把它叫做 id[x],存放的是第id[x]项元素的下标,使得整体元素单调下降趋势(降序排序)

在拥有了降序排序之后,依次遍历 id[x]每一项元素

由于序列元素是单调下降的,就可以保证一个局面,对于当前id[i]位置的值而言,前面所有值都是大于他的。

而我们只需要在这些元素里面找到比当前下标大两个位置的元素即可。

这一点,可以用一个set来维护,利用二分去查找当前位置的下一个坐标,由于可以保证这里面所有的元素都是大于 id[i]的,所以,我们只需要维护一些set集合中的 id[j]就好了

整体时间复杂度为O(nlogn)O(nlogn)

class Solution {
public:
    vector<int> secondGreaterElement(vector<int>& nums) {
        int n = nums.size();
        vector<int> id(n), ans(n, -1);
        set<int> st;
        for (int i = 0; i < n; i ++) id[i] = i;
        sort(begin(id), end(id), [&](int a, int b) {
            if (nums[a] != nums[b]) return nums[a] > nums[b];
            return a < b;
        });
        for (int i = 0; i < n; i ++) {
            int v = id[i];
            auto k = st.lower_bound(v);
            if (k != st.end() && (++k) != st.end())
                ans[v] = nums[*k];
            st.insert(v);
        }

        return ans;
    }
};

解法2 主席树+二分

上面的方法可能不是那么易懂,虽然想一想也可以想出来,其实可以更暴力一点。

我们只关心对于当前数的右边第二个大于他的元素。

所以,我们可以二分答案去查找右边的区间中,第二个大于他的元素所在的位置。

为什么可以保证这一点呢?

首先,假设我们可以求出右边任意一个区间中大于自己的元素个数。

于是,我们就可以干这样一件事情:

对于元素x而言若是[i+1,x]的大于自己的元素个数小于2,则答案一定在x+1之后否则,答案一定在[i+1,x]之间这就是一个典型的二分答案的用途罢了对于元素x而言 \\ 若是[i+1, x] 的大于自己的元素个数小于2,则答案一定在 x +1 之后\\ 否则,答案一定在 [i+1, x]之间 \\ 这就是一个典型的二分答案的用途罢了

但是如何动态查询一个区间内有几个元素是大于自己的呢?

其实,动态查询一个区间内有多少个元素大于自己、第k大元素之类的问题

用主席树就非常好解决这一件事情。

只需要建立一颗主席树,然后在主席树的基础上对其右区间进行二分答案(左区间是当前位置)

主席树每次查询是log(n)log(n)级别的,二分答案是lognlogn级别的,

一共遍历n次

整体时间复杂度 O(n×logn×logn)O(n \times logn \times logn)

(细节:由于主席树存不下10^9这么大的值域,其实应该将其离散化一下,映射到 1-n之间然后进行建树)

#define ll long long
using namespace std;
const int N = 3e5 + 10;
int n, q, root[N], cnt;
struct node {
    int l, r, num;
} tr[N * 40];
void add(int l, int r, int pre, int &now, int pos) {
    tr[++cnt] = tr[pre], now = cnt, tr[cnt].num++;
    if(l == r)
        return;
    int m = (l + r) >> 1;
    if(pos <= m)
        add(l, m, tr[pre].l, tr[cnt].l, pos);
    else
        add(m + 1, r, tr[pre].r, tr[cnt].r, pos);
}
int query(int k, int l, int r, int L, int R) {
    if(k > r)
        return tr[R].num - tr[L].num;
    int m = (l + r) >> 1, ans = 0;
    if(k > l)
        ans += query(k, l, m, tr[L].l, tr[R].l);
    if(k > m + 1)
        ans += query(k, m + 1, r, tr[L].r, tr[R].r);
    return ans;
}
class Solution {
public:
    vector<int> secondGreaterElement(vector<int>& nums) {
        int n = nums.size();
        stack<int> st;
        vector<int> tr(n + 1);
        vector<int> ans(n);
        vector<int> ve = nums;
        sort(ve.begin(), ve.end());
        ve.erase(unique(ve.begin(), ve.end()), ve.end());
        vector<int> cov(n);
        for (int i = 0; i < n; i ++) 
            cov[i] = lower_bound(begin(ve), end(ve), nums[i]) - begin(ve) + 1;
        root[0] = {};
        for (int i = 1; i <= n; i ++) {
            add(1, n, root[i - 1], root[i], cov[i-1]);
        }
        for (int i = n-1; i >= 0; i --) {
            if ((n-i - query(cov[i] + 1, 1, n, root[i], root[n])) < 2) ans[i] = -1;
            else {
                int l = i+1, r = n;
                while (l < r) {
                    int x = l + r + 1 >> 1;
                    int val = (x-i - query(cov[i] + 1, 1, n, root[i], root[x]));
                    if (val < 2) {
                        l = x;
                    } else {
                        r = x - 1;
                    }
                }
                ans[i] = nums[l];
            }
        }
        return ans;
    }
};