给定一个数组a [],确定至少一个元素是否满足条件a [i] == i的最有效方法是什么?

数组中的所有元素都是排序和不同的,但它们不一定是整数类型(即它们可能是浮点类型).

解决方法

有几个人声称“排序”,“不同”和“不一定是整数”的相关性.实际上,正确选择有效的算法来解决这个问题取决于这些特性.如果我们知道数组中的值既是不同的又是积分的,那么更有效的算法是可能的,而如果值可能是非不同的,则需要效率较低的算法,无论它们是否是整数.当然,如果数组尚未排序,您可以先对其进行排序(平均复杂度为O(n log n)),然后使用更有效的预排序算法(即对于排序的数组),但在未排序的情况下例如,简单地保持数组未排序并直接比较线性时间(O(n))中的值会更有效.请注意,无论选择何种算法,最佳情况都是O(1)(当检查的第一个元素包含其索引值时);在执行任何算法的任何时候,我们可能会遇到一个元素,其中[i] == i,此时我们返回true;在这个问题的算法性能方面真正重要的是我们能够多快地排除所有元素并声明没有这样的元素a [i]其中a [i] == i.

问题没有说明[]的排序顺序,这是一个非常重要的缺失信息.如果它是递增的,最坏情况下的复杂性将始终为O(n),我们无法做任何事情来使最坏情况的复杂性更好.但是如果排序顺序是下降的,即使是最坏情况的复杂性也是O(log n):因为数组中的值是不同的并且是降序的,所以只有一个可能的索引,其中a [i]可以等于i,基本上所有你要做的是二元搜索以找到交叉点(如果有这样的交叉,则升序索引值越过降序元素值),并确定交叉点索引值处的[c] == c C.由于这非常简单,我将继续假设排序顺序为升序.有趣的是,如果元素是整数,即使在升序的情况下也存在类似的“交叉”情况(尽管在升序的情况下可能存在多个a [i] == i匹配),所以如果元素是整数,二进制搜索也适用于升序情况,在这种情况下,即使是最坏情况的性能也是O(log n)(见Interview question – Search in sorted array X for index i such that X[i] = i).但是在这个版本的问题上我们没有给予豪华.

以下是我们如何解决此问题:

