我当时正在申请职位,他们要求我为他们完成编码问题。我这样做并提交了,但是后来我发现我被该职位拒绝了。无论如何,我有一个折衷的编程背景,所以我不确定我的代码是否严重错误或者我是否没有最好的解决方案。我想发布我的代码并获得一些反馈。在开始之前,这里是对问题的描述:


您将得到一个排序的整数数组,例如{1, 2, 4, 4, 5, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 11, 13 }。现在,您应该编写一个程序(使用C或C ++,但我选择了C),该程序提示用户输入要搜索的元素。然后程序将搜索该元素。如果找到了它,则它应该返回找到该条目的第一个索引以及该元素的实例数。如果未找到该元素,则应返回“未找到”或类似的内容。这是一个简单的运行过程(我刚刚放置了数组): (所以我写了一个二进制搜索)。无论如何,我的代码基本上按如下方式运行:而不是数组中最大的元素。
如果是,则执行二进制搜索。
如果找到该元素,则编写了两个while循环。一个while循环将计数到找到的元素的左侧,第二个while循环将计数到找到的元素的右侧。当相邻元素与所需值不匹配时,循环终止。

EX:4,4,4,4,4

粗体4是二进制搜索所基于的值。一个循环将在其左侧进行检查,而另一个循环将在其右侧进行检查。它们的总和将是数字4的实例总数。

无论如何,我不知道我是否缺少任何先进的技术,或者我只是没有CS背景并且犯了一个大错误。任何建设性的批评将不胜感激!

Enter a number to search for: 4

4 was found at index 2.
There are 2 instances for 4 in the array.

Enter a number to search for: -4.

-4 is not in the array.


评论

作为开始的建议:“少用”

太糟糕了,您选择了C。std :: equal_range使这一操作变得非常容易。

不能将那个六向位中的三种情况重构为if(L == 0)* first = m;吗?并且第一个和最后一个案例看起来也可以折叠,因此只剩下三个案例-更好。

这段代码很好,绝对很好。请放心,您没有做任何令人讨厌的事情。我在此页面上看到的唯一有价值的建议是,可以将线性时间搜索起点和终点设置为对数时间,但即使我称之为“挑剔”。您的想法完全正确。振作起来,有很多“程序员”在面试中都把FizzBu​​zz弄错了(google it),而您显然不是其中之一。 :)

请写一个更好的标题,例如:“对排序数组中的元素进行搜索时必填。”它可以帮助其他人了解所有内容-无需阅读大型文章。

#1 楼

对于雇用为此代码示例提交此文档的人,我会有些担心。这就是我所看到的。

首先,解决总体设计问题,该算法是次优的,并且是最坏情况下的线性算法,而不是最坏情况下的对数算法,因为它不使用二进制搜索来查找元素,但是是线性元素。

其次,变量名(这对我来说真的是致命的)。其中大多数是一个或两个字母,因此,代码非常不易读。为变量提供描述性名称对于可维护性很重要。

第三,忽略标准库。除非指示您不要使用它们,否则您应该选择具有二进制搜索实现的标准库(例如stl或bsearch)。

第四,为什么get_num_of_ints返回-1对我来说具有神奇的价值;最好只将count设置为0并检查。

第五,get_num_of_ints太长了,并且尝试做太多事情。

第六(这是个人选择),我认为C ++和STL在这种情况下是更好的选择。

本着“显示,不告诉”的精神,这就是我如何编写赋值(未调试,未编译)(已编辑以匹配所需的函数签名)的方式:

#include <iostream>
#include <algorithm>

using namespace std;

// This function signature is required:
int get_num_of_ints(const int* searchBegin, size_t searchSize, int input,
    size_t* first, size_t* count) {
  const int* searchEnd = searchBegin + searchSize;
  const int* result = lower_bound(searchBegin, searchEnd, input);

  if (searchEnd == result || *result != input)
    return -1;

  *first = result - searchBegin;
  *count = upper_bound(result, searchEnd, input) - result;
  return 0;
}

void print_search_results(const int* searchBegin, size_t searchSize, int input) {
  size_t first;
  size_t count; 

  if (get_num_of_ints(searchBegin, searchSize, input, &first, &count) < 0) {
    cout << input << " is not in the array." << endl;
    return;
  }

  cout << input << " was found at index " << first << ". "
       << "There are " << count << " instances for " << input << " in the array."
       << endl;
}

bool read_input(int* input) {
  cout << "Enter a number to search for: ";
  bool succeeded = cin >> *input;
  cout << endl;    
  return succeeded;
}

