检测分组词的最佳方法

Posted

技术标签:

【中文标题】检测分组词的最佳方法【英文标题】:Best way to detect grouped words 【发布时间】:2010-02-11 19:54:17 【问题描述】:

如果对于单词中的每个字母,该字母的所有出现恰好形成一个连续的序列,则该单词被分组。换句话说,没有两个相同的字母被一个或多个不同的字母隔开。

给定一个vector<string>,返回分组词的数量。

例如:

“ab”、“aa”、“aca”、“ba”、“bb”

返回4

这里,“aca”不是一个组合词。

我快速而肮脏的解决方案:

int howMany(vector <string> words) 
  int ans = 0;
  for (int i = 0; i < words.size(); i++) 
       bool grouped = true;
  for (int j = 0; j < words[i].size()-1; j++)
      if (words[i][j] != words[i][j+1])
         for (int k = j+1; k < words[i].size(); k++)
           if (words[i][j] == words[i][k])
              grouped = false;
           if (grouped) ans++;
       
   return ans;
 

我想要一个更好的算法来解决同样的问题。

【问题讨论】:

@ Justin & R Samuel Klatchko:OOP 错字!已修复。 一个可以真正加速你的算法的简单的事情是在 grouped = false; 之后休息一下。您还需要检查 grouped 是否为 false 并再次中断以退出外部循环,这会增加一些开销。但是,这将停止检查一个单词是否在发现它没有被分组时被正确分组,这可以真正加快它的速度。 【参考方案1】:

尝试以下方法:

bool isGrouped( string const& str )

  set<char> foundCharacters;
  char currentCharacter='\0';

  for( int i = 0 ; i < str.size() ; ++i )
  
    char c = str[i];
    if( c != currentCharacter )
    
      if( foundCharacters.insert(c).second )
      
        currentCharacter = c;
      
      else
      
        return false;
      
    
  
  return true;

【讨论】:

+1。只是我的 2 美分:您要查找每个字符两次(find 中的一个,insert 中的另一个)。 insert 的重载可用于避免这种情况(返回 pair&lt;iterator,bool&gt; 的重载 @Manuel:感谢您的提示。我更新了我的答案以有效地使用插入。【参考方案2】:

只考虑一个词,这里是一个 O(n log n) 的破坏性算法:

std::string::iterator unq_end = std::unique( word.begin(), word.end() );
std::sort( word.begin(), unq_end );
return std::unique( word.begin(), unq_end ) == unq_end;

编辑:第一次调用unique 将连续字母的运行减少为单个字母。对sort 的调用将相同的字母组合在一起。第二次调用unique 检查sort 是否形成了任何新的连续字母组。如果是,则不得对该词进行分组。

与其他发布的相比,它的优势在于它不需要存储——尽管这并不是什么优势。

这是替代算法的简单版本,也只需要 O(1) 存储(是的,也经过测试):

if ( word.empty() ) return true;
bitset<CHAR_MAX+1> symbols;
for ( string::const_iterator it = word.begin() + 1; it != word.end(); ++ it ) 
    if ( it[0] == it[-1] ) continue;
    if ( symbols[ it[0] ] ) return false;
    symbols[ it[-1] ] = true;

return ! symbols[ * word.rbegin() ];

请注意,您需要稍作修改才能使用 ASCII 以外的字符。 bitset 来自标头&lt;bitset&gt;

【讨论】:

是的,我刚刚做了,它确实有效。它有什么问题? 我认为单词是向量 不,这只是一个单词,所以单词是一个字符串 可爱,我喜欢你的解决方案 :) 然而,这并不完全是 O(nlog n)。您正在对每个单词的字母进行排序。这需要 O(L log L)。所以最坏的情况是,如果所有单词的长度都为 L,则为 O(nL log L)。【参考方案3】:

您可以使用某种类型的 Set(最好是具有 O(1) 插入和查找时间的 Set)。

每次遇到与前一个不同的角色时,请检查集合中是否包含该角色。如果是这样,您的匹配将失败。如果没有,请将其添加到集合中并继续。

【讨论】:

我认为具有 O(1) 插入和查找的集合也称为数组(对于范围非常有限的 char) - 或 Potatoswatter 的答案中的位集。【参考方案4】:

这可能在每个单词两个循环中起作用:

1) 循环单词,计算出现的不同符号的数量。 (这将需要最多等于字符串长度的额外存储空间——可能是某种哈希值。)

2) 循环计算符号 n 与符号 n+1 不同的次数。

如果这两个值不相差一个,则不会对单词进行分组。

【讨论】:

我的解决方案也是每个单词使用两个循环。 并且应该有一个比 n 与符号 n+1 不同的次数多一个不同的符号。您可以使用集合来查找不同符号的数量。 确实如此。但是,您的算法中的循环是嵌套的,而这些不是嵌套的(尽管在创建不同符号列表时可能存在隐式嵌套循环)。 这是 O(n),而提问者的解决方案是 O(n^2)。 @Bill Carey:是的,在创建不同符号列表时可能存在隐式嵌套循环。【参考方案5】:

这是一种每个单词有两个循环的方法,除了其中一个循环不直到单词长度,而是直到字母大小。最坏的情况是 O(NLs),其中 N = 单词数,L = 单词长度,s = 字母大小:

for each word wrd:

  for each character c in the alphabet:
  
    for each letter i in wrd:
    
      let poz = last position of character c in wrd. initially poz = -1
      if ( poz == -1 && c == wrd[i] )
         poz = i;
      else if ( c == wrd[i] && poz != i - 1 )
         // definitely not grouped, as it's separated by at least one letter from the prev sequence
    
  
  // grouped if the above else condition never executed

基本上,检查字母表中的每个字母是否不存在或仅出现在该字母的一个子字符串中。

【讨论】:

我们可以做得更好:O(N(L + s^2)) 如果我们为每个单词保留此信息: first[i] = 当前单词中字符 i 的第一次出现,last[ i] = 当前单词中字符 i 的最后一次出现。这些可以通过单词的一次遍历找到。现在,对于每个字符 c,检查是否存在字符 c' != c 使得 first[c] 【参考方案6】:
    public static Boolean isGrouped( String input )
    
        char[] c = input.ToCharArray();
        int pointer = 0;
        while ( pointer < c.Length - 1 )
        
            char current = c[pointer];
            char next = c[++ pointer];
            if (   next != current && 
                 ( next + 1 ) != current && 
                 ( next - 1 ) == current 
               ) return false; 
        
        return true;
    

(C#,但原则适用)

【讨论】:

【参考方案7】:

这是一个匹配失败的多行、详细、正则表达式:

(?: # 非捕获组 ... (\S)\1* # 一个或多个任意非空格字符(被捕获)。 ) (?!# 那么一个没有的位置 \1 # ... 捕获的字符 ).+ # ... 至少一次。 \1 # 后跟捕获的字符。

或更小:

"(?:(\S)\1*)(?!\1).+\1"

我只是假设 C++ 有一个适合它的正则表达式实现,它确实可以在 Python 中工作,并且也应该在 Perl 和 Ruby 中工作。

【讨论】:

以上是关于检测分组词的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

检测 IronPython 的最佳方法

检测移动设备的最佳方法是啥?

用 php 检测浏览器的最佳方法是啥?

MIME 类型检测是检测一种文件的最佳方法吗?

检测 <= IE10 的最佳方法

检测用户何时离开网页的最佳方法?