给定一个旋转的排序数组,我怎样才能找到该数组中的最大值?

Posted

技术标签:

【中文标题】给定一个旋转的排序数组,我怎样才能找到该数组中的最大值?【英文标题】:Given a rotated sorted array, how can I find the largest value in that array? 【发布时间】:2015-03-12 01:59:34 【问题描述】:

我对此进行了很多思考,但无法找到最佳解决方案。我正在准备技术面试,但是我没有找到很多与这个问题相关的东西。我的第一步是实现一个简单的 O(n) 算法,该算法搜索整个数组以找到最大整数。现在我知道我可以做得比这更好,所以我想也许有一种方法可以使用二分搜索,或者利用数组至少有一半是完全排序的这一事实。也许您可以找到中间值并将其与数组的开头和结尾进行比较。

示例:

[5, 7, 11, 1, 3] 将返回 11。

[7, 9, 15, 1, 3] 将返回 15。

【问题讨论】:

好的,继续你的思路。回答面试问题不是要知道答案,而是要解决问题。 看看这个:***.com/questions/1878769/… 1.找到最小值。 2.返回左边的值。 我认为最佳复杂度是 O(log(n))。读取第一个、中间和最后一个值。如果 first 您的第一个数组似乎不符合条件。该数组应该是一个经过排序然后可能旋转的数组,对吗?在这种情况下,“排序”数组是[1, 5, 7, 6, 4, 10, 11],它没有排序——对吧?还是我误解了这个问题?如果我误解了,我认为您需要更好地解释数组的约束是什么。 【参考方案1】:

在一个排序好的数组中(即使是旋转的),你可以确保使用二分查找(O(log2(n)))。


/**
* Time complexity: O(log2(n))
* Space complexity: O(1)
*
* @param nums
* @return
*/
public int findMax(int[] nums) 
        // binary search
        int left = 0;
        int right = nums.length - 1;

        while (left < right) 
            int mid = left + (right - left) / 2;
            if (nums[left] < nums[mid]) 
                left = mid;
             else if (nums[left] > nums[mid]) 
                right = mid - 1;
             else     
                // subtility here if there are duplicate elements in the array.
                // shift the left linearly
                left = left + 1;
            
        
        return nums[left];
    

【讨论】:

(在“非旋转”非降序的值数组中,我可能会做得更快。)【参考方案2】:

您必须以一种巧妙的方式进行二分搜索才能实现 O(lg n) 界限。观察到最大元素右侧的元素是最小值(如果数组根本不旋转,则为无)。因此,请进行常规二进制搜索,但检查索引 mid 处的元素是否为最大值,如果不比较每个左/右子数组中的第一个和最后一个元素。如果first&lt;last在左子数组中,则知道左子数组已排序并向右走,否则向左走。

假设数组名为 a,它有 n 个元素。

/* check if not rotated at all */
int ans = INFINITY;
if(a[0] < a[n-1] || n == 1)
   ans = a[n-1];
    return;


/* array is certainly rotated */
int l = 0, r = n-1;
while(r - l > 5)
   int m = (l + r) / 2;
    if(a[m] > a[m+1])  ans = a[m]; break; 
    else
       if(a[l] < a[m-1]) l = m+1;
        else r = m-1;
    


/* check the remaining elements (at most 5) in a loop */
if(ans == INFINITY)
   for(int i = l; i <= r; i++)
       ans = max(ans, a[i]);
    

我没有测试过这段代码。我在元素数量为 5 或更少时中断的原因是确保任一子数组中的元素数量至少为 2(因此您可以确定第一个和最后一个不是同一个元素)。如果有什么要修复的,你必须自己尝试并修复它。希望这会有所帮助。

【讨论】:

我认为这是正确的答案,但为什么要明确检查“未旋转”的情况?不是包含在 bsearch 部分吗? 我没有测试代码,可能这段代码是多余的,但无论如何它是一个恒定的工作量。【参考方案3】:

在每个步骤中使用修改后的二分搜索消除一半的排序子数组(如果有两个排序子数组删除“较低”子数组),同时跟踪可能更新的最大值。

#include <iostream>
#include <cstdlib>
#include <vector>