int main (int argc, char** argv) {
  const int searchNumbers[] = {1, 2, 4, 4, 5, 8, 9, 9, 9, 9, 9, 9, 10, 10, 10, 11, 13};
  const int searchNumbersSize = sizeof(searchNumbers)/sizeof(searchNumbers[0]);

  while(1) {
     int input;
     if(!read_input(&input)) {
       count << "Bad input, exiting" << endl;
       return 1;
     }

     print_search_results(searchNumbers, searchNumbersSize, input);
  }
}


评论


\ $ \ begingroup \ $
确实在接口上施加了一些限制,但是您仍然可以拆分内部结构。例如,请使用binary_search函数,而不是将这些实现放入get_num_of_ints内部。
\ $ \ endgroup \ $
–托德·加德纳(To​​dd Gardner)
2010-3-14的3:10

\ $ \ begingroup \ $
在Steve McConnell编写的完整代码中可以找到很多这些要点,它涵盖了许多编码风格方面的知识,并在需要时提供了大量参考资料,供您深入研究。
\ $ \ endgroup \ $
– Emile Vrijdags
2010-3-14在23:30

\ $ \ begingroup \ $
尽管对计算复杂度的判断是正确的,但我必须将其设为-1。恕我直言,在简短的独立代码中对变量名进行抱怨。我不费吹灰之力地记住了少数几个简短的变量名,实际上发现减少的视觉混乱使其更易于阅读和理解。同样,尽管我同意在实际代码中尽可能使用标准库函数的想法,但尚不清楚它们是否在这里尝试测试工程知识或算法能力,并且押注后者恕我直言。
\ $ \ endgroup \ $
– j_random_hacker
2010年6月2日于17:53

\ $ \ begingroup \ $
@j_random_hacker:+1以抵消您的-1。在所提供的OP的代码中,变量名太可怕了,“简短的独立代码段”或其他。在非常有限的情况下,可以使用简短的变量名,例如循环计数器,小的函数(即少于8行)。与读时便利相比,读时便利性更高。
\ $ \ endgroup \ $
–用户
2011-10-28 18:56

\ $ \ begingroup \ $
由于使用了命名空间std,我几乎扣留了+1; -如果我正在招聘,那绝对值得进行对话!
\ $ \ endgroup \ $
– Toby Speight
17年12月14日在19:29

#2 楼

以我的经验,

if (condition)
    consequence;
statementx;
...


样式是一种地雷,只是在等待另一个(甚至相同的)开发人员将其扩展到:

if (condition)
    consequence1;
    consequence2;
statementx;
...


有些人可能会看到问题所在,但对于大多数程序员来说,这实际上是一个不可见的错误,因为即使缺少花括号,开发人员也倾向于通过缩进来解释代码,从而使result2无条件。

评论


\ $ \ begingroup \ $
+1-不确定为什么要投反对票,我已经看到太多这种情况的实例,以至于从来没有亲自在if语句上不使用方括号。我同意,作为开发人员,我们通常通过缩进而不是括号来读取代码。
\ $ \ endgroup \ $
–mynameiscoffey
2010-3-14在22:17

\ $ \ begingroup \ $
如果只有一种语言使用缩进来表示嵌套块,那么就此答案而言,“代码看起来如何”将始终与“代码含义”匹配。
\ $ \ endgroup \ $
–罗杰·佩特
2010-3-19在14:36

\ $ \ begingroup \ $
我同意您的建议,但是我认为这并不足以证明不雇用某人。 +0。
\ $ \ endgroup \ $
– j_random_hacker
2010年6月2日于17:10

\ $ \ begingroup \ $
从未来的4年开始,一个这样的地雷:由于这个问题,Apple的SSL实现跳过了一些安全检查。 imperialviolet.org/2014/02/22/applebug.html
\ $ \ endgroup \ $
–迈克
2014年3月11日15:34

\ $ \ begingroup \ $
罗杰·佩特(Richard Pate)摆弄了吧? Python在撰写评论时已经使用了大约9年,它使用缩进来表示嵌套块。
\ $ \ endgroup \ $
–雪体
17年12月14日在19:09

#3 楼

除了许多其他注释外,以下内容:

m = (hi + lo)/2;


是查找中间索引的错误方法。这会溢出。您应该这样做:

m = lo + (hi - lo) / 2;


其次,该行:

m=m;


无效,可以被淘汰。

评论


\ $ \ begingroup \ $
是的,但是我怀疑有人会因为忘记了这个错误而无法找到工作。
\ $ \ endgroup \ $
–伊丹·毛(Edan Maor)
2010-3-15的1:52

