剑指Offer:28 数组中出现次数超过一半的数字

目录

一、解法一:基于Partition函数(快速排序的基础)的复杂度O(n)的解法

二、针对解法一程序中Bug的再讨论

2.1 指针形式的参数传递

2.2 引用形式的参数传递

2.3 函数参数为指针传递、引用传递两种情况下,函数定义和调用的总结

三、解法二:思路同解法一,直接利用sort函数排序

四、解法三 

五、解法四


一、解法一:基于Partition函数(快速排序的基础)的复杂度O(n)的解法

这道题我首先用的书上的解法一,是一种写起来很繁琐的解法,也出了很多bug,记录如下:

1、 throw new std::exception("Invalid Inputs"); 异常提醒的写法不对,暂时注释掉了,return 0;

2、RandomInRange不是C++内置的函数,需要自己写,后来参考别人的写法结果没注意那是Java,Math.random()是Java中的方法,最后搜了一下C++的随机数生成的写法:https://www.cnblogs.com/afarmer/archive/2011/05/01/2033715.html

3、swap函数:一开始把 逗号, 打成中文字符都没注意,后来报错&a = &b,不能赋值(C++的基本知识还要再巩固一下)

改成第二种写法,依然报错 3 errors,调用形式不正确,直接把调用时的&data改为data即可。

(这里是因为&data是取地址,形参是data的引用,调用直接用data就可以,下面2.3有详细说明

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        
        if(numbers.empty()) return 0;
        
        int middle = numbers.size()>>1;
        int start = 0;
        int end = numbers.size()-1;
        
        int index = Partition(numbers, start, end); //初始索引
        while(index != middle) //返回的索引不是中位数,就一直循环下去
        {
            if(index>middle){
                index = Partition(numbers, start, index-1); //注意-1,类似的二分法也是有这点注意
            }
            else{
                index = Partition(numbers, index+1, end); //注意+1
            }
        }
        
        int times = 0; //记录次数
        for(int i=0; i<numbers.size(); i++) //i为int,numbers.size()返回的为size_t,?会报warning
        {
            if(numbers[index]==numbers[i]) times++;
        }
        if (times <= numbers.size()/2) return 0; // times是int,numbers.size()/2是unsigned long
        
        return numbers[index];
    }
    
    int Partition(vector<int> data, int start, int end) //此处vector应该加&,但是我不加&依然通过了,???
    {
        if(data.empty()==true || start<0 || end>data.size()-1)
            //throw new std::exception("Invalid Inputs");
            return 0;
            
        //int index = RandomInRange(start, end); //随机选中一个值作为分界点
        int index = start + (rand() %(end-start+1)); // 取得[start,end]区间随机整数
        swap(data, index, end); //把分界点元素放到数组最后
        
        int small = start-1; //比分界点值小的数的边界索引值
        for(index=start; index<end; index++){
            if(data[index]<data[end]){
                small++;
                if(small != index){
                    swap(data, small, index);
                }
            }
        }
        
        small++;
        swap(data, small, end); //把分界点放回到small+1的位置上
        
        return small; //返回分界点索引
    }
    
    void swap(vector<int> &numbers, int a, int b)
    {
        int temp = numbers[a];
        numbers[a] = numbers[b];
        numbers[b] = temp;
    }
    
    //void swap(int& a, int& b)//之前逗号打成了中文字符,自己没发觉到后面的int都没有变蓝色
    //{
        //int temp = &a;
        //&a = &b;
        //&b = temp;
    //}
};

二、针对解法一程序中Bug的再讨论

2.1 指针形式的参数传递

将swap函数写成第二种写法:

    void swap(int& a, int& b)
    {
        int temp = &a;
        &a = &b;
        &b = temp;
    }

将上面的函数定义、调用中的&全部改成*,依然会报错:

indirection requires pointer operand ('value_type' (aka 'int') invalid)

原因是:函数调用时出错,data[small]本身就是int,*int自然会报错(为什么会犯这种智障的错误……)

swap(*data[small], *data[end]);
改成:
swap(&data[small], &data[end]);

像上面这样,取地址传入,就不会报错了。

2.2 引用形式的参数传递

再来看一看,把swap函数的形参改回到引用形式,调用也加上了取地址符 & :

报错 5 erors,将swap函数内部变量a,b前的&去掉(我一直把在a,b前加 & 理解成了 引用 ,其实是 取地址 符号)

void swap(int& a, int& b)
    {
        int temp = a;
        a = b;
        b = temp;
    }

只改swap定义,不改调用(调用还是这样:swap(&data[small], &data[end]); )

