• 剑指offer面试题29:数组中出现次数超过一半的数字


    题目:数组中有一个数字出现的次数超过数组长度的一般,请找出这个数字,例如输入一个长度为9的数组(1,2,3,2,2,2,5,4,2,)。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。

    个人第一眼想法是通过一个sort函数,再判断中间那数出现次数,只要出现多于n/2,就直接输出。

    一般来说,最为直观的算法面试官都不会满意,那么有没有更优的算法呢?

    这种算法是受快速排序算法的启发。在随机快速排序算法中,我们现在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中的数字小的数字都排在它的左边,比选中的数字大的数字都排在它的右边。如果这个选中的数字的下标刚好是n/2,那么这个数字就是数组的中位数。如果它的下标大于n/2,那么中位数应该位于它的左边,我们可以接着在它的左边部分的数组中查找。如果它的下标小于n/2,那么中位数应该位于它的右边,我们可以接着在它的右边部分的数组中查找。这是一个典型的递归过程,实现代码如下:

     1 int MoreThanHalfNum_Solution(vector<int> numbers) 
     2 {
     3     int length = numbers.size();
     4     if (numbers.empty() || length<0)
     5         return 0;
     6 
     7     int begin = 0;
     8     int end = length - 1;
     9     int middle = length >> 1;
    10     int index = 0; 
    11     //利用
    12     while (begin<end)
    13     {
    14         index = Partition(numbers, begin, end);
    15         if (index == middle)
    16         {
    17             break;
    18         }
    19         else if (index > middle)
    20         {
    21             end = index - 1; //则值在左边部分的数组。
    22         }
    23         else
    24         {
    25             begin = index + 1;//则值在右边部分的数组。
    26         }
    27     }
    28     //检查该值是否超过数组长度的一半
    29     int cnt = 0;
    30     for (int i = 0; i < length; ++i)
    31     {
    32         if (numbers[index] == numbers[i])
    33             cnt++;
    34     }
    35     if (cnt * 2 > length) return numbers[index];
    36 
    37     return 0;
    38 }

    一.基于partition函数的O(n)算法

    算法一

    partition函数思路:

    • 使用第一个数组元素作为枢轴点,即为pivot;
    • 使用一个指针去扫描整个数组,凡是小于pivot的全部放到数组左端;
    • 最后将pivot放到数组中间的位置,pivot左边全部都是小于它的数字,右边反之,最后返回pivot的位置信息;

     代码如下:

     1 void swap(int &x, int &y)
     2 {
     3     int t = x;
     4     x = y;
     5     y = t;
     6 
     7 }
     8 int partition(vector<int> &nums, int begin, int end)
     9 {
    10     int pivot = nums[begin];//枢轴(也可以是在begin和end之间的随机数)
    11     // Last position where puts the no_larger element.
    12     //凡是小于pivot的全部放到数组左端,pos指向<枢轴值的最后一个
    13     //pos++指向不满足条件的(用于交换,将满足条件的换过来)
    14     int pos = begin;
    15     for (int i = begin + 1; i < end; ++i)
    16     {
    17         if (nums[i] < pivot)
    18         {
    19             pos++;
    20             if (i != pos) //避免自身交换
    21                 swap(nums[pos], nums[i]);
    22         }
    23 
    24     }
    25     swap(nums[pos], nums[begin]);
    26     return pos;
    27 }

    算法分析
    这种实现思路比较直观,但是其实并不高效。从直观上来分析一下,每个小于pivot的值基本上(除非到现在为止还没有遇见大于pivot的值)都需要一次交换,大于pivot的值(有可能需要被交换多次才能到达最终的位置。

    算法二

    算法思路

    • 就如快速排序中最常使用的那样,使用两个指针分别从头部和尾部进行扫描,头部遇到大于pivot的数和尾部遇到小于pivot的数进行交换;
    • 使用了两个指针,效率更高一点;避免使用swap函数

    如果我们考虑用 Two Pointers 的思想,保持头尾两个指针向中间扫描,每次在头部找到大于pivot的值,同时在尾部找到小于pivot的值,然后将它们做一个交换,就可以一次把这两个数字放到最终的位置。一种比较明智的写法如下:

    //Two Pointers思想的分割函数(begin为0,end为n-1)
    int Partition(vector<int> &nums, int begin, int end)
    {
        int pivot = nums[begin];//第一个记录作为枢轴(也可是在begin和end之间的随机数)
        while (begin < end)
        {
            while (begin < end && nums[end] >= pivot)
            {
                end--;
            }
            nums[begin] = nums[end];//尾部找到小于pivot的值,移到低端
    
            while (begin < end && nums[begin] <= pivot)
            {
                begin++;
            }
            nums[end] = nums[begin];//头部找到大于pivot的值,移到高端
        }
    
        nums[begin] = pivot;//枢轴基准归位
    
        return begin;
    }

    算法分析:赋值操作不多,效率会更高


    二分Partition算法

    快速排序算法

    void quickSort(vector<int> &nums, int begin, int end)
    {
    
        if (begin >= end) return;
    
        int index = partition(nums, begin, end);
        if (index>begin)
            quickSort(nums, begin, index-1);
        if (index<end)
            quickSort(nums, index+1, end);
    }
    调用:
    quickSort(vec, 0, vec.size()-1); //end为n-1

     

  • 相关阅读:
    深入理解JavaScript定时器(续)
    也谈前端基础设施建设
    Reporting Services在指定计算机上找不到报表服务器
    优化tempdb提高SQL Server的性能
    SQL 代理服务未运行。此操作需要 SQL 代理服务。 (rsSchedulerNotResponding) 获取联机帮助
    报表服务器上出现内部错误。有关详细信息,请参阅错误日志。 (rsInternalError) 获取联机帮助.找不到存储过程 'GetOneConfigurationInfo'。
    表中包含有外键时无法进行导入数据,
    SQLSTATE ODBC API(驱动程序管理器)错误
    数据库只能用机器名连接,不能用ip地址连接
    请教:不能访问通过IP访问,却可以通过机器名访问
  • 原文地址:https://www.cnblogs.com/knis/p/12334095.html
Copyright © 2020-2023  润新知