• 单调旋转数组的TopK问题


    问题描述:输入一个单调旋转后的数组,求该数组中的第k小的元素。

    分析:很多人看到这个题目会有点懵,可能读者不知道什么是旋转数组,我先解释下两个概念,            

            旋转数组的定义:把一个数组的前几项元素移动到数组的末尾,称之为数组的旋转。

            单调旋转数组的定义:如果数组在旋转之前是一个单调数组,则旋转之后称之为单调旋转数组。

            为了方便解释,这里我以单调非递减的旋转数组为例进行解释,其他类型可以通过这个例子类推。

           

            解法一:最简单的方法就是从头到位扫描一遍,求出小的元素下标,进而推算出第k小的元素下标,即可得到答案。

                       但是这样做的时间复杂度为O(n).还可以寻求更快的解法。

            解法二:充分利用数组的单调性质解题,可以发现旋转之后的数组可以划分为两个单调子数组,其中一个单调子数组所有的元素都大于等于另一个数组中的元素。

                       并且最小的元素正好是两个元素的分解点,这样就可以使用二分查找法进行查找。

                       可以设置三个指针left,right,mid分别指向数组头元素、尾元素和中间元素的下标。利用中间元素去和头元素比较,有两种情况:

                                  如果a[mid]>=a[left],则令left=mid

                                  如果a[mid]<a[left],则令right=mid

                                  然后再重新计算mid,就这样一直循环去做,直到left+1==right停止,此时rigth就是最小元素下标,再根据k计算出第k小元素的下标即可。

                       此方法的时间复杂度为O(logn)。

            对比:很明显解法二比解法一要快,但是要求数组必须是单调数组,而解法一可以用于所有的旋转数组。由于解法一,比较简单,我就不再编程实现了,我主要对解法二去编程实现。

                    对于一般的测试样例,解法二都没问题,但是对于特殊的测试样例,却出现了问题。

                    例如:1,0,1,1,1和1,1,1,0,1可以发现结果不正确,因此需要加以改进,如果left,right,mid所指元素都相等,解法二无能为力,需要使用解法一求解。

            我们可以在程序中对待这一特定条件去单独判断即可。

            另外解法二还可以进一步优化,如果a[left]<a[right],我们直接就可以求出a[left]就是最小的元素,进而根据k求出第k小的元素。

            具体的Java代码如下,代码写法都比较通用,读者可以很容易的转化为其他语言实现。

     1 public class Main{
     2     public static void mintopk(int a[],int k){
     3         if(a==null ||k<=0 ||k>a.length)                      //如果数组不存在或者k不符合要求,则直接结束
     4             { System.out.println("不存在");
     5               return ;
     6             }
     7         int p;                                              //用来表示mintopk数的下标
     8         int left=0,right=a.length-1,mid=(left+right)/2;     //求出起初的开始、结束和中间元素下标
     9         if(a[mid]==a[left] && a[mid]==a[right])             //如果三个下标所指元素相等,则从头开始扫描
    10             for(int i=1;i<a.length;i++)
    11                 if(a[i-1]>a[i])
    12                 {   p=i+k-1;
    13                     if(p>a.length-1)p=p-a.length;           //求出mintopk的下标 
    14                     System.out.println(a[p]);
    15                     return;
    16                 }
    17         if(a[left]<a[right])                                //如果头元素小于尾元素,则表明left所指为最小元素
    18         {
    19             p=left+k-1;
    20             if(p>a.length-1)p=p-a.length;
    21             System.out.println(a[p]);
    22             return;
    23         }
    24         
    25         while((left+1)!=right)                              //利用三个指针进行查找
    26         {   
    27             if(a[mid]>=a[left])left=mid;
    28             else right=mid;
    29             mid=(left+right)/2;
    30         }
    31         p=right+k-1;
    32         if(p>a.length-1)p=p-a.length;
    33         System.out.println(a[p]);
    34     }
    35     public static void main(String[] args) {
    36         // TODO 自动生成的方法存根
    37         int a[]={3,4,5,1,2};
    38         int k=5;
    39         mintopk(a,k);
    40     }
    41 
    42 }

    输出结果为:

    5

    注意:代码中我只写了一组测试例子,读者可以下载代码自行验证。

  • 相关阅读:
    购物菜单
    数据库
    增删改查
    页面交互
    计算器
    2020.9.21
    团队-团队编程项目中国象棋-项目总结
    团队-团队编程项目作业名称-最终程序
    课后作业-阅读任务-阅读提问-4
    《20171130-构建之法:现代软件工程-阅读笔记》
  • 原文地址:https://www.cnblogs.com/guozhenqiang/p/5468571.html
Copyright © 2020-2023  润新知