python 怎么找到相同元素第2个后的位置 python找到列表相同元素_数组

LeetCode 举办的 LeetCoding Challenge,悄悄然来到了九月的征程,每日一道经典老题供大家练习。今天就来和大家分享其中一道:找出重复元素 III。

英文版地址:

https://leetcode.com/problems/contains-duplicate-iii/

中文版地址:

https://leetcode-cn.com/problems/contains-duplicate-iii/

题意


给定一个整数数组 nums,判断里面是否存在两个元素,它们的下标相差不超过 k,它们的值相差不超过 t:

  • 存在 i,j,满足
  • abs(i - j) <= k
  • abs(nums[i] - nums[j]) <= t

Two pointers + 树状数组的 O(nlogn) 算法


题目需要我们满足两个限制条件,一个是值,一个是下标,值的范围是无规律的,但下标有范围:当有 n 个元素时,下标范围是 [0, n-1]。

首先我们对数组按升序排序,然后维护一个滑动窗口,左右端点下标分别为 L,R。窗口右端不断右移,并始终保证 nums[R] - nums[L] <= t。

当 nums[R] - nums[L] > t 时,说明窗口过大,开始将 L 右移,直到重新满足 nums[R] - nums[L] <= t。

至此,关于滑动窗口的部分就完成了,我们能保证在滑动窗口内的任意两个元素 A,B 总是满足 abs(A - B) <= t。

由于数组经过排序,因此原本的下标被打乱了,一个滑动窗口内的任意两个元素的下标 x,y 并不一定满足 abs(x - y) <= k。

但我们可以把窗口右端点 R 的右移,看作是添加一个元素,把窗口左端点 L 的右移,看作是删除一个元素。

对此,我们可以开辟一个长度为 n 的 0/1 数组,当 a[i] = 0 时表示原数组下标为 i 的元素不在滑动窗口中,当 a[i] = 1 时表示原数组下标为 i 的元素在滑动窗口中。

当窗口右端点 R 右移,将一个元素添加到滑动窗口,设该元素在原数组中的下标为 x,同时我们将 a[x] 设置为 1。

那么,我们只需要查找 a 数组中 [x-k,x+k] 的元素中是否存在另一个 1(不包括 a[x] 本身)。即存在下标 y,满足 x-k <= y <= x+k,y != x, a[y] = 1, 那么就说明 nums[x] 和 nums[y] 此时都在滑动窗口中,肯定满足 abs(nums[x] - nums[y]) <= t, 同时下标满足 abs(x - y) <= k,这正是我们要找的元素对。

同理,当窗口左端点 L 右移,将一个元素移除出滑动窗口,设该元素在原数组中的下标为 x',同时我们将 a[x'] 设置为 0。

问题最终转化为,不断修改数组 a 中的元素值,同时对于一个下标 idx,要查询 a[idx-k, idx+k] 内是否存在两个元素为 1,这可以借助树状数组做到。

const int N = 20005;int c[N];inline int lowbit(int p) {    return p & (-p);}void update(int p, int n, int v) {    while (p <= n) {        c[p] += v;        p += lowbit(p);    }}int query(int p) {    int sum = 0;    while (p) {        sum += c[p];        p -= lowbit(p);    }    return sum;}bool check(int p, int n, int k) {    int l = max(0, p - k);    int r = min(n, p + k);        int cnt = query(r);    if (l > 1) {        cnt -= query(l-1);    }        return cnt >= 2;}class Solution {public:    bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {        int n = nums.size();        if (n == 0 || k <= 0 || t < 0) return false;                memset(c, 0, sizeof c);        using pii = pair<int, int>;        vector data;        for (int i = 0; i < n; ++i) {            data.push_back(make_pair(nums[i], i));        }        sort(data.begin(), data.end());                int l = 0, r = 0;                while (true) {            while(r < n && 0LL + data[r].first - data[l].first <= t + 0LL) {                update(data[r].second+1, n, 1);                if (check(data[r].second+1, n, k)) return true;                r++;            }            if (r >= n) break;            while(l <= r && 0LL + data[r].first - data[l].first > 0LL + t) {                update(data[l].second+1, n, -1);                l++;            }            if (l >= n) break;                    }        return false;    }};

基于数学性质的 O(n) 算法


除了上述算法外,我们还能利用一个数学性质,更高效解决它。另外在这个算法中,我们还是需要用到滑动窗口的思想。

不需要对原数组排序,维护一个滑动窗口,窗口的右端点 R 不断右移,并始终保证 R - L <= k,即窗口大小始终不超过 k;当窗口过大时则右移左端点 L。

这样能保证,窗口中的任意两个元素,它们的下标满足 abs(x - y) <= k。

另外,对于数组中每个元素,我们计算 b[i] = floor(nums[i] / t),表示将  nums[i] 丢入 b[i] 这个桶中。

结合滑动窗口,每次右端点 R 右移,我们就将一个新元素丢入它归属的桶中,左端点 L 右移,则将一个旧元素从桶中剔除。

显然,当 nums[i] 和 nums[j] 都归属于同一个桶时,它们必定满足 abs(nums[i] - nums[j]) <= t。换句话说,当一个桶有两个或以上元素时,就找到了答案。

不仅同一个桶的两个元素满足这个限制条件,两个左右相邻的桶也可能满足,例如 x=7,y=4,t=3:

  • bx = floor(7/3) = 2
  • by = floor(4/3) = 1

bx 和 by 是相邻的桶,它们的值满足 7-4 <= 3。

因为对于任意一个元素 x,假设它归属于 b 桶,我们看 b-1 桶,b 桶,b+1 桶中的元素是否和 x 的差值小于等于 t,如果满足,就找到了答案。

class Solution {public:    void add(int x, int t, map<long long, int>& buckets) {        if (!t) buckets[x] = x;        else {            int b = floor(1.0 * x / t);            buckets[b] = x;        }            }            void remove(int x, int t, map<long long, int>& buckets) {        if (!t) buckets.erase(x);        else {            int b = floor(1.0 * x / t);            buckets.erase(b);        }    }    bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {        int n = nums.size();        if (n == 0 || k <= 0 || t < 0) return false;                int l = 0, r = 0;        map<long long, int> buckets;                while (r < n) {            if (t == 0) {                if (buckets.count(nums[r])) return true;            } else {                int b = floor(1.0 * nums[r] / t);                if (buckets.count(b) ||                     (buckets.count(1LL * b-1) && abs(1LL * buckets[b-1] - nums[r]) <= 1LL * t) ||                     (buckets.count(1LL * b+1) && abs(1LL * buckets[b+1] - nums[r]) <= 1LL * t)) {                    return true;                }            }            add(nums[r], t, buckets);            r++;            if (r - l > k) {                remove(nums[l], t, buckets);                l++;            }        }        return false;    }};