首页 > 代码库 > 剑指offer (29) 数组中出现次数超过一半或1/3或1/N的数字

剑指offer (29) 数组中出现次数超过一半或1/3或1/N的数字

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字

方法一:如果把这个数字排序,那么排序之后位于数组中间的数字一定就是出现次数超过数组长度一半的数字

这个数字就是统计学中的中位数,即长度为n的数组中第n/2大的数字

在数组中得到任意第k大数字,这一问题有O(n)解,注:这里第kth个元素,kth从1开始计数,并且重复元素不去重

(1) 直接sort排序,然后定位到索引为kth-1的元素

int FindKth1(std::vector<int>& num, int kth){    if (kth > num.size()) {        throw std::invalid_argument("invalid_argument");    }    std::sort(num.begin(), num.end(), std::greater<int>());    return num.at(kth - 1);}

 

(2) 直接使用std::nth_element,这个函数将 第二个参数所指元素定位到排序后的最终位置,并且其左侧小于该值,右侧大于该值

注意第kth个元素索引为num.begin() + kth - 1

int FindKth2(std::vector<int>& num, int kth){    if (kth > num.size()) {        throw std::invalid_argument("invalid_argument");    }    std::nth_element(num.begin(), num.begin() + kth - 1, num.end(), std::greater<int>());    return num.at(kth - 1);}

 

(3) 直接使用std::partial_sort,这个函数将 [first, mid)区间排好序,其余元素无任何保证,注意是左闭右开区间

int FindKth3(std::vector<int>& num, int kth){    if (kth > num.size()) {        throw std::invalid_argument("invalid_argument");    }    std::partial_sort(num.begin(), num.begin() + kth, num.end(), std::greater<int>());    return num.at(kth - 1);}

 

 

方法二:根据有一个数val的出现次数超过了数组长度的一半,我们每次删除两个 不同 的数字(不论是否包含val),那么剩下的数组中,数字val的出现次数仍然超出剩余数组长度的一半

在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数

当遍历到下一个数字的时候,如果下一个数字与之前保存的数字相同,则次数加1

如果下一个数字与之前保存的数字不同,则次数1,如果次数减为0,则需要保存下一个数字,并把次数设为1

int FindMoreHalf(const std::vector<int>& num){    if (num.size() == 0) {        throw std::invalid_argument("invalid_argument");    }    int result = num.at(0);    int count = 1;    for (int i = 1; i < num.size(); ++i) {        if (count == 0) {            result = num.at(i);            count = 1;        } else {            if (num.at(i) == result) {                ++count;            } else {                --count;            }        }    }    int resNum = std::count(num.begin(), num.end(), result);  // 检查    if (resNum * 2 > n) {        return result;    } else {        throw std::invalid_argument("invalid_argument");    }}

 

 扩展问题:

如果求解数组中出现次数超过数组长度1/k的数字呢?

首先,最多有 k - 1 个出现次数超过数组长度1/k的数字

方法一:可以用c++11的unordered_map做一个hash<key, count>,第一次遍历构建完hash,第二次遍历查找长度超过1/k的数字 T(n) = O(n) (n为数组长度)

方法二:可以用c++11的unordered_map做一个长度为k的hash<key, count>,遍历原数组

如果元素已经在hash内,就将对应的count加1

如果元素不在hash之内,就insert到hash,并设count=1,此时如果insert之后,map长度为k,那么就将每个key的count减1,并将count=0的key直接删除

时间复杂度T(n) = O(n) 但是空间复杂度减小了点