查找连续出现最多的数字的算法 - C++

Posted

技术标签:

【中文标题】查找连续出现最多的数字的算法 - C++【英文标题】:Algorithm for finding the number which appears the most in a row - C++ 【发布时间】:2010-01-02 16:03:12 【问题描述】:

我需要帮助来制定解决一个问题的算法:有一排数字在该行中出现不同的时间,我需要找到出现次数最多的数字以及它在该行中出现的次数,例如:

1-1-5-1-3-7-2-1-8-9-1-2

那将是 1,它会出现 5 次。

算法应该很快(这是我的问题)。 有什么想法吗?

【问题讨论】:

我试过用数组和几个循环来做这件事,但这需要很多时间。 请描述问题的约束... 元素的范围是否已知?这组数字有多长? 【参考方案1】:

您要查找的内容称为mode。您可以对数组进行排序,然后查找最长的重复序列。

【讨论】:

这是O(n log n) 时间和O(1) 空间。通过放弃空间,您可以在O(n) 中执行此操作。显然这是最好的办法。 不,你只是使用字典,然后范围无关紧要。 @Jason,当你说 'just dictinary' 时,你不认为计算 任意大 数的哈希值的复杂性也应该考虑在内,你呢? Jason:显然决定“最佳”(和“更好”)取决于所有要求,而 OP 几乎没有列出任何时间或空间要求。此外,虽然在哈希表中查找和插入的时间为 O(1),但这并不是全部——一般来说,大哦符号隐藏了重要的考虑因素,即使它本身很有用。 当所有项目都作为一个块读入时,此方法是首选。出于性能考虑,该算法可能会先读入所有数字,然后再进行排序。字典或关联容器可能不是最终的解决方案。【参考方案2】:

您可以保留哈希表并存储该结构中每个元素的计数,如下所示

h[1] = 5
h[5] = 1
...

【讨论】:

C++中的“哈希表”实现为std::map类。 实际上,“哈希表”不同于std::map。在 散列表 中,键被 散列 或转换为唯一值,然后用作容器的索引。 std::map 是 [key, value] 对的容器。一些实现可能会通过提供哈希表来扩充 STL。 目前有std::tr1::unordered_map【参考方案3】:

您无法比线性时间更快地获得它,因为您至少需要查看每个数字一次。

如果你知道数字在一定范围内,你可以使用一个额外的数组来总结每个数字的出现次数,否则你需要一个哈希表,它会稍微慢一些。

不过,这两个都需要额外的空间,最后你需要再次循环计数才能得到结果。

除非您确实有大量数字并且绝对需要 O(n) 运行时间,否则您可以简单地对数字数组进行排序。然后你可以遍历这些数字,并简单地保持当前数字的计数和两个变量中出现次数最多的数字。因此,您可以节省大量空间,并用一点点时间进行权衡。

【讨论】:

"每个数字至少需要查看一次。" -- 这个说法是错误的。 在某些输入情况下,您不需要查看每个输入的数字来找到出现次数最多的数字,但您仍然需要查看每个输入的数字找出“有多少次[最频繁的]在一行中”。弗兰克是正确的。 对我来说似乎很明显。如果我不看x,而x是获胜者的副本,那我如何正确报告x出现的次数。 @Roger, @GregS:是的,抱歉,我错过了问题中的“多少次”部分。【参考方案4】:

有一种算法可以在线性时间内解决您的问题(与输入中的项目数成线性关系)。这个想法是使用哈希表将输入中的每个值关联到一个计数,该计数指示该值已被看到的次数。您必须根据您的预期输入进行概要分析,看看这是否满足您的需求。

请注意,这会使用O(n) 额外空间。如果这是不可接受的,您可能需要考虑按照其他人的建议对输入进行排序。该解决方案将是时间上的O(n log n) 和空间上的O(1)

这是一个使用 std::tr1::unordered_map 的 C++ 实现:

#include <iostream>
#include <unordered_map>

using namespace std;
using namespace std::tr1;

typedef std::tr1::unordered_map<int, int> map;

