How to find max. and min. in array using minimum comparisons?

后端 未结 14 1418
深忆病人
深忆病人 2020-12-04 09:08

This is a interview question: given an array of integers find the max. and min. using minimum comparisons.

Obviously, I can loop over the array twice and use ~

相关标签:
14条回答
  • 2020-12-04 09:41

    Compare in Pairs will work best for minimum comparisons

    # Initialization # 
    - if len(arr) is even, min = min(arr[0], arr[1]), max = max(arr[0], arr[1])
    - if len(arr) is odd, min = min = arr[0], max = arr[0]
    
    # Loop over pairs # 
    - Compare bigger of the element with the max, and smaller with min,
    - if smaller element less than min, update min, similarly with max.
    

    Total Number of comparisons -

    • For size = odd, 3(n - 1) / 2 where n is size of array
    • For size = even, 1 + 3*(n - 2)/2 = 3n/2 - 2

    Below is the python code for the above pseudo-code

    class Solution(object):
        def min_max(self, arr):
            size = len(arr)
            if size == 1:
                return arr[0], arr[0]
    
            if size == 2:
                return arr[0], arr[1]
    
            min_n = None
            max_n = None
            index = None
            if size % 2 == 0:       # One comparison
                min_n = min(arr[0], arr[1])
                max_n = max(arr[0], arr[1])
                st_index = 2
    
            else:
                min_n = arr[0]
                max_n = arr[0]
                st_index = 1
            for index in range(st_index, size, 2):
                if arr[index] < arr[index + 1]:
                    min_n = min(arr[index], min_n)
                    max_n = max(arr[index + 1], max_n)
                else:
                    min_n = min(arr[index + 1], min_n)
                    max_n = max(arr[index], max_n)
    
            return min_n, max_n
    
    0 讨论(0)
  • Brute-force is FASTER!

    I would love someone to show me the error of my ways, here, but, …

    I compared the actual run times of the brute-force method vs. the (more beautiful) recursive divide and conquer. Typical results (in 10,000,000 calls to each function):

    Brute force :
      0.657 seconds 10 values => 16 comparisons.  Min @ 8, Max @ 10
      0.604 seconds 1000000 values => 1999985 comparisons.  Min @ 983277, Max @ 794659
    Recursive :
      1.879 seconds 10 values => 13 comparisons.  Min @ 8, Max @ 10
      2.041 seconds 1000000 values => 1499998 comparisons.  Min @ 983277, Max @ 794659
    

    Surprisingly, the brute-force method was about 2.9 times faster for an array of 10 items, and 3.4 times faster for an array of 1,000,000 items.

    Evidently, the number of comparisons is not the problem, but possibly the number of re-assignments, and the overhead of calling a recursive function (which might explain why 1,000,000 values runs slower than 10 values).

    Caveats : I did this in VBA, not C, and I was comparing double-precision numbers and returning the index into the array of the Min and Max values.

    Here is the code I used (class cPerformanceCounter is not included here but uses QueryPerformanceCounter for high-resolution timing) :

    Option Explicit
    
    '2014.07.02
    
    Private m_l_NumberOfComparisons As Long
    
    Sub Time_MinMax()
    
       Const LBOUND_VALUES As Long = 1
    
       Dim l_pcOverall As cPerformanceCounter
       Dim l_d_Values() As Double
       Dim i As Long, _
           k As Long, _
           l_l_UBoundValues As Long, _
           l_l_NumberOfIterations As Long, _
           l_l_IndexOfMin As Long, _
           l_l_IndexOfMax As Long
    
       Set l_pcOverall = New cPerformanceCounter
    
       For k = 1 To 2
    
          l_l_UBoundValues = IIf(k = 1, 10, 1000000)
    
          ReDim l_d_Values(LBOUND_VALUES To l_l_UBoundValues)
    
          'Assign random values
          Randomize '1 '1 => the same random values to be used each time
          For i = LBOUND_VALUES To l_l_UBoundValues
             l_d_Values(i) = Rnd
          Next i
          For i = LBOUND_VALUES To l_l_UBoundValues
             l_d_Values(i) = Rnd
          Next i
    
          'This keeps the total run time in the one-second neighborhood
          l_l_NumberOfIterations = 10000000 / l_l_UBoundValues
    
          '——————— Time Brute Force Method —————————————————————————————————————————
          l_pcOverall.RestartTimer
    
          For i = 1 To l_l_NumberOfIterations
    
             m_l_NumberOfComparisons = 0
    
             IndexOfMinAndMaxDoubleBruteForce _
                   l_d_Values, _
                   LBOUND_VALUES, _
                   l_l_UBoundValues, _
                   l_l_IndexOfMin, _
                   l_l_IndexOfMax
    
          Next
    
          l_pcOverall.ElapsedSecondsDebugPrint _
                3.3, , _
                " seconds Brute-Force " & l_l_UBoundValues & " values => " _
                & m_l_NumberOfComparisons & " comparisons. " _
                & " Min @ " & l_l_IndexOfMin _
                & ", Max @ " & l_l_IndexOfMax, _
                True
          '——————— End Time Brute Force Method —————————————————————————————————————
    
          '——————— Time Brute Force Using Individual Calls —————————————————————————
          l_pcOverall.RestartTimer
    
          For i = 1 To l_l_NumberOfIterations
    
             m_l_NumberOfComparisons = 0
    
             l_l_IndexOfMin = IndexOfMinDouble(l_d_Values)
             l_l_IndexOfMax = IndexOfMaxDouble(l_d_Values)
    
          Next
    
          l_pcOverall.ElapsedSecondsDebugPrint _
                3.3, , _
                " seconds Individual  " & l_l_UBoundValues & " values => " _
                & m_l_NumberOfComparisons & " comparisons. " _
                & " Min @ " & l_l_IndexOfMin _
                & ", Max @ " & l_l_IndexOfMax, _
                True
          '——————— End Time Brute Force Using Individual Calls —————————————————————
    
          '——————— Time Recursive Divide and Conquer Method ————————————————————————
          l_pcOverall.RestartTimer
    
          For i = 1 To l_l_NumberOfIterations
    
             m_l_NumberOfComparisons = 0
    
             IndexOfMinAndMaxDoubleRecursiveDivideAndConquer _
                   l_d_Values, _
                   LBOUND_VALUES, _
                   l_l_UBoundValues, _
                   l_l_IndexOfMin, l_l_IndexOfMax
    
          Next
    
          l_pcOverall.ElapsedSecondsDebugPrint _
                3.3, , _
                " seconds Recursive   " & l_l_UBoundValues & " values => " _
                & m_l_NumberOfComparisons & " comparisons. " _
                & " Min @ " & l_l_IndexOfMin _
                & ", Max @ " & l_l_IndexOfMax, _
                True
          '——————— End Time Recursive Divide and Conquer Method ————————————————————
    
       Next k
    
    End Sub
    
    'Recursive divide and conquer
    Sub IndexOfMinAndMaxDoubleRecursiveDivideAndConquer( _
          i_dArray() As Double, _
          i_l_LBound As Long, _
          i_l_UBound As Long, _
          o_l_IndexOfMin As Long, _
          o_l_IndexOfMax As Long)
    
       Dim l_l_IndexOfLeftMin As Long, _
           l_l_IndexOfLeftMax As Long, _
           l_l_IndexOfRightMin As Long, _
           l_l_IndexOfRightMax As Long, _
           l_l_IndexOfMidPoint As Long
    
       If (i_l_LBound = i_l_UBound) Then 'Only one element
    
          o_l_IndexOfMin = i_l_LBound
          o_l_IndexOfMax = i_l_LBound
    
       ElseIf (i_l_UBound = (i_l_LBound + 1)) Then 'Only two elements
    
          If (i_dArray(i_l_LBound) > i_dArray(i_l_UBound)) Then
             o_l_IndexOfMin = i_l_UBound
             o_l_IndexOfMax = i_l_LBound
          Else
             o_l_IndexOfMin = i_l_LBound
             o_l_IndexOfMax = i_l_UBound
          End If
    
          m_l_NumberOfComparisons = m_l_NumberOfComparisons + 1
    
       Else 'More than two elements => recurse
    
          l_l_IndexOfMidPoint = (i_l_LBound + i_l_UBound) / 2
    
          'Find the min of the elements in the left half
          IndexOfMinAndMaxDoubleRecursiveDivideAndConquer _
                i_dArray, _
                i_l_LBound, _
                l_l_IndexOfMidPoint, _
                l_l_IndexOfLeftMin, _
                l_l_IndexOfLeftMax
    
          'Find the min of the elements in the right half
          IndexOfMinAndMaxDoubleRecursiveDivideAndConquer i_dArray, _
                l_l_IndexOfMidPoint + 1, _
                i_l_UBound, _
                l_l_IndexOfRightMin, _
                l_l_IndexOfRightMax
    
          'Return the index of the lower of the two values returned
          If (i_dArray(l_l_IndexOfLeftMin) > i_dArray(l_l_IndexOfRightMin)) Then
             o_l_IndexOfMin = l_l_IndexOfRightMin
          Else
             o_l_IndexOfMin = l_l_IndexOfLeftMin
          End If
    
          m_l_NumberOfComparisons = m_l_NumberOfComparisons + 1
    
          'Return the index of the lower of the two values returned
          If (i_dArray(l_l_IndexOfLeftMax) > i_dArray(l_l_IndexOfRightMax)) Then
             o_l_IndexOfMax = l_l_IndexOfLeftMax
          Else
             o_l_IndexOfMax = l_l_IndexOfRightMax
          End If
    
          m_l_NumberOfComparisons = m_l_NumberOfComparisons + 1
    
       End If
    
    End Sub
    
    Sub IndexOfMinAndMaxDoubleBruteForce( _
          i_dArray() As Double, _
          i_l_LBound As Long, _
          i_l_UBound As Long, _
          o_l_IndexOfMin As Long, _
          o_l_IndexOfMax As Long)
    
       Dim i As Long
    
       o_l_IndexOfMin = i_l_LBound
       o_l_IndexOfMax = o_l_IndexOfMin
    
       For i = i_l_LBound + 1 To i_l_UBound
    
          'Usually we will do two comparisons
          m_l_NumberOfComparisons = m_l_NumberOfComparisons + 2
    
          If (i_dArray(i) < i_dArray(o_l_IndexOfMin)) Then
    
             o_l_IndexOfMin = i
    
             'We don't need to do the ElseIf comparison
             m_l_NumberOfComparisons = m_l_NumberOfComparisons - 1
    
          ElseIf (i_dArray(i) > i_dArray(o_l_IndexOfMax)) Then
    
             o_l_IndexOfMax = i
    
          End If
       Next i
    
    End Sub
    
    Function IndexOfMinDouble( _
          i_dArray() As Double _
          ) As Long
    
       Dim i As Long
    
       On Error GoTo EWE
    
       IndexOfMinDouble = LBound(i_dArray)
    
       For i = IndexOfMinDouble + 1 To UBound(i_dArray)
    
          If (i_dArray(i) < i_dArray(IndexOfMinDouble)) Then
             IndexOfMinDouble = i
          End If
    
          m_l_NumberOfComparisons = m_l_NumberOfComparisons + 1
    
       Next i
    
       On Error GoTo 0
       Exit Function
    EWE:
       On Error GoTo 0
       IndexOfMinDouble = MIN_LONG
    End Function
    
    Function IndexOfMaxDouble( _
          i_dArray() As Double _
          ) As Long
    
       Dim i As Long
    
       On Error GoTo EWE
    
       IndexOfMaxDouble = LBound(i_dArray)
    
       For i = IndexOfMaxDouble + 1 To UBound(i_dArray)
    
          If (i_dArray(i) > i_dArray(IndexOfMaxDouble)) Then
             IndexOfMaxDouble = i
          End If
    
          m_l_NumberOfComparisons = m_l_NumberOfComparisons + 1
    
       Next i
    
       On Error GoTo 0
       Exit Function
    EWE:
       On Error GoTo 0
       IndexOfMaxDouble = MIN_LONG
    End Function
    
    0 讨论(0)
提交回复
热议问题