\ $ \ begingroup \ $
@Edan:我不太确定。像这样的“简单”问题具有多层复杂性。面试官可能已经问过这个问题,以弄清楚申请人的水平。即使不是这种意图,编写正确的代码还是有帮助的。也许要评论一下“这样做(而不是(hi + lo)/ 2)以避免溢出”的效果,然后,面试官会意识到申请人想到了面试官没有想到的事情! :-)
\ $ \ endgroup \ $
– Alok
2010-3-15的1:57

\ $ \ begingroup \ $
我敢肯定,提出这一点将为您带来很多好处。但是,这不是大约4年前发现的bug,在过去20年中,它几乎是二进制搜索的每个实现中的bug?我只是说,如果这是Java库中被发现之前的15年中的错误,我不会反对任何人自己去思考它。
\ $ \ endgroup \ $
–伊丹·毛(Edan Maor)
2010-3-15在8:51

\ $ \ begingroup \ $
@Edan Maor:甚至比这更好–根据googleresearch.blogspot.com/2006/06/…,著名的计算机科学家Jon Bentley在Programming Pearls中实现了包含此错误的二进制搜索。而且,如果您不雇用Bentley担任编程职位,您会雇用谁?
\ $ \ endgroup \ $
– j_random_hacker
2010年6月2日于17:14

\ $ \ begingroup \ $
@Thomas如果此错误在库中,您会说同样的话吗?
\ $ \ endgroup \ $
– Alok
14年6月20日在22:11

#4 楼

我来晚了一点,但这就像我可能希望在C ++中看到的一样。

已经解决了:)

评论


\ $ \ begingroup \ $
哇,这就是解决方案。先生,我向你致敬。您希望看到这种雄辩而简单的解决方案。击败了我自己制定的解决方案。
\ $ \ endgroup \ $
–小麦
2010-3-19在23:07

\ $ \ begingroup \ $
干净的解决方案。但是,总是很难知道他们是否想知道(a)您具有足够的图书馆知识来让equal_range()为您完成所有工作,或者(b)您具有算法能力来编写不间断的二进制搜索自己,因为两种能力都很有用。
\ $ \ endgroup \ $
– j_random_hacker
2010年6月2日于17:39

\ $ \ begingroup \ $
关于实现算法印章的观点,但是如果这是必需的,为什么不问?知道合适的工具来工作,可以使我们在实践中更有效。
\ $ \ endgroup \ $
–msandiford
2010年6月3日,下午2:51

\ $ \ begingroup \ $
完全同意,这是我总是会问的我是否可以做的事情,作为面试官,我会问一些成熟点。我只是觉得这种编码挑战是脱机完成的,可能不是一个选择。
\ $ \ endgroup \ $
– j_random_hacker
2010年6月3日,6:55

\ $ \ begingroup \ $
您提出了替代解决方案,但尚未检查代码。请对其进行编辑,以说明您的推理(解决方案的工作原理以及如何对原始解决方案进行改进),以便所有人都可以从您的思考过程中学习。
\ $ \ endgroup \ $
– Toby Speight
17年12月14日在19:27

#5 楼

随机观察结果:


二进制搜索应与“查找包含该索引的相同元素的最长运行时间”分离。

我会拒绝任何提交六路案例分析的候选人,以解决一个简单的问题,例如寻找跑步。如果将其拆分为自己的例程,则可能会找到一种更简单的方法来实现,例如

如果他们想要对数,那将是一个棘手的小问题-但在我看来,这是面试的首要问题。



评论


\ $ \ begingroup \ $
谢谢,我知道多个语句不是编写它的最佳方法,但是我无法弄清楚如何在分配时间后做得更好。顺便说一句,您能建议我可以用来帮助编写更好代码的实用资源吗?
\ $ \ endgroup \ $
– Micky
10 Mar 14 '10在2:44

\ $ \ begingroup \ $
@Micky:那里有很多好书。我可能会为您推荐Jon Bentley的《 Programming Pearls》,因为它非常着重于代码和代码方面。获得第二版。
\ $ \ endgroup \ $
–诺曼·拉姆齐(Norman Ramsey)
2010-3-14的3:35

#6 楼

我认为算法的最后一部分是次优的。您可以进行二进制搜索以找到与要查找的元素相等的最低和最高元素,然后减去地址以找到其中的多少。

如果有许多相同的元素,这样做会更好地进行缩放,但是对于面试问题更重要的是,它将使您的算法更简单。例如,“ 6个结果”代码非常繁琐,而拥有大量if-else通常被认为是代码气味。

评论


\ $ \ begingroup \ $
是的,例如,如果数组仅包含O(1)个不同的元素,则该算法会退化为O(n)性能。
\ $ \ endgroup \ $
–总统James K. Polk
10 Mar 14 '10在1:36