int main() 
    map m;

    int a[12] = 1, 1, 5, 1, 3, 7, 2, 1, 8, 9, 1, 2;
    for(int i = 0; i < 12; i++) 
        int key = a[i];
        map::iterator it = m.find(key);
        if(it == m.end()) 
            m.insert(map::value_type(key, 1));
        
        else 
            it->second++;
        
    
    int count = 0;
    int value;
    for(map::iterator it = m.begin(); it != m.end(); it++) 
        if(it->second > count) 
            count = it->second;
            value = it->first;
        
    

    cout << "Value: " << value << endl;
    cout << "Count: " << count << endl;

该算法使用输入整数作为哈希表中的键来计算每个整数出现的次数。因此,算法的关键(双关语)是构建这个哈希表:

int key = a[i];
map::iterator it = m.find(key);
if(it == m.end()) 
    m.insert(map::value_type(key, 1));

else 
    it->second++;

所以我们在这里查看输入列表中的ith 元素。然后我们要做的是看看我们是否已经看到它。如果还没有,我们向包含这个新整数的哈希表添加一个新值,初始计数为 1 表示这是我们第一次看到它。否则,我们增加与该值关联的计数器。

一旦我们建立了这个表,只需遍历值以找到出现最多的一个:

int count = 0;
int value;
for(map::iterator it = m.begin(); it != m.end(); it++) 
    if(it->second > count) 
        count = it->second;
        value = it->first;
    

目前没有逻辑来处理两个不同值出现相同次数并且该次数是所有值中最大的情况。您可以根据自己的需要自行处理。

【讨论】:

您可能不想为 value->count 映射中所有元素的最终迭代付出代价。您可以随时跟踪计数最高的值(以及该计数)。【参考方案5】:

这里有个简单的,就是O(n log n):

Sort the vector @ O(n log n)
Create vars: int MOST, VAL, CURRENT
for ELEMENT in LIST:
    CURRENT += 1
    if CURRENT >= MOST:
        MOST = CURRENT
        VAL = ELEMENT
return (VAL, MOST)

【讨论】:

这一次弗兰克也打败了我! O(n log n) 时间,但不需要太多额外空间。这当然假设您拥有整个列表(即,它不是流)。 有效的 Python,除了前两行。 :) 你也必须初始化 CURRENT!【参考方案6】:

有几种方法:

通用方法是“排序并找到最长的子序列”,即O(nlog n)。最快的排序算法是快速排序(平均,最差的是O( n^2 ))。您也可以使用 heapsort,但在平均情况下它会很慢,但在最坏的情况下,渐近复杂度也是 O( n log n )

如果您有一些关于数字的信息,那么您可以使用一些技巧。如果数字来自有限范围,那么您可以使用部分算法进行计数排序。是O( n )

如果不是你的情况,还有其他一些排序算法可以在线性时间内完成,但没有一种是通用的。

【讨论】:

快速排序并不是最快的算法。 是的,在一般情况下。查看统计数据 具有线性复杂度的算法在一般情况下不可用,基于比较的算法在平均情况下是 QS 最快的。【参考方案7】:

您可以在这里获得的最佳时间复杂度是 O(n)。您必须查看所有元素,因为最后一个元素可能是决定模式的元素。

解决方案取决于时间或空间更重要。

如果空间更重要,那么您可以对列表进行排序,然后找到最长的连续元素序列。

如果时间更重要,您可以遍历列表,记录每个元素的出现次数(例如散列元素 -> 计数)。在执行此操作时,请跟踪具有最大计数的元素,并在必要时进行切换。

如果你也知道模式是多数元素(即数组中有超过 n/2 个具有此值的元素),那么你可以得到O(n) speed and O(1) space efficiency。

【讨论】:

【参考方案8】:

通用 C++ 解决方案:

#include <algorithm>
#include <iterator>
#include <map>
#include <utility>

template<class T, class U>
struct less_second

    bool operator()(const std::pair<T, U>& x, const std::pair<T, U>& y)
    
        return x.second < y.second;
    
;

template<class Iterator>
std::pair<typename std::iterator_traits<Iterator>::value_type, int>
most_frequent(Iterator begin, Iterator end)

    typedef typename std::iterator_traits<Iterator>::value_type vt;
    std::map<vt, int> frequency;
    for (; begin != end; ++begin) ++frequency[*begin];
    return *std::max_element(frequency.begin(), frequency.end(),
                             less_second<vt, int>());


#include <iostream>

