views:

927

answers:

9

Is it possible to find the second maximum number from an array of integers by traversing the array only once?

As an example, I have a array of five integers from which I want to find second maximum number. Here is an attempt I gave in the interview:

#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;
}

The interviewer, however, came up with the test case int arr[6]={5,4,3,2,1,0};, which prevents it from going to the if condition the second time. I said to the interviewer that the only way would be to parse the array two times (two for loops). Does anybody have a better solution?

+5  A: 

The easiest solution would be to use std::nth_element.

avakar
OK, explain how std::nth_element is implemented.
Svante
See http://en.wikipedia.org/wiki/Selection_algorithm and http://stackoverflow.com/questions/2392485/algorithm-for-nth-element
avakar
+1 Easiest, bug-free, optimized.
Jon-Eric
Deleted my answer since it was pretty much a dupe.
Robert Davis
Depending on how pivot is selected, it may be `O(N^2)` in the worst-case. The `O(N)`-guaranteed one pass algorithm, while not extensible, is better for this specific problem.
polygenelubricants
@polygenelubricants, can you elaborate? The problem is `O(n)` in worst-case. The page I linked to provides an `O(n)` worst-case algorithm.
avakar
Like I said, it depends on how the pivot is selected. The partition-based selection algorithm, just like the partition-based quick sort algorithm, can exhibit `O(N^2)` worst-case behavior unless you choose the pivot very carefully (wikipedia: "Like quicksort, the performance of the algorithm is sensitive to the pivot that is chosen. If bad pivots are consistently chosen, this degrades to the minimum-based selection described previously, and so can require as much as O(n^2) time"). On average in practice, though, even the potentially bad pivot selection algorithm is good enough.
polygenelubricants
The point I was missing is that the standard guarantees expected linear complexity for `std::nth_element`, not worst-case. As such, the implementation may indeed exhibit super-linear runtime.
avakar
+1  A: 
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];
    }
}

This works.

Pindatjuh
Fails for input {1,2,2}
TopCoder
I don't have any problems with it here.
Pindatjuh
it will overrun the bounds of the array when it gets to the 4th iteration of the array.
Robert Davis
+3  A: 

You need a second test:

 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];
   }
 }
Anders Abel
In the second test, make sure arr[i] != max; otherwise your max and second_max could wind up the same, which could be a problem depending on how you'd like things to work.
ty
Thanks for the comment, I've updated the code.
Anders Abel
Fails for input {1,2,2}
TopCoder
@TopCoder: What is the correct behaviour in case of two occurences of the max value? I don't think it is clearly stated in the question.
Anders Abel
You could change the first test to "arr[i]>=max" and remove the "arr[i] != max" from the second test. That would allow max and second_max to equal the same value, but only if that value is duplicated in the array. Depending on the particulars of the question, that may be a valid answer.
semiuseless
It fails because you've hard-coded 5 as the array length.
Robert Davis
+2  A: 

Here you are:

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;
}
Johann Gerell
This never pushes the first down to second place. Consider {5, 1, 7}.
John Marshall
Strange use of std::for_each.
pmr
@John: Thanks - fixed!
Johann Gerell
@pmr: "Strange use" - in what way?
Johann Gerell
@Johann: `std::for_each()` can't be used as a loop-statement - its a function. Try to compile your code and watch all those errors fly by. You probably meant to write `for` instead. *(side-note: even if there was a `for_each` statement, `++it` would contradict the intention)*
Georg Fritzsche
@gf: Ouch, the C++ days were too long ago... - fixed.
Johann Gerell
@Johann: As gf said, your use of std::for_each is simply wrong. You couldn't even define the block inside your for statement as a functor as it depends on values outside of it. It's just a minor flaw however. Just change std::for_each to for.
pmr
@pmr: Thanks - I had already changed it. :)
Johann Gerell
@Johann: I wrote the commented while you edited it. Didn't mean to keep on bashing onto that minor flaw :)
pmr
+10  A: 

Your initialization of max and second_max to -1 is flawed. What if the array has values like {-2,-3,-4}?

What you can do instead is to take the first 2 elements of the array (assuming the array has at least 2 elements), compare them, assign the smaller one to second_max and the larger one to max:

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

Then start comparing from the 3rd element and update max and/or second_max as needed:

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];
    }
}
codaddict
You're right about the negative numbers. However your code will give second_max == max if there are two occurences of the max number. Whether that is correct or not is not clearly stated in the question.
Anders Abel
I like the test for the first two elements prior to the for loop. There could even be a guard for the degenerate case of a two element array...and avoid the loop entirely.
semiuseless
@Anders: Exactly. For input like {1,2,3,3}. The def of second_max can vary. It can be just second_max or a second_max different from max. I've gone with the 1st one, as it makes sense (at least to me) and that is what even the library std::nth_element would do.
codaddict
A small optimization, you can compare the current array element in the loop with second_max first and then if that passes, then compare with max. So it would be like : if (arr[i] > second_max) { if (arr[i] > max) { second_max = max; max = arr[i]; } else if (arr[i] != max) { second_max = arr[i]; } }This will always give different values for max and second_max, if the array does not contain a single number throughout.
abhinav
+2  A: 

Your original code is okay, you just have to initialize the max and second_max variables. Use the first two elements in the array.

Hans Passant
+1  A: 

Quickselect is the way to go with this one. Pseudo code is available at that link so I shall just explain the overall algorithm:

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

This is quite obviously based on the good old quicksort algorithm.

Following this algorithm through, always selecting element zero as the pivot every time:

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

This will leave your array in an undefined order afterwards, it's up to you if that's a problem.

Martin
+1  A: 

Step 1. Decide on first two numbers.
Step 2. Loop through remaining numbers.
Step 3. Maintain latest maximum and second maximum.
Step 4. When updating second maximum, be aware that you are not making maximum and second maximum equal.

Tested for sorted input (ascending and descending), random input, input having duplicates, works fine.

#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;
}
Rajendra Kumar Uppal
+1  A: 

Other way to solve this problem, is to use comparisons among the elements. Like for example,

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

Compare 1,2 and say max = 2 and second max = 1

Now compare 3 and 4 and compare the greatest of them with max.

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

The advantage with this is, you are eliminating two numbers in just two comparisons.

Let me know, if you have any problem understanding this.

Algorist