\ $ \ begingroup \ $
@small_duck。谢谢。我对正在编写的多个if..else if语句也有相同的感觉,但是由于时间紧迫,我只是用自己编写的混乱方式来做。您能否澄清一下“进行二进制搜索..”更多评论?
\ $ \ endgroup \ $
– Micky
2010-3-14的1:50

\ $ \ begingroup \ $
如果您进行了binary_search(requested_item-1)和binary_search(requested_item + 1)并记录了它们的位置(如果不存在,则搜索失败的位置),则可以从每个位置向后/向前扫描直到找到了实际要查找的元素。然后,您将拥有它的第一个和最后一个位置,这些位置可以用来平凡地计算实例数。这是O(nlogn)。有一些次优的情况,但我认为它们不会显着影响运行时。
\ $ \ endgroup \ $
–SoapBox
2010-3-14的2:22

\ $ \ begingroup \ $
@SoapBox我可能会丢失一些东西,但是为什么要麻烦找一个不同的元素,然后线性扫描直到该序列的结尾,而不是仅仅扫描原始序列?
\ $ \ endgroup \ $
–大卫·卡纳雷克(David Kanarek)
2010-3-14的3:38

\ $ \ begingroup \ $
+1 SoapBox(尽管复杂度为O(log(n)),对吧?)。米奇(Micky),诀窍在于改变您的二进制搜索,不仅找到该部分的一个元素,而且找到范围的最低元素。然后,您可以对数地找到序列的第一个和最后一个元素,简单的减法即可得出长度。在C ++中,标准库具有“ equal_range”,可以做到这一点。
\ $ \ endgroup \ $
–small_duck
10 Mar 14 '10在10:59

#7 楼

您的代码太复杂,无法执行。注释太多,变量命名不当,并且没有明确定义函数角色。
一些代码可以显示我期望得到的响应:

#include <stdio.h>

int binary_search( const int value, const int *arr, size_t start, size_t end ){
    if( value < arr[start] || value > arr[end] ){
        return -1;
    }

    while(start <= end){
        int pivot = (start+end) >> 1; 
        if(arr[pivot] == value){
            return pivot;
        }else if(arr[pivot] < value){
            start = pivot+1;
        } else if(arr[pivot] > value){
            end = pivot-1;
        } 
    }
    return -1;
}

int get_occurences( int begin, const int *arr, size_t max){
    int counter = 1;
    int cursor = begin;
    while ( (cursor+1) < max && arr[cursor] == arr[cursor+1]) {
        counter++;
        cursor++;
    }
    cursor = begin;
    while ( (cursor-1) > 0 && arr[cursor] == arr[cursor-1]) {
        counter++;
        cursor--;
    }
    return counter;
}


#define MAX 22
int main()
{   
    int value;
    int arr_sorted []={1,1,2,3,3,
                       4,4,4,4,5,
                       5,7,7,7,7,
                       8,8,8,9,11,
                       12,12};    
    size_t arr_size = MAX; // works also the other way               

    printf( "\nPlease input the integer you would like to find.\n" );
    scanf( "%d", &value );
    printf("Searching %d\n", value);
    int pos = binary_search( value, arr_sorted, 0, arr_size-1);

    if( pos == -1) {    
        printf( "%d has not been found.\n", value );
    } else{
        int howmany = get_occurences( pos, arr_sorted, arr_size);
        printf( "The first matching index is %d.\n", pos );
        printf( "The total number of instances is %d.\n", howmany );
    }

    return 0;
}


评论


\ $ \ begingroup \ $
虽然可以编写递归二进制搜索算法,但可以使用迭代算法,如果使用C可能更好。
\ $ \ endgroup \ $
– Yacoby
2010-3-14的1:34

\ $ \ begingroup \ $
@fabrizioM:您能提出(教学)书,材料或讲义来纠正我的错误吗?
\ $ \ endgroup \ $
– Micky
2010-3-14的1:51

\ $ \ begingroup \ $
该代码看起来相当简洁和易于理解,因此我怀疑缺少递归是一个因素。
\ $ \ endgroup \ $
–总统James K. Polk
10 Mar 14 '10 at 2:06

\ $ \ begingroup \ $
@GregS是个人喜好。作为审阅者,我不会接受该代码。作为第二个想法,我同意,递归不是一个因素。
\ $ \ endgroup \ $
– FabrizioM
10 Mar 14 '10在2:12

\ $ \ begingroup \ $
您的出现功能不起作用。二进制搜索并不总是返回数组中的第一个匹配项,因此您既需要向后搜索,也需要向前搜索。
\ $ \ endgroup \ $
–SoapBox
2010-3-14在2:19