int main()

    int array[] = 1, 1, 5, 1, 3, 7, 2, 1, 8, 9, 1, 2;
    std::pair<int, int> result = most_frequent(array, array + 12);
    std::cout << result.first << " appears " << result.second << " times.\n";

Haskell 解决方案:

import qualified Data.Map as Map
import Data.List (maximumBy)
import Data.Function (on)

count = foldl step Map.empty where
    step frequency x = Map.alter next x frequency
    next  Nothing    = Just 1
    next (Just n)    = Just (n+1)

most_frequent = maximumBy (compare `on` snd) . Map.toList . count

example = most_frequent [1, 1, 5, 1, 3, 7, 2, 1, 8, 9, 1, 2]

在堆栈溢出的帮助下,更短的 Haskell 解决方案:

import qualified Data.Map as Map
import Data.List (maximumBy)
import Data.Function (on)

most_frequent = maximumBy (compare `on` snd) . Map.toList .
                Map.fromListWith (+) . flip zip (repeat 1)

example = most_frequent [1, 1, 5, 1, 3, 7, 2, 1, 8, 9, 1, 2]

【讨论】:

【参考方案9】:

编辑:取出未使用的比较功能。

这是 Python 3.1 的实现:

#Python 3.1
lst = [1,1,5,1,3,7,2,1,8,9,1,2]

dct = 
for i in lst:
    if i in dct:
        dct[i] += 1
    else:
        dct[i] = 1

mx = max(dct.keys(), key=lambda k: dct[k])

print('Value 0 appears 1 times.'.format(mx, dct[mx]))

>>> 
Value 1 appears 5 times.

【讨论】:

你在哪里使用比较功能? 如果您打算在 python 3.1 中编写此代码,请查找恰好适合此问题的 collections.Counter。 docs.python.org/dev/py3k/library/collections.html 谢谢,没必要 - 我把它拿出来了。【参考方案10】:

Python 2.6

>>> from collections import defaultdict
>>> lst = [1,1,5,1,3,7,2,1,8,9,1,2]
>>> d = defaultdict(int)
>>> for i in lst:
...     d[i] += 1
...
>>> max_occurring = max((v, k) for k, v in d.items())
>>> print "%d occurs %d times" % (max_occurring[1], max_occurring[0])
1 occurs 5 times

【讨论】:

@MrFooz 您没有运行建议的代码更改。而且您没有注意到我的生成器从items() 反转了键和值的顺序。这很重要。否则,您按键排序,而不是按值排序。【参考方案11】:

下面的解决方案为您提供每个数字的计数。在时间和空间方面,它是比使用地图更好的方法。如果你需要得到出现次数最多的数字,那么这并不比以前的更好。

编辑:这种方法仅适用于无符号数字和从 1 开始的数字。

    std::string row = "1,1,5,1,3,7,2,1,8,9,1,2";
    const unsigned size = row.size();
    int* arr = new int[size];
    memset(arr, 0, size*sizeof(int));
    for (int i = 0; i < size; i++)
    
        if (row[i] != ',')
        
            int val = row[i] - '0';
            arr[val - 1]++;
        
    

    for (int i = 0; i < size; i++)
        std::cout << i + 1 << "-->" << arr[i] << std::endl;

【讨论】:

【参考方案12】:

由于这是家庭作业,我认为可以提供不同语言的解决方案。

在 Smalltalk 中,以下内容将是一个很好的起点:

SequenceableCollection>>mode
  | aBag maxCount mode |

  aBag := Bag new
            addAll: self;
            yourself.
  aBag valuesAndCountsDo: [ :val :count |
    (maxCount isNil or: [ count > maxCount ])
      ifTrue: [ mode := val.
                maxCount := count ]].

  ^mode

【讨论】:

以上是关于查找连续出现最多的数字的算法 - C++的主要内容,如果未能解决你的问题,请参考以下文章

去重算法--给出一段英文连续的英文字符窜,找出重复出现次数最多的字母

求一个字符串中连续出现次数最多的子串

java求数组中,某个值连续出现次数最多的数的次数

寻找一个字符串中连续出现次数最多的子串(面试宝典14.5节面试题1)

寻找一个字符串中连续出现次数最多的子串(面试宝典14.5节面试题1)

python 将连续元素分组,然后找出出现次数最多的。