报错 3errors ,调用3次,报错3次:non-const lvalue reference to type 'int' cannot bind to a temporary of type 'value_type *' (aka 'int *')

调用时,取地址符不加,就顺利通过。

之前出错的主要原因:&用作 引用 和 取地址 搞混淆了,函数调用时&a是取地址,我在写程序时理解成引用了。

C++中 引用&与取地址&的区别:https://blog.csdn.net/passtome/article/details/7937141

https://blog.csdn.net/qq_33266987/article/details/52047473

2.3 函数参数为指针传递、引用传递两种情况下,函数定义和调用的总结

1、指针传递

  void swap(int* a, int* b)
    {
        int temp = *a;
        *a = *b;
        *b = temp;
    }

swap(*data[i], *data[j] );

2、引用传递

  void swap(int& a, int& b)
    {
        int temp = a;
        a = b;
        b = temp;
    }

swap(data[i],  data[j] );

3、值传递

void swap(int a, int b)

{
        int temp = a;
        a = b;
        b = temp;

 }

swap(data[i],  data[j] );

这种情况下swap函数实际上起不到任何作用,因为是形参向实参拷贝了一份数据进行操作,函数返回就释放掉了,原来的实参并不会有任何变化。

所以最简单的办法,就是把函数的形参定义为引用的形式,这样传递进来的a,b,就是实参的别名,函数中改变了a,b的值,实际的实参也会被改变。

一个讲解很好的博客:C与C++关于*与&(引用方式传参,传值方式传参)

https://blog.csdn.net/qq_34243930/article/details/81638852

 

三、解法二:思路同解法一,直接利用sort函数排序

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        if(numbers.size() == 0)
            return 0;
         
        sort(numbers.begin(), numbers.end());
        int x = numbers[numbers.size() / 2];
        int count = 0;
        for(int i = 0; i < numbers.size(); i++)
        {
            if(numbers[i] == x)
                count ++;
        }
         
        if(count > numbers.size() / 2)
            return x;
        else
            return 0;
    }
};

四、解法三 

书上的第二种解法:

在遍历数组时保存两个值:一是数组中一个数字,一是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。

遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        int n = numbers.size(); //取数组的长度,返回的是size_t(unsigned long),放入int n中
        if(n==0) return 0;
        
        int result, times;
        //初始化
        result = numbers[0];
        times = 1;
        for(int i=1; i<n; i++)
        {
            if(numbers[i]==result){
                times++;
            }
            else{
                times--;
                if(times==0){
                    result=numbers[i+1];
                }
            }
        }
        
        if(CheckMoreThanHalf(numbers, result)) return result;
        else return 0;
    }
    
    bool CheckMoreThanHalf(vector<int> &numbers, int result)
    {
        int n = numbers.size();
        int times = 0;
        bool flag = false;
        for(int i=0; i<n; i++){
            if(numbers[i]==result) times++;
        }
        if(times>n/2) flag = true;
        return flag;
    }
};

五、解法四

注意到目标数字 超过数组长度的一半,对数组同时去掉两个不同的数字,到最后剩下的一个数就是该数字。如果剩下两个,那么这两个也是一样的,就是结果,在其基础上把最后剩下的一个数字或者两个回到原来数组中,将数组遍历一遍统计一下数字出现次数进行最终判断。

Java代码:

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        int length=array.length;
            if(array==null||length<=0){
                return 0;
            }
            
            if(length==1){
                return array[1];
            }
            
            int[] tempArray=new int[length];
            for(int i=0;i<length;i++){
                tempArray[i]=array[i];
            }
            
            for(int i=0;i<length;i++){
                //后面需要用零来代表抹除数字,所以对0时做特殊处理
                if(tempArray[i]==0){
                    continue;
                }
                
                for(int j=i+1;j<length;j++){
                    if(tempArray[i]!=tempArray[j]&&tempArray[j]!=0){
                        tempArray[i]=0;//此处用0代表抹去该数字
                        tempArray[j]=0;
                        break;
                    }
                    
                }
            }
            
            for(int i=0;i<length;i++){
                System.out.println(tempArray[i]);
            }
            
            //找出未被抹去的数字
            int result=0;
            for(int i=0;i<length;i++){
                if(tempArray[i]!=0){
                    result=tempArray[i];
                    break;
                }
            }
            
            int times=0;
            for(int i=0;i<length;i++){
                if(result==array[i]){
                    
                    times++;
                }
            }
            
            if(times*2<length){
                result=0;
            }
            return result;
    }
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值