#8 楼

是我一个人,还是我是唯一一个以完全不同的方式实现此目标的人?

查看问题的要求:


确定if元素存在
如果存在则返回第一次出现的数组索引
如果存在则返回出现次数

您必须在这样的问题之外思考。具体来说,我将通过在程序开始时将数组转储为哈希值来实现此目的的解决方案。哈希键是数字,值是包含第一次出现的索引和出现的总次数的结构。您可以在程序启动/初始化过程中设置此哈希,然后用户进行的任何后续查找都始终是固定时间的操作-BigO(1)。使用STL的C ++。

unordered_map(C ++)

二进制搜索只是解决此问题的错误方法。

编辑
<关于设置哈希的成本:

设置是恒定成本,仅产生一次。尽管我不会说这没关系,但在少数情况下确实如此。通常,当您拥有非常小的数据集或执行算法的次数很少时。除此之外,安装成本在算法的所有执行中摊销。需要n设置但在BigO(1)中执行的算法仍然胜过需要0设置但在BigO(log n)时间中执行的算法,除非执行的次数很少。

评论


\ $ \ begingroup \ $
“二进制搜索只是解决此问题的错误方法。” -似乎程序在运行时只寻找一个值。使用哈希表,您必须查找多个值以补偿创建表的开销。
\ $ \ endgroup \ $
– UncleBens
2010-3-14在11:39

\ $ \ begingroup \ $
@UncleBens:OP表示,他们明确要求的一件事是可扩展的解决方案。即使设置时间相当长,BigO(n)的可伸缩性也远不及BigO(1)。
\ $ \ endgroup \ $
– Robert S. Barnes
2010年3月14日在12:34

\ $ \ begingroup \ $
这实际上取决于要求。如果要求您基本上重新实现std :: equal_range(如此处所示),该函数接受排序的范围和值,那么构造哈希表可能不是可接受的解决方案(因为不可避免地,您会d必须为每次查询创建一个新表)。 -如果要求不同,并且您知道您使用的是同一阵列,那么情况可能会有所不同。 -此外,从排序数组构造std :: map对其执行二进制搜索,似乎完全浪费时间和资源。
\ $ \ endgroup \ $
– UncleBens
2010-03-14 12:40

\ $ \ begingroup \ $
设置将花费O(n),比二进制搜索的O(log n)还差
\ $ \ endgroup \ $
– swegi
10 Mar 14 '10 at 18:53

\ $ \ begingroup \ $
-1表示“二进制搜索只是解决此问题的错误方法”。正如UncleBens指出的那样,根据我们所没有的信息,这两种方法都可能(快得多),即要执行多少次搜索,因此此评论是没有根据的。 (还有一个事实,因为现在必须存储键和值,所以哈希表(至少)会增加两倍的内存需求。)如果您取消此注释,则将+1。
\ $ \ endgroup \ $
– j_random_hacker
2010年6月2日于17:31

#9 楼

在我看来,这足够合理。是的,也许某些STL方法可能会有所帮助。我唯一要批评的是您不验证输入的内容实际上是数字。

我仍然在这里看到不足以拒绝您进行此分配。也许这是采访的另一部分。也许他们没有拒绝您-他们接受了其他人。

评论


\ $ \ begingroup \ $
我完全同意。请放心,您(Micky)没有做任何令人讨厌的事情。我在此页面上看到的唯一有价值的建议是,可以将线性时间搜索起点和终点设置为对数时间,但即使我称之为“挑剔”。您的想法完全正确。有很多“程序员”在面试中都把FizzBu​​zz弄错了(google it),而您显然不是其中之一。 :)
\ $ \ endgroup \ $
– j_random_hacker
2010年6月2日于17:44

#10 楼

另一种可能性是C ++和C的选择本身就是一个测试:)也许他们愿意为具有C经验的人安顿下来,但会更喜欢具有C ++经验的人,所以这可能是一个因素。

如果您真的很好奇,您甚至可以再次与他们联系并寻求反馈。

但是,编程风格是您应该持续工作的,而不仅仅是准备面试。在那段时间内,您不会改变太多。我会进行更广泛的研究,并尝试获得一些C ++,C#和Java的经验,例如PHP或Perl,ruby或python,在编程方面持续不断地工作,也许能读一本有关面试的书或文章,然后继续写作。 ,然后继续申请。

我真的认为也许他们只是不喜欢您穿的衬衫:)

#11 楼

