【问题标题】:How can we find second maximum from array efficiently?我们如何有效地从数组中找到第二个最大值?
【发布时间】:2011-01-24 11:05:22
【问题描述】:

是否可以通过只遍历数组一次从整数数组中找到第二个最大数?

例如,我有一个包含五个整数的数组,我想从中找到第二个最大数。这是我在采访中的一次尝试:

#define MIN -1
int main()
{
    int max=MIN,second_max=MIN;
    int arr[6]={0,1,2,3,4,5};
    for(int i=0;i<5;i++){
        cout<<"::"<<arr[i];
    }
    for(int i=0;i<5;i++){
        if(arr[i]>max){
            second_max=max;
            max=arr[i];          
        }
    }
    cout<<endl<<"Second Max:"<<second_max;
    int i;
    cin>>i;
    return 0;
}

然而,面试官提出了测试用例int arr[6]={5,4,3,2,1,0};,这阻止了它第二次进入if 条件。 我对面试官说,唯一的方法是解析数组两次(两个for 循环)。有人有更好的解决方案吗?

【问题讨论】:

  • 在寻找第二个最大值的过程中,数组是否被重新排序?
  • 使用Heap 结构代替array 或使用sorted array 结构

标签: c++ arrays algorithm


【解决方案1】:

您将maxsecond_max 初始化为-1 存在缺陷。如果数组有{-2,-3,-4} 之类的值怎么办?

您可以做的是取数组的前 2 个元素(假设数组至少有 2 个元素),比较它们,将较小的分配给 second_max,将较大的分配给 max:

if(arr[0] > arr[1]) {
 second_max = arr[1];
 max = arr[0];
} else {
 second_max = arr[0];
 max = arr[1];
}

然后从第三个元素开始比较并根据需要更新max 和/或second_max

for(int i = 2; i < arr_len; i++){
    // use >= n not just > as max and second_max can hav same value. Ex:{1,2,3,3}   
    if(arr[i] >= max){  
        second_max=max;
        max=arr[i];          
    }
    else if(arr[i] > second_max){
        second_max=arr[i];
    }
}