从第一个元素开始,a [0].如果它的值是== 0,你找到了一个满足[i] == i的元素,所以返回true.如果它的值是< 1,下一个元素(a [1])可能包含值1,因此您将继续下一个索引.但是,如果[0]> = 1,你知道(因为值是不同的)条件a [1] == 1不可能是真的,所以你可以安全地跳过索引1.但你甚至可以做比这更好:例如,如果a [0] == 12,你知道(因为值按升序排序),在元素a之前不可能有任何满足[i] == i的元素[13 ].因为数组中的值可以是非整数的,所以我们不能在此处进行任何进一步的假设,因此我们可以安全地直接跳到的下一个元素是[13](例如a [1]到[12]可能全部包含12.000 ...和13.000之间的值......这样[13]仍然可以正好等于13,所以我们必须检查它. 继续该过程产生如下算法:

// Algorithm 1
bool algorithm1(double* a,size_t len)
{
    for (size_t i=0; i<len; ++i) // worst case is O(n)
    {
        if (a[i] == i)
            return true; // of course we Could also return i here (as an int)...
        if (a[i] > i)
            i = static_cast<size_t>(std::floor(a[i]));
    }
    return false; // ......in which case we’d want to return -1 here (an int)
}

如果[]中的许多值大于它们的索引值,那么它具有相当好的性能,并且如果[]中的所有值都大于n(在仅一次迭代后它返回false),则具有优异的性能,但它具有如果所有值都小于其索引值,则表现不佳(在n次迭代后它将返回false).所以我们回到绘图板……但我们所需要的只是略微调整.考虑到算法可能已被编写为从n向下扫描到0,就像它可以从0向前扫描一样容易.如果我们将迭代的逻辑从两端组合到中间,我们得到如下算法:

// Algorithm 2
bool algorithm2(double* a,size_t len)
{
    for (size_t i=0,j=len-1; i<j; ++i,--j) // worst case is still O(n)
    {
        if (a[i]==i || a[j]==j)
            return true;
        if (a[i] > i)
            i = static_cast<size_t>(std::floor(a[i]));
        if (a[j] < j)
            j = static_cast<size_t>(std::ceil(a[j]));
    }
    return false;
}

这在两种极端情况下都具有出色的性能(所有值都小于0或大于n),并且几乎任何其他值的分布都具有相当好的性能.最糟糕的情况是,如果数组下半部分的所有值都小于它们的索引,并且上半部分中的所有值都大于它们的索引,性能会降低到最坏情况下的O( N).最好的情况(或者极端情况)是O(1),而平均情况可能是O(log n),但是我推迟到有数学专业的人来确定.

有几个人建议采用“分而治之”的方法解决问题,但没有具体说明如何划分问题以及如何处理递归划分的子问题.当然,这样一个不完整的答案可能不会满足面试官.上述算法2的朴素线性算法和最坏情况性能都是O(n),而算法2通过跳过(不检查)元素,将平均情况性能提高到(可能)O(log n).如果在一般情况下,它在某种程度上能够跳过比算法2可以跳过的更多元素,那么分而治之的方法只能胜过算法2.让我们假设我们通过将数组分成两个(几乎)相等的连续半部来递归地划分问题,并决定是否由于产生的子问题,我们可能能够跳过比算法2可以跳过更多的元素,尤其是算法2的最坏情况.对于本讨论的其余部分,我们假设输入对于算法2来说是最坏的情况.在第一次分割之后,我们可以检查两半的顶部和顶部.对于相同极端情况的底部元素,其导致算法2的O(1)性能,但是在两个半部合并的情况下导致O(n)性能.如果下半部分中的所有元素都小于0并且上半部分中的所有元素都大于n-1,则会出现这种情况.在这些情况下,对于我们可以排除的任何一半,我们可以立即将O(1)性能排除在底部和/或上半部分之外.当然,在进一步递归之后仍然需要确定该测试不能排除的任何一半的性能,再将该半除以半直到我们找到其顶部或底部元素包含其索引值的任何段.与算法2相比,这是一个相当不错的性能提升,但它仅出现在算法2最坏情况的某些特殊情况下.我们用分而治之的方式做的就是减少(略微)引起最坏情况行为的问题空间的比例.对于分而治之,仍然存在最坏情况,它们完全匹配大多数问题空间,这会引发算法2的最坏情况行为.

因此,鉴于分而治之的算法具有较少的最坏情况,继续使用分而治之的方法是不是有意义?

总之,没有.也许.如果您事先知道大约一半的数据小于0且一半大于n,那么这种特殊情况通常会采用分而治之的方法.或者,如果您的系统是多核的并且您的’n’很大,那么在所有核心之间平均分配问题可能会有所帮助,但是一旦它们在它们之间分开,我认为每个核心上的子问题可能是最好的用上面的算法2解决,避免进一步划分问题,当然避免递归,正如我在下面论述….

在递归的分治方法的每个递归级别,算法需要某种方式来记住问题的尚未解决的后半部分,同时它会递归到上半部分.通常,这是通过让算法首先为一半递归调用自身,然后为另一半,一种在运行时堆栈上隐式维护此信息的设计来完成的.另一种实现可以通过在显式堆栈上保持基本相同的信息来避免递归函数调用.在空间增长方面,算法2是O(1),但任何递归实现都不可避免地是O(log n),因为必须在某种堆栈上维护这些信息.但是除了空间问题之外,递归实现还有额外的运行时开销,即记住尚未递归到子问题的一半的状态,直到可以递归到它们为止.这种运行时开销并不是免费的,并且考虑到上面算法2的实现的简单性,我认为这种开销是成比例的.因此,我建议上面的算法2将对绝大多数情况下的任何递归实现进行全面打击.

c – 在排序数组中找到[i] = i的最有效方法是什么?的更多相关文章

  1. ios – 嵌套递归函数

    我试图做一个嵌套递归函数,但是当我编译时,编译器崩溃.这是我的代码:编译器记录arehere解决方法有趣的…它似乎也许在尝试在定义之前捕获到内部的引用时,它是bailing?以下修复它为我们:当然没有嵌套,我们根本没有任何问题,例如以下工作完全如预期:我会说:报告!

  2. ios – 按键键入字典的Swift排序数组,其中value是可选的AnyObject

    我正在直接从Parse中提取一系列字典并将它们显示在表格中.所以我真的很想处理我所掌握的数据结构.PFObject是[String:AnyObject?解决方法Swift无法比较任何两个对象.您必须先将它们转换为特定类型:如果有多个字典没有指定键的值,它们将被放置在结果数组的末尾,但它们的相对顺序是不确定的.

  3. swift override --有一个递归问题未解决

    classca{varcount:Int{get{return1;}set{self.count=newValue;}}funcdescribe()->String{return"ca";}}classcb:ca{overridefuncdescribe()->String{return"cb";}overridevarcount:Int{get{return2;}set{//引起了递归调用,未找

  4. Swift2.0语言教程之函数嵌套调用形式

    Swift2.0语言教程之函数嵌套调用形式Swift2.0语言函数嵌套调用形式在Swift中,在函数中还可以调用函数,从而形成嵌套调用。以下将对这两种调用进行详细讲解。调用方式如图7.4所示。图7.4函数嵌套的形式以下将使用函数的嵌套调用实现对s=22!这个数值,即调用f1()函数,计算22,结果为4,然后在调用f2()函数,对4的阶乘求取,计算完成22!但是在Swift语言中递归必须要有一个满足结束的条件。

  5. swift实现排序算法

    swift实现排序算法swift插入排序funcinsertionSort(){varx,y,key:Intfor(x=0;x-1;y--){if(key

  6. 【Swift】学习笔记(九)——枚举

    因为类完全可以替代枚举。不过swift中也有许多类的特性被枚举支持。这样判断必须穷举所有成员,否则就需要增加default这个选项了。使用递归枚举时,编译器会插入一个中间层。

  7. Swift实现的快速排序及sorted方法的对比

    Swift语言有着优秀的函数式编程能力,面试的时候面试官都喜欢问我们快速排序,那么用Swift如何实现一个快速排序呢?然后实现快速排序的方法:可以发现使用Swift实现快速排序的代码非常的简洁。在看完这段代码后我做了如下思考:既然是排序,那么必然可以使用系统的sorted方法,效果如何呢?对于快排最头疼的顺序性数组,sorted的重复次数只有n次!说明在面对这种类型的数组的时候sorted方法进行过判断,直接输出了。

  8. 《swift2.0 官方教程中文版》 第2章-08枚举

    importFoundation//在Swift中,枚举类型是一等公民。像Swift中其他类型一样,它们的名字必须以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于读起来更加容易理解:vardirectionToHead=Compasspoint.West//directionToHead的类型可以在它被Compasspoint的一个可能值初始化时推断出来。//使用枚举成员的rawValue属性可以访问该枚举成员的原始值:letearthsOrder=Planet2.Earth.rawVa

  9. swift枚举

    Swift中的枚举更加灵活,不必给每一个枚举成员提供一个值。它是Directionoperation类型,因为swift中的枚举不会自动给成员赋值为0,1…枚举类型易扩展。原始值的隐式赋值在使用原始值为整数或者字符串类型的枚举时,不需要显式地为每一个枚举成员设置原始值,Swift将会自动为你赋值。

  10. Swift学习之路04-枚举

    枚举在Swift中,枚举类型是一等类型。*注意与C和Objective-C不同,Swift的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的nt例子中,north,East和West不会被隐式地赋值为0,1,2和3。相反,这些枚举成员本身就是完备的值,这些值的类型是已经明确定义好的Compasspoint类型。下面的例子是Compasspoint枚举的细化,使用字符串类型的原始值来表示各个方向的名称:上面例子中,Compasspoint.south拥有隐式原始值South,依次类推。使用递归枚举时,

随机推荐

  1. 从C到C#的zlib(如何将byte []转换为流并将流转换为byte [])

    我的任务是使用zlib解压缩数据包(已接收),然后使用算法从数据中生成图片好消息是我在C中有代码,但任务是在C#中完成C我正在尝试使用zlib.NET,但所有演示都有该代码进行解压缩(C#)我的问题:我不想在解压缩后保存文件,因为我必须使用C代码中显示的算法.如何将byte[]数组转换为类似于C#zlib代码中的流来解压缩数据然后如何将流转换回字节数组?

  2. 为什么C标准使用不确定的变量未定义?

    垃圾价值存储在哪里,为什么目的?解决方法由于效率原因,C选择不将变量初始化为某些自动值.为了初始化这些数据,必须添加指令.以下是一个例子:产生:虽然这段代码:产生:你可以看到,一个完整的额外的指令用来移动1到x.这对于嵌入式系统来说至关重要.

  3. 如何使用命名管道从c调用WCF方法?

    更新:通过协议here,我无法弄清楚未知的信封记录.我在网上找不到任何例子.原版的:我有以下WCF服务我输出添加5行,所以我知道服务器是否处理了请求与否.我有一个.NET客户端,我曾经测试这一切,一切正常工作预期.现在我想为这个做一个非托管的C客户端.我想出了如何得到管道的名称,并写信给它.我从here下载了协议我可以写信给管道,但我看不懂.每当我尝试读取它,我得到一个ERROR_broKEN_P

  4. “这”是否保证指向C中的对象的开始?

    我想使用fwrite将一个对象写入顺序文件.班级就像当我将一个对象写入文件时.我正在游荡,我可以使用fwrite(this,sizeof(int),2,fo)写入前两个整数.问题是:这是否保证指向对象数据的开始,即使对象的最开始可能存在虚拟表.所以上面的操作是安全的.解决方法这提供了对象的地址,这不一定是第一个成员的地址.唯一的例外是所谓的标准布局类型.从C11标准:(9.2/20)Apointe

  5. c – 编译单元之间共享的全局const对象

    当我声明并初始化一个const对象时.两个cpp文件包含此标头.和当我构建解决方案时,没有链接错误,你会得到什么如果g_Const是一个非const基本类型!PrintInUnit1()和PrintInUnit2()表明在两个编译单元中有两个独立的“g_Const”具有不同的地址,为什么?

  6. 什么是C名称查找在这里? (&amp;GCC对吗?)

    为什么在第三个变体找到func,但是在实例化的时候,原始变体中不合格查找找不到func?解决方法一般规则是,任何不在模板定义上下文中的内容只能通过ADL来获取.换句话说,正常的不合格查找仅在模板定义上下文中执行.因为在定义中间语句时没有声明func,并且func不在与ns::type相关联的命名空间中,所以代码形式不正确.

  7. c – 在输出参数中使用auto

    有没有办法在这种情况下使用auto关键字:当然,不可能知道什么类型的.因此,解决方案应该是以某种方式将它们合并为一个句子.这可用吗?解决方法看起来您希望默认初始化给定函数期望作为参数的类型的对象.您无法使用auto执行此操作,但您可以编写一个特征来提取函数所需的类型,然后使用它来声明您的变量:然后你就像这样使用它:当然,只要你重载函数,这一切都会失败.

  8. 在C中说“推动一切浮动”的确定性方式

    鉴于我更喜欢将程序中的数字保留为int或任何内容,那么使用这些数字的浮点数等效的任意算术最方便的方法是什么?说,我有我想写通过将转换放在解析的运算符树叶中,无需将表达式转化为混乱是否可以使用C风格的宏?应该用新的类和重载操作符完成吗?解决方法这是一个非常复杂的表达.更好地给它一个名字:现在当您使用整数参数调用它时,由于参数的类型为double,因此使用常规的算术转换将参数转换为double用C11lambda……

  9. objective-c – 如何获取未知大小的NSArray的第一个X元素?

    在objectiveC中,我有一个NSArray,我们称之为NSArray*largeArray,我想要获得一个新的NSArray*smallArray,只有第一个x对象…

  10. c – Setprecision是混乱

    我只是想问一下setprecision,因为我有点困惑.这里是代码:其中x=以下:方程的左边是x的值.1.105=1.10应为1.111.115=1.11应为1.121.125=1.12应为1.131.135=1.14是正确的1.145=1.15也正确但如果x是:2.115=2.12是正确的2.125=2.12应为2.13所以为什么在一定的价值是正确的,但有时是错误的?请启发我谢谢解决方法没有理由期望使用浮点系统可以正确地表示您的帖子中的任何常量.因此,一旦将它们存储在一个双变量中,那么你所拥有的确切的一

返回
顶部