出现次数最多的子字符串?——其实没那么复杂

【有兴趣的网友可以参阅我的另一篇文章《 一次遍历找出“出现次数最多的子串” 》——对本文算法的改进。】

问题描述:

求一个字符串中出现次数最多的子串,子串的长度可以是 1 。

分析问题:

乍一看,好像无处下手。简单的穷举效率太低,随着输入的文本增长,时间复杂度和空间复杂度就会火箭般窜升至无法接受的地步。

我们需要寻找规律。

假设存在一个长度为 N 的子串 S 出现的次数最多。那么它具有哪些特点呢?
  • S 的任一子串的出现次数不少于 S 的出现次数
  • S 中不会出现重复的子串字符
  • S 中不会出现重复的字符
  • 组成 S 的每一个字符、每一个子串的出现次数都和 S 一样
“S 中不会出现重复的字符”,“组成 S 的每一个字符、每一个子串的出现次数都和 S 一样”!
有了这个结论,问题就简单了。

算法描述:

找到文本中的出现次数最高的单个字符组成的子串,放入一个队列中,
从队列的头部开始,对结果集中的每一个子串 S 进行处理
  1. 找到文本中该子串出现的任意一个位置 P,
  2. 判断文本中紧随 S 之后的字符 C 是否的出现次数是最多的,
  3. 如果 C 的出现次数不是最多的,结束。
  4. 如果 C 的出现次数是最多的,搜索文本中的每一个 S 并判断紧随其后的字符是否是 C,
  5. 如果文本中的每一个 S 之后都存在字符 C ,将 S + C 生成的子串放入结果集中,
  6. 如果文本中出现 S 之后的字符不是 C ,结束。
如此,直至到达队列尾。

代码:

#pragma  warning(disable : 4786)
#include  < iostream >
#include 
< string >
#include 
< deque >

#define  BUFFSIZE 1024

using  std::cout;
using  std::endl;
using  std:: string ;
using  std::deque;

typedef deque
< string >  strlist;

const   string ::size_type npos  =   - 1 ;
const   string  ignoreChars( " /t/n/r " );

inline bool IgnoreChar(char c){
    
return (ignoreChars.find(c) != npos);
}

/*
 * 统计字符出现次数
 
*/
unsigned CharSummary(
const string& text, unsigned usecount[], int tbllen){
    unsigned count, i;

    memset(usecount, 
0, tbllen * sizeof(unsigned));
    
for(i = 0;i < text.length();usecount[unsigned(text[i++])]++);

    
for(count = i = 0;i < 256;i++){
        
if(IgnoreChar(i))continue;
        
if(usecount[i] > count)count = usecount[i];
    }

    
return count;
}

/*
 * 试着增长字符串
 
*/
char TryGrowthString(const string& text, const string& str, int maxcount, unsigned* usecount){
    
int count;
    
string::size_type pos;
    
char c = 0;

    pos 
= text.find(str);
    
if(pos == npos)
        
return 0;

/*  不是出现次数最多的字符 */
    c 
= text[pos + str.length()];
    
if(usecount[unsigned(c)] < maxcount)
        
return 0;

/*  检查文本中的出现次数 */
    
for(count = 0; pos + str.length() + 1 < text.length();pos += str.length(), count++){
        pos 
= text.find(str, pos);
        
if(pos == npos) break;
        
if(c != text[pos + str.length()]) return 0;
    }

    
return count < maxcount ? 0 : c;
}

void PrintResult(const strlist& result){
    strlist::const_iterator citer;

    cout
<<endl<<"The result substrings :"<<endl;
    cout
<<"-------------------------------------"<<endl;
    
for(citer = result.begin(); citer != result.end(); citer++){
        cout
<<'"'<<*citer<<'"'<<endl;
    }
    cout
<<"-------------------------------------"<<endl;
    cout
<<"Total : "<<result.size()<<endl;
}

void main(void){
    unsigned usecount[
256];
    
char buffer[BUFFSIZE], c;
    unsigned count, i;
    
string text;
    strlist result;

/*
 * 从 stdin 读入文本
 
*/
    
while(!feof(stdin)){
        
if(fgets(buffer,sizeof(buffer),stdin))
            text 
+= buffer;
    }

/*
 * 统计字符出现次数
 
*/
    count 
= CharSummary(text, usecount, sizeof(usecount) / sizeof(*usecount));
    cout
<<"Max count :"<<count<<endl;

    
if(0 == count){
        cout
<<"There is empty string!"<<endl
            
<<"Bye!"<<endl;
    }

/*
 * 将出现次数最多的字符作为子串放入结果中
 
*/
    
for(i = 0;i < sizeof(usecount) / sizeof(*usecount);i++){
        
if(usecount[i] == count)
            result.push_back(
string(1,char(i)));
    }

/*
 * 查找更多的子串
 
*/
    
for(strlist::iterator iter = result.begin();iter != result.end();iter++){
        c 
= TryGrowthString(text, *iter, count, usecount);
        
if(c)
            result.push_back(
*iter + string(1, c));
    }

    PrintResult(result);
    cout
<<"Bye!"<<endl;
}
 
测试输入:
abcdefg
bcdef
hijklmnopqrstabcdefg

输出:
Max count :3

The result substrings :
-------------------------------------
"
"
"b"
"c"
"d"
"e"
"f"
"bc"
"cd"
"de"
"ef"
"bcd"
"cde"
"def"
"bcde"
"cdef"
"bcdef"
-------------------------------------
Total : 16
Bye!

【更快速的算法请参阅:《 一次遍历找出“出现次数最多的子串” 》】
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值