【讨论】:

  • 您对负数是正确的。但是,如果最大数量出现两次,您的代码将给出 second_max == max。问题中没有明确说明这是否正确。
  • 我喜欢在 for 循环之前测试前两个元素。甚至可以对两个元素数组的退化情况进行保护......并完全避免循环。
  • @Anders:没错。对于像 {1,2,3,3} 这样的输入。 second_max 的 def 可以变化。它可以只是 second_max 或与 max 不同的 second_max。我选择了第一个,因为它是有道理的(至少对我而言),甚至图书馆 std::nth_element 也会这样做。
  • 一个小优化,可以先将循环中的当前数组元素与second_max进行比较,如果通过,再与max进行比较。所以它会像: if (arr[i] > second_max) { if (arr[i] > max) { second_max = max;最大值 = arr[i]; } else if (arr[i] != max) { second_max = arr[i];如果数组始终不包含单个数字,这将始终为 max 和 second_max 提供不同的值。
【解决方案2】:

最简单的解决方案是使用std::nth_element

【讨论】:

  • 好的,解释一下std::nth_element是如何实现的。
  • 删除了我的答案,因为这几乎是一个骗局。
  • 取决于如何选择枢轴,在最坏的情况下可能是O(N^2)O(N)-保证一次通过算法虽然不可扩展,但更适合此特定问题。
  • @polygenelubricants,你能详细说明一下吗?在最坏的情况下,问题是O(n)。我链接到的页面提供了O(n) 最坏情况算法。
【解决方案3】:

您需要进行第二次测试:

 for(int i=0;i<5;i++){  
   if(arr[i]>max){  
     second_max=max;  
     max=arr[i];            
   }
   else if (arr[i] > second_max && arr[i] != max){
     second_max = arr[i];
   }
 }

【讨论】:

  • 在第二个测试中,确保 arr[i] != max;否则你的 max 和 second_max 可能会相同,这可能是一个问题,具体取决于你希望事情如何工作。
  • @TopCoder:如果出现两次最大值,正确的行为是什么?我认为问题中没有明确说明。
  • 您可以将第一个测试更改为“arr[i]>=max”,并从第二个测试中删除“arr[i] != max”。这将允许 max 和 second_max 等于相同的值,但前提是该值在数组中重复。根据问题的具体情况,这可能是一个有效的答案。
【解决方案4】:

您的原始代码没问题,您只需初始化 max 和 second_max 变量。使用数组中的前两个元素。

【讨论】:

    【解决方案5】:

    你在这里:

    std::pair<int, int> GetTwoBiggestNumbers(const std::vector<int>& array)
    {
        std::pair<int, int> biggest;
        biggest.first = std::max(array[0], array[1]);  // Biggest of the first two.
        biggest.second = std::min(array[0], array[1]); // Smallest of the first two.
    
        // Continue with the third.
        for(std::vector<int>::const_iterator it = array.begin() + 2;
            it != array.end();
            ++it)
        {
            if(*it > biggest.first)
            {
                biggest.second = biggest.first;
                biggest.first = *it;
            }
            else if(*it > biggest.second)
            {
                biggest.second = *it;
            }
        }
    
        return biggest;
    }
    

    【讨论】:

    • 这永远不会将第一名推到第二名。考虑 {5, 1, 7}。
    • std::for_each 的奇怪用法。
    • @pmr:“奇怪的使用”——以什么方式?
    • @Johann: std::for_each() 不能用作循环语句——它是一个函数。尝试编译您的代码并观察所有这些错误。您可能打算改为写for(旁注:即使有 for_each 声明,++it 也会与意图相矛盾)
    • @gf:哎呀,C++ 时代太久远了...... - 已修复。
    【解决方案6】:

    Quickselect 是这个方法。该链接提供了伪代码,因此我将解释整个算法:

    QuickSelect for kth largest number:
        Select a pivot element
        Split array around pivot
        If (k < new pivot index)
           perform quickselect on left hand sub array
         else if (k > new pivot index)
           perform quickselect on right hand sub array (make sure to offset k by size of lefthand array + 1)
         else
           return pivot
    

    这显然是基于良好的旧快速排序算法。

    遵循这个算法,每次总是选择元素零作为枢轴:

    select 4th largest number:
    1) array = {1, 3, 2, 7, 11, 0, -4}
    partition with 1 as pivot
    {0, -4, _1_, 3, 2, 7, 11}
    4 > 2 (new pivot index) so...
    
    2) Select 1st (4 - 3) largest number from right sub array
    array = {3, 2, 7, 11}
    partition with 3 as pivot
    {2, _3_, 7, 11}
    1 < 2 (new pivot index) so...
    
    3) select 1st largest number from left sub array
    array = {2}
    
    4) Done, 4th largest number is 2
    

    这将使您的数组在之后保持未定义的顺序,是否有问题由您决定。

    【讨论】:

      【解决方案7】:

      第 1 步。确定前两个数字。
      第 2 步:循环遍历剩余的数字。
      步骤 3. 保持最新的最大值和第二个最大值。
      第 4 步:更新第二个最大值时,请注意您没有使最大值和第二个最大值相等。

      经过排序输入(升序和降序)、随机输入、重复输入的测试,工作正常。

      #include <iostream>
      #define MAX 50
      int GetSecondMaximum(int* data, unsigned int size)
      {
          int max, secmax;
          // Decide on first two numbers
          if (data[0] > data[1])
          {
              max = data[0];
              secmax = data[1];
          }
          else
          {
              secmax = data[0];
              max = data[1];
          }
          // Loop through remaining numbers
          for (unsigned int i = 2; i < size; ++i)
          {
              if (data[i] > max)
              {
                  secmax = max;
                  max = data[i];
              }
              else if (data[i] > secmax && data[i] != max/*removes duplicate problem*/)
                  secmax = data[i];
          }
          return secmax;
      }
      int main()
      {
          int data[MAX];
          // Fill with random integers
          for (unsigned int i = 0; i < MAX; ++i)
          {
              data[i] = rand() % MAX;
              std::cout << "[" << data[i] << "] "; // Display input
          }
          std::cout << std::endl << std::endl;
          // Find second maximum
          int nSecondMax = GetSecondMaximum(data, MAX);
          // Display output
          std::cout << "Second Maximum = " << nSecondMax << std::endl;
          // Wait for user input
          std::cin.get();
          return 0;
      }
      

      【讨论】:

        【解决方案8】:

        解决此问题的其他方法是使用元素之间的比较。比如,

        a[10] = {1,2,3,4,5,6,7,8,9,10}
        

        比较 1,2 并说 max = 2 和第二个 max = 1

        现在比较 3 和 4 并将它们中的最大值与最大值进行比较。

        if element > max
             second max = max
             element = max
        else if element > second max
             second max = element
        

        这样做的好处是,您只需在两次比较中消除两个数字。

        如果您对此有任何疑问,请告诉我。

        【讨论】:

        • 错别字.. 你需要 max = element.
        【解决方案9】:

        检查此解决方案。

        max1 = a[0];
        max2 = a[1];
        
        for (i = 1; i < n; i++)
        {
            if (max1 < a[i])
            {
                max2 = max1;
                max1 = a[i];
            }
        
            if (max2 == max1) max2 = a[i + 1];
        
            if (max2 == a[n])
            {
                printf("All numbers are the same no second max.\n");
                return 0;
            }
        
            if (max2 < a[i] && max1 != a[i]) max2 = a[i];
        }
        

        【讨论】:

        • 如果 a[0]=a[1] = max 会发生什么?那我觉得有问题
        【解决方案10】:

        这里有一些可行的方法,

        public static int secondLargest(int[] a){
            int max=0;
            int secondMax=0;
        
            for(int i=0;i<a.length;i++){
                if(a[i]<max){
                    if(a[i]>secondMax){
                        secondMax=a[i];
                    }
                    continue;
                }
        
                if(a[i]>max){
                    secondMax=max;
                    max=a[i];
                }
        
            }
            return secondMax;
        }
        

        【讨论】:

          【解决方案11】:

          上限应该是 n+log2⁡n−2,但在随机选择算法的情况下它大于 O(n),但在最坏的情况下它要小得多。解决方案可能是

          1. 构建一棵树,以查找具有 n - 1 个比较的 MAX 元素

            最大(N) / \ 最大(N/2) 最大(N/2)

          2. 去掉MAX再找MAX log2n-1对比

          PS。它使用额外的内存,但在最坏的情况下它比随机选择算法更快。

          【讨论】:

            【解决方案12】:

            我们不能按降序排序并从排序后的数组中取出第二个元素吗?

            【讨论】:

              【解决方案13】:

              下面的怎么样。 make_heap 是 O(n) 所以这是有效的,这是 1-pass 我们利用它必须是具有最大值的父堆子堆之一的优势来找到第二个最大值。

              #include <algorithm>
              #include <iostream>
              
              int main()
              {
                  int arr[6]={0,1,2,3,4,5};
              
                  std::make_heap(arr, arr+6);
                  std::cout << "First Max: " << arr[0] << '\n';
                  std::cout << "Second Max: " << std::max(arr[1], arr[2]) << '\n';
                  return 0;
              }
              

              【讨论】:

                【解决方案14】:
                int max,secondMax;
                max=secondMax=array[0];
                                                                for(int i=0;i<array.length;i++)
                    {                                                   if(array[i]>max)                                                    {                                           max=array[i];                                                   }
                                                                        if(array[i]>secondMax && array[i]<max)                                                  {
                    secondMax=array[i];                                                 }
                    }
                

                【讨论】:

                  【解决方案15】:
                  #include <iostream>
                  using namespace std;
                  
                  int main() {
                  
                     int  max = 0;
                      int sec_Max = 0;
                  
                      int array[] = {81,70,6,78,54,77,7,78};
                  
                      int loopcount = sizeof(array)/sizeof(int);
                  
                      for(int i = 0 ; i < loopcount ; ++i)
                      {
                  
                          if(array[i]>max)
                          {
                              sec_Max = max;
                              max = array[i];
                          }
                  
                          if(array[i] > sec_Max && array[i] < max)
                          {
                              sec_Max = array[i];
                          }
                      }
                  
                      cout<<"Max:" << max << " Second Max: "<<sec_Max<<endl;
                  
                      return 0;
                  }
                  

                  【讨论】:

                  • 多年前的这个问题已经得到了广泛的回答,您的问题没有添加任何新内容。此外,除了您的代码之外,对于您所做的 OP 没有做的事情,没有任何评论。
                  【解决方案16】:
                  // Set the first two different numbers as the maximum and second maximum numbers
                  
                   int max = array[0];
                   int i = 1;
                  //n is the amount of numbers
                  
                   while (array[i] == max && i < n) i++;
                   int sec_max = array[i];
                   if( max < sec_max ) {
                      tmp = sec_max;
                      sec_max = max;
                      max = tmp;
                   }
                  
                  //find the second maximum number
                  
                   for( ; i < n; ++i ) {
                     if( array[i] > max ) {
                       sec_max = max;
                       max = array[i];
                     } else if( array[i] > sec_max && array[i] != max ) {
                       sec_max = array[i];
                     }
                   }
                   printf("The second maximum number is %d\n", sec_max);
                  

                  【讨论】:

                  • 欢迎您,在这里,解释为什么要使用您的解决方案而不只是如何使用是一个很好的做法。这将使您的答案更有价值,并帮助进一步的读者更好地理解您是如何做到的。我还建议您查看我们的常见问题解答:stackoverflow.com/faq
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-03-08
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多