我想出的解决方案是\ $ O(\ log n)\ $。它涉及一个经过修改的二进制搜索,可以告诉该搜索始终进行准确的log n比较(如果找到匹配项,则不早破),并且根据参数的不同,它将继续尝试在左侧或右侧查找值。在数组中直到用尽为止。返回匹配数(最后一个索引-第一个索引+ 1)。我将pos变量作为指针传递,因此我们可以有效地“返回” 2个值-匹配数和运行中第一个匹配项的索引。

// returns the number of matches, and sets pos to the index of
// the first match, or -1 if none found
int findMatches(int array[], int size, int searchNum, int* pos)
{
   *pos = binarySearch(array, size, searchNum, -1);
   // if it was found, pos points to a positive number
   if(*pos >= 0)
   {
      int lastIdx = binarySearch(array, size, searchNum, 1);
      return lastIdx - *pos + 1;
   }
   return 0;
}


然后,我有了一个二进制搜索方法,它带有一个额外的参数direction,它指示您是要第一个二进制搜索索引(0),最早的(-1)还是最后一个(+1)。 >
int binarySearch(int array[], int size, int searchNum, int direction)
{
   int left = 0;
   int right = size - 1;
   int center;
   int pos = -1;

   while(left <= right)
   {
      center = (right + left) >> 1;

      if(array[center] == searchNum)
      {
         pos = center;
         // break early if we want to find the exact match
         if(direction == 0) break;
      }

      // adding 1 to searchNum means we will find the last in the run
      if(array[center] < searchNum + ((direction > 0) ? 1 : 0))
      {
         left = center + 1;
      }
      else
      {
         right = center - 1;
      }
   }

   return pos;
}


评论


\ $ \ begingroup \ $
您提出了替代解决方案,但尚未检查代码。请对其进行编辑,以说明您的推理(解决方案的工作原理以及如何对原始解决方案进行改进),以便所有人都可以从您的思考过程中学习。
\ $ \ endgroup \ $
– Toby Speight
17年12月14日在19:29

#12 楼

自从我使用C ++已经有很长时间了,所以我现在不能写它了,但是我知道STL中有一些算法可以使它工作很短。实施自己的二进制搜索可能会向潜在的雇主表明您是一个初学者。您可以实现它的事实很好-但是,当已经为您实现了这样的算法时,您会选择这样做,这可能会令他们沮丧。

评论


\ $ \ begingroup \ $
啊。我认为这里有一个二进制搜索算法,但没有“计数实例数”部分。更重要的是,它也不会返回第一索引。无论如何,我会再看一看。但是还是谢谢你!这是我下一个工作应用程序必须考虑的问题。
\ $ \ endgroup \ $
– Micky
2010年3月14日在1:30

\ $ \ begingroup \ $
也许吧,但这是一个棘手的问题。如果他使用过STL,那么其他人会说这证明他并不真正了解CS的基本算法,如果他需要STL中没有的东西,就会迷路。
\ $ \ endgroup \ $
–总统James K. Polk
2010-3-14的1:33

\ $ \ begingroup \ $
@Micky:如果您使用的是C ++,请查看std :: lower_bound。如果搜索返回的迭代器指向您搜索的值,则说明该值在容器中。然后,您可以向前走并计算容器中该值的实例数。
\ $ \ endgroup \ $
–詹姆斯·麦克奈利斯
2010-3-14的1:35

\ $ \ begingroup \ $
@James:或也加入std :: upper_bound,然后减去它们。如果我的STL生锈,至少是正确的。
\ $ \ endgroup \ $
–卡尔·曼纳斯特(Carl Manaster)
2010-3-14的1:43

\ $ \ begingroup \ $
或仅使用std :: equal_range
\ $ \ endgroup \ $
– UncleBens
10 Mar 14 '10在11:23

#13 楼

我会将二进制搜索作为一个单独的过程进行分解。

我也将输入数组(即使未要求使用该数组),或将其分解为可转换为可从文件读取的内容的输入过程。

我更喜欢camelCase约定和更有意义的变量名。但是,您的算法似乎还不错。您是否考虑过考试不是招聘决定的主要因素的可能性?几位申请人可能给出了同样不错的答案,并且根据其他标准做出了决定。

评论


\ $ \ begingroup \ $
我考虑过编写一个while循环来输入和排序数组。问题描述与我是否可以认为它已经输入和排序无关。在这一点上,我应该更加谨慎。您可能对“其他因素”的观点是正确的。我不得不提交一些有关各个主题的文章,这些文章也可能起到了作用。我只是怀疑我的应用程序的这一部分一定做错了,因为我有这样的背景。我主修数学与生物专业,所以我不确定自己的CS技能。我不知道雇主会期望些什么。谢谢
\ $ \ endgroup \ $
– Micky
2010年3月14日在1:38

\ $ \ begingroup \ $
不要流汗。甚至可能有些愚蠢,例如他不喜欢其他{,而更喜欢其他{。或者也许你穿的衬衫是:)
\ $ \ endgroup \ $
–拉里·渡边(Larry Watanabe)
2010-3-14在19:29

#14 楼

是否有特定要求在列表上进行二进制搜索?如果没有,为什么要这样做?不必要地使解决方案过于复杂是对您的打击。 *编辑:糟糕,您说您必须在问题中使用这种方式!没关系,继续*

为什么要使用单字符变量名?符号表中有足够的空间容纳更多描述性名称。那是另一个罢工。

评论太多了。我现在可以听到大家的声音:“他疯了吗?”。说真的该代码应尽可能说明一切。每次您开始写评论时,请思考“是否有一种方法可以使代码足够清晰以避免该评论?”。这是三点。

编码与其他人的交流与与机器的交流一样多。您的编码风格应反映出这一点。 Code Complete是一本很棒的书,其中包含许多有用的编码样式建议。阅读前几章,并使用他描述的技术再次编写解决方案的代码。比较两个解决方案,同时问自己“我想维护哪个?”。

评论


\ $ \ begingroup \ $
他之所以在列表上进行二进制搜索,是因为“他们评论说,我的代码应该可以很好地与大型数组一起扩展。”正确执行后,二进制搜索将在O(lg(n))时间处理此问题,而不是仅线性检查所需的O(n)时间。在这种情况下,使用二进制搜索不会使解决方案过于复杂。它正在使用适当级别的复杂性,以针对给定的需求获得更有效的解决方案。
\ $ \ endgroup \ $
– MBennett
10 Mar 14 '10在10:02

#15 楼

这就是我上交的内容,简单干净且易于阅读:

我对其进行了修改,以使用3亿个整数数组测试时序,即使在我的旧系统上,它也发现“最糟糕的情况” ”(数组末尾的值)大约一秒钟(在启动时花了6秒钟填充数组)。成熟优化”这个简单的面试问题。 :-)

做最简单的事情。编写代码,以便人们可以阅读它,而不必解密它。当然,如果需要,可以准备讨论性能替代方案。

最新编辑:添加了FindStartingPoint()函数以优化速度。


评论


\ $ \ begingroup \ $
这只是对需要O(n)时间的数组的线性搜索。您绝对可以使用二进制搜索为此花费O(lg(n))的时间。鉴于原始帖子中的内容,Micky写道:“他们评论说我的代码应该可以很好地使用大型数组进行扩展。”我觉得这根本不会给人留下深刻的印象。
\ $ \ endgroup \ $
– MBennett
2010-3-14在9:54

\ $ \ begingroup \ $
我同意代码应该可读,但是二进制搜索算法也可以可读。编写可读代码并不意味着编写最笨拙,效率最低的东西。如果问题说要对一个巨大的数组进行排序,您是否会因为它是最简单的算法而选择冒泡排序?这实际上就是您在这里所做的。
\ $ \ endgroup \ $
–Ricket
2010-3-14在21:42

\ $ \ begingroup \ $
重点是首先要做最简单的事情(线性搜索)。这花费了5分钟的代码,并且可以轻松地用于我测试过的多达3亿个int数组。如果这已经足够好了,也就是说,他们对“大数组”的定义就不那么了-您就完成了。如果没有,请改进算法(花更多的时间),因为我很无聊,所以我用最新的编辑来做。 :-)
\ $ \ endgroup \ $
–罗恩·萨维奇(Ron Savage)
2010-3-14在23:10

\ $ \ begingroup \ $
//下一行分配10亿个整数数组:size_t listSize = 300000000;嗯...您的评论吓到我了。
\ $ \ endgroup \ $
– Ponkadoodle
2010-3-15的1:10

\ $ \ begingroup \ $
@Ron Savage-如果我将代码作为工作的一部分来工作,那么我同意您首先要做最简单的事情。但是,如果您在面试中被问到一个问题,并且他们提出要大数组扩展它的观点,那么对我来说,他们似乎很希望他们实现二进制搜索。我至少会问他们一个O(n)解决方案是否适合他们的要求。
\ $ \ endgroup \ $
–伊丹·毛(Edan Maor)
2010-3-15的1:57

#16 楼

略大于一个“ nitpick”,但可能还不足以排除您的问题:

为什么要在三个详尽的else语句中显式测试最终的if ... else情况?通常,他们应该有另一个else语句来捕获任何可能的意外情况,或者在所有这些情况下,您都应该注释掉最后一个if子句:

if( a < 0)    
    printf( "%d has not been found.\n", N );