int main(int argc, char** argv)
   
    std::vector<int> nums;
    for(int i = 1; i < argc; i++)
        nums.push_back(atoi(argv[i]));

    int start = 0;
    int end = argc - 2;
    int max = nums[start];
    while(start <= end) 
        int mid = (start + end) >> 1;
        int cand;
        if(nums[start] <= nums[mid])  
            start = mid + 1;
         else 
            end = mid - 1;
        
        cand = nums[mid];
        if(cand > max)
           max = cand;
    
    std::cout << max << std::endl;
    return 0;

【讨论】:

【参考方案4】:

问题:在旋转的排序数组中找到最大的。该数组没有任何重复项: 解决方案:使用二分搜索。 理念:永远记住在有序旋转数组中,最大的元素总是在数组的左边。同样,最小的元素将始终位于数组的右侧。 代码是:

public class Test19 

    public static void main(String[] args) 
        int[] a =  5, 6, 1, 2, 3, 4 ;
        System.out.println(findLargestElement(a));

    

    private static int findLargestElement(int[] a) 

        int start = 0;
        int last = a.length - 1;

        while (start + 1 < last) 

            int mid = (last - start) / 2 + start;
            if (mid < start) 
                mid = start;
            
            if (mid > start) 
                last = mid - 1;
             else 
                mid--;
            

         // while

        if (a[start] > a[last]) 
            return a[start];
         else
            return a[last];

    


【讨论】:

【参考方案5】:

我提出的解决方案既紧凑又高效。 它基本上是二分搜索算法的衍生产品。

int maxFinder(int[] array, int start, int end)
  
    //Compute the middle element
    int mid = (start + end) / 2;

    //return the first element if it's a single element array
    //OR
    //the boundary pair has been discovered.
    if(array.length == 1 || array[mid] > array[mid + 1])
    return mid;

    //Basic Binary Search implementation
    if(array[mid] < array[start])
    return maxFinder(array, start, mid - 1);
    else if(array[mid] > array[end])
    return maxFinder(array, mid + 1, end);

    //Return the last element if the array hasn't been rotated at all.
    else
    return end;
  

【讨论】:

上述程序适用于单元素数组和具有重复元素的数组。 0, 0, 1, 0 ?这将返回索引:查看问题中的示例。【参考方案6】:

说到使用二分查找来解决这个问题,时间复杂度为 O(log2n)。我会这样做

#include<stdio.h>
#define ARRSIZE 200 
int greatestElement(int* , int ) ;

int main()  
    int arr[ARRSIZE] ; 
    int n ; 
    printf("Enter the number of elements you want to enter in the array!") ;
    scanf("%d" , &n) ;
    printf("Enter the array elements\n") ;
    for(int i = 0 ; i < n ; i++)    
        scanf("%d", &arr[i]) ;
    
    printf("%d is the maximum element of the given array\n",greatestElement(arr,n)) ;


int greatestElement(int* arr, int n)    
    int mid = 0 ;
    int start = 0 , end = n-1 ;
    while(start < end)  
        mid = (start+end)/2 ;
        if(mid < n-1 && arr[mid] >= arr[mid+1]) 
            return arr[mid] ;
        
        if(arr[start] > arr[mid])   
            end = mid - 1 ;
        
        else    
            start = mid + 1; 
        
    
    return arr[start] ;
```

【讨论】:

【参考方案7】:

使用另一个版本的二分搜索,这个问题很容易:


    int solve(vector<int>& a) 
        int n = a.size();
        int k=0;
        for(int b=n/2; b>=1; b/=2)
        
            while(k+b<n && a[k+b] >= a[0])
                k += b;
        
        return a[k];
    

【讨论】:

(不鼓励仅使用代码的答案。我发现的一个描述性标识符是size - 授予,n 有点传统,a 也是,如果这里不太适用的话。不要写,永远不要发布未注释/未记录的代码。(尝试实现“const正确性”。) 请补充一些cmets,解决方案背后的想法

以上是关于给定一个旋转的排序数组,我怎样才能找到该数组中的最大值?的主要内容,如果未能解决你的问题,请参考以下文章

62. 搜索旋转排序数组

给定一个随机顺序的整数数组,您必须找到最小交换次数才能将其转换为循环排序数组

Lintcode_62.搜索旋转排序数组

如何在 ColdFusion 中对结构数组进行排序

旋转数组中的元素查找

lintcode:寻找旋转排序数组中的最小值 II