在 O( log(log (max_value))) 中找到具有非递减连续差的排序数组中小于 X 的最大元素

Posted

技术标签:

【中文标题】在 O( log(log (max_value))) 中找到具有非递减连续差的排序数组中小于 X 的最大元素【英文标题】:Find the largest element smaller than X in a sorted array with non-decreasing consecutive differences, in O( log(log (max_value))) 【发布时间】:2020-12-29 20:05:39 【问题描述】:

我想在排序后的数组中找到小于x 的最大元素,其中数组的连续元素之间的差异不递减。

例如:

[1, 3, 10, 17, 50, 1000] 是一个有效的输入,因为(2,7,7,33,950) 的连续差异是按递增顺序排列的。

[1, 3, 4, 10, 18] 不是有效输入,因为连续差异 (2, 1, 6, 8) 不是按递增顺序排列的。

最佳解决方案是O(log(log max_value)),所以我正在寻找比上限/二分搜索更好的解决方案。实际上,我希望优化从 O(log N)O(log(log max_value) 的二分搜索。

#include <bits/stdc++.h>

using namespace std;

const int n = 50000001;
int arr[n];

int binarysearch_lowerbound(int begin, int end, int x)

    int ans = end;
    while (begin < end) 
        int mid = (begin + end) / 2;
        if (arr[mid] >= x) 
            ans = mid;
            end = mid;
        
        else 
            begin = mid + 1;
        
    
    return ans;

int main()

    int N;
    cin >> N;
    for (int i = 0; i < N; i++) 
        cin >> arr[i];
    
    int q;
    cin >> q;
    for (int i = 0; i < q; i++) 
        int x;
        cin >> x;
        int k;
        int begin = x / (arr[N - 1] - arr[N - 2]);
        int end = x / (arr[1] - arr[0]);
        if (end > N - 1) 
            end = N;
        
        k = binarysearch_lowerbound(begin, end, x);
        cout << arr[k - 1] << endl;
    
    return 0;

【问题讨论】:

我对问题陈述有点困惑。最大元素只是排序范围的最后一个元素。你的意思是检查相邻的差异是否在增加?请展示一些输入/输出示例。 数组的最大元素,小于给定值X 那么,问题是什么? 极端情况 [0,1,2,3,4,5,6,7,8,9,10] 将是一个有效输入,因为该示例表明要求不是差异在增加。它只是不允许减少,对吧? @Ted Lyngmo 是的,你是对的 【参考方案1】:

要获得 O(log log N),您可能需要使用插值搜索而不是二分搜索。

在插值搜索中,您获取要查找的值以及数组中的最小值和最大值,并根据它们猜测您要查找的元素的可能位置,而不是盲目地猜测数组未知部分的中间。

简单的情况是您的元素在数组中大致均匀分布,在这种情况下,您可以使用线性插值来猜测要搜索的点。

在您的情况下,数据遵循绝对非线性的分布,因此您希望基于此来预测位置,而不是线性插值。作为第一个近似值,我们假设它大致是二次的。

对于第一个测试,让我们从一组精确遵循二次分布的数字开始:[1, 4, 9, 16, 25, 36, 49, 64]。我们将搜索小于 30 的最大值。我们的集合有 8 个值,范围为 1..64 = 63。

如果我们使用线性插值来搜索 30,我们的初始猜测将是 30/63*8 = 3 或 4(在这种情况下实际上非常准确)。

要进行二次插值,我们首先取范围顶部和底部的平方根(分别为 8 和 1),然后取 30 的平方根 (~5.5) 并进行线性插值那些 (5.5/(8-1) * 8) 得到我们的初步猜测。

从那里开始,我们继续进行二分搜索 - 如果我们的数字大于我们找到的值,我们在较大的数字中进行新的插值,如果它更小,我们在其中进行新的插值较小的数字。

所有这一切中的困难部分是找到一个实际上相当适合您的数字的函数。在您的情况下,这可能特别成问题,因为您只获得了关于分布的一点点信息。差异不能减小的要求意味着它至少是线性的,但除此之外,我们真的不知道——它可能是线性的,或者 N1.1,或者 N10,甚至可能是 N!。线性插值不能很好地拟合阶乘分布(反之亦然)。

【讨论】:

我在想,检查第一个和最后一个元素不会大致了解用于查找下一个要比较的元素的函数吗?对于每个检查的新元素,都会形成一个新函数。 @TedLyngmo:第一个和最后一个自己不会。仅从这两点可以做出的最佳猜测将是线性分布。首先,中间和最后会告诉你更多。 你的解释很有帮助,我不知道插值搜索。给出了一个提示或子问题,其中连续的差异是从 100 到 200。 Newton-Raphson 肯定会给出O(log log N),而不假设分布的二次性质。 @user58697:我上次听说,Newton-Raphson 要求对被插值的函数进行一阶导数...

以上是关于在 O( log(log (max_value))) 中找到具有非递减连续差的排序数组中小于 X 的最大元素的主要内容,如果未能解决你的问题,请参考以下文章

Codility峰的O(N * log(log(N)))算法?

O(n) 和 O(log(n)) 之间的区别 - 哪个更好,O(log(n)) 到底是啥?

什么是 O(log* N)?

在 O(K*log(K)) 中打印给定堆中最大的 K 个元素?

在 O(log(n)) 时间内从 std::set 中随机选择一个元素

大 O - O(log(n)) 代码示例