else //if(a >= 0)
{ ...


if(arr[m] < N)
    lo = m+1;
else if(arr[m] > N)
    hi = m-1;
else //if(arr[m]==N)
{ ...

    if( j > 0 && L > 0 )
        *first=j+1;
    else if( j==0 && L==0)
        *first=m;
    else if( j > 0 && L==0 )
        *first=m;
    else if(j < 0 && L==0 )
        *first=m; 
    else if( j < 0 && L > 0 )
        *first=0;
    else //if( j=0 && L > 0 )
        *first=j+1;


还要注意最后一种情况的错别字。

我确实调整了第一种情况,以明确确保没有要添加的else案子,这对于最后一种情况也是有效的选择,但其他人也有提到无论如何这里还有更多合并。

顺便说一句,顺便说一句,我不要重复使用您的单句then / else子句作为对此的认可;那不是我打算讨论的。

#17 楼

您的解决方案有效,但是,如先前的回答所述,它在最坏的情况下线性时间运行。您可以通过将C ++标准库中的两个函数重写为C:来将其减少到最坏情况的对数,即std :: lower_bound和std :: upper_bound。 std::lower_bound会将“迭代器”(C中的指针,称为const int* res)返回到第一个匹配元素(值为N)。如果没有,我们将有N != *res。如果指针正确,则执行std::upper_bound,匹配元素的数量将只是std::upper_boundstd::lower_bound的结果之间的差。另外,关于API,我建议您在不匹配的情况下将零值插入count。将所有零件放在一起,您可能会获得以下内容:

#include <stdio.h>

static const int* upper_bound(const int* begin,
                              const int* end,
                              const int value)
{
    size_t count;
    size_t step;
    const int* iter;
    count = end - begin;

    while (count > 0)
    {
        iter = begin + (step = (count >> 1));

        if (*iter <= value)
        {
            begin = iter + 1;
            count -= step + 1;
        }
        else
        {
            count = step;
        }
    }

    return begin;
}

static const int* lower_bound(const int* begin,
                              const int* end,
                              const int value)
{
    size_t count;
    size_t step;
    const int* iter;
    count = end - begin;

    while (count > 0)
    {
        iter = begin + (step = (count >> 1));

        if (*iter < value) /* upper_bound compares "*iter <= value" */
        {
            begin = iter + 1;
            count -= step + 1;
        }
        else
        {
            count = step;
        }
    }

    return begin;
}

void get_number_of_ints(const int* array,
                       size_t array_length,
                       int target_value,
                       size_t* first_index,
                       size_t* count)
{
    const int* iter1;
    const int* iter2;

    iter1 = lower_bound(array, array + array_length, target_value);

    if (*iter1 != target_value)
    {
        /* No match. */
        *count = 0;
        return;
    }

    iter2 = upper_bound(array, array + array_length, target_value);
    *first_index = (size_t)(iter1 - array);
    *count = (size_t)(iter2 - iter1);
}

int main()
{
    int N;                                 /* input variable */
    int arr[]={1,1,2,3,3,4,4,4,4,5,5,7,7,7,7,8,8,8,9,11,12,12}; /* sorted */
    size_t r = sizeof(arr)/sizeof(arr[0]); /* right bound */
    size_t first;                          /* first match index */
    size_t count;                          /* total number of matches */

    /* prompts the user to enter input */

    printf( "\nPlease input the integer you would like to find.\n" );
    scanf( "%d", &N );

    get_number_of_ints(arr, r, N, &first, &count);

    if (count == 0)
    {
        printf("%d not found!\n", N);
    }
    else
    {
        printf("%d found at %zu, length %zu.\n", N, first, count);
    }

    return 0;
}


#18 楼

在我们甚至没有讨论接口和算法之前,有一些问题要解决:就我所知,不需要<string.h><stddef.h>(请注意,<stdlib.h>必须提供size_t ,如果这就是为什么要包括后者的原因)。
int main()是非原型定义;请改用int main(void)
无法检查scanf()是否成功转换了值。缺少检查是一个严重的错误。如果scanf()在此处未返回1,则需要采取纠正措施(再次输入并再次提示,或者仅向stderr写入一条消息并返回EXIT_FAILURE)。我们应该改用%d

对于size_t函数,由于使用了%zd变量(实际上应该是get_num_of_ints()),因此隐藏了许多缩小和符号转换的转换(不仅仅是盲目转换,因为当int为零时减法会溢出。)

老实说,我不明白为什么您要从头开始实现自己的二进制搜索,而不是简单地使用标准库size_t

哦,函数名称极易产生误导,因为返回值不是匹配数;这是第一场比赛的索引。没有必要重复这些。