在线性时间内查找字符串中多重集的所有组合

时间:2011-11-05 20:06:35

标签: string algorithm linear multiset bag

你还没有给出一个大小为m的字符包B(multiset)和一个大小为n的字符串文本S.是否有可能在线性时间O(n)中找到S中可由B(4!= 24种组合)创建的所有子串?

示例:

S = abdcdbcdadcdcbbcadc (n=19)
B = {b, c, c, d} (m=4)
Result: {cdbc (Position 3), cdcb (Position 10)}

我发现最快的解决方案是为每个角色保留一个计数器,并在每个步骤中将其与Bag进行比较,因此运行时间为O(n * m)。如果需要,可以显示算法。

3 个答案:

答案 0 :(得分:4)

有一种方法可以在O(n)中进行,假设我们只对长度为m的子串感兴趣(否则这是不可能的,因为对于包含字符串中所有字符的包,你必须返回s的所有子串,表示无法在O(n)中计算的O(n ^ 2)结果。

算法如下:

  • 将行李转换为直方图:

    hist = []
    for c in B do:
        hist[c] = hist[c] + 1
    
  • 初始化我们要修改的正在运行的直方图(histrunsum是histrun中的字符总数):

    histrun = []
    histrunsum = 0
    
  • 我们需要两个操作:在直方图中添加一个字符并将其删除。它们的运作方式如下:

    add(c):
        if hist[c] > 0 and histrun[c] < hist[c] then:
            histrun[c] = histrun[c] + 1
            histrunsum = histrunsum + 1
    
    remove(c):
        if histrun[c] > 0 then:
            histrun[c] = histrun[c] - 1
            histrunsum = histrunsum + 1
    
  • 本质上,histrun捕获当前子字符串中B中存在的字符数量。如果histrun等于hist,则我们的子字符串与B具有相同的字符。histrun等于hist iff histrunsum等于B的长度。

  • 现在将前m个字符添加到histrun;如果histrunsum等于B的长度;发出第一个子串;现在,直到我们到达字符串的结尾,删除当前子字符串的第一个字符并添加下一个字符。

  • add,remove是O(1),因为hist和histrun是数组;检查hist是否等于histrun是通过将histrunsum与长度(B)进行比较来完成的,所以它也是O(1)。循环迭代计数为O(n),结果运行时间为O(n)。

答案 1 :(得分:1)

感谢您的回答。必须更改add()remove()方法才能使算法正常运行。

add(c):
    if hist[c] > 0 and histrun[c] < hist[c] then
        histrunsum++
    else
        histrunsum--

    histrun[c] = histrun[c] + 1


remove(c):
    if histrun[c] > hist[c] then
        histrunsum++
    else
        histrunsum--

    histrun[c] = histrun[c] - 1

说明: histrunsum可以看作两个多重集合的相同程度。

add(c):当histrun multiset中的char出现次数少于hist multiset时,该char的额外出现必须“奖励”,因为histrun multiset越来越接近hist multiset。如果已经在histrun集合中至少有相同或更多的字符,则额外的字符为负数。

删除(c):与add(c)类似,其中当在thetrun multiset中的数字时,对char的删除进行加权。 hist multiset。

示例代码(PHP):

function multisetSubstrings($sequence, $mset)
{
    $multiSet = array();
    $substringLength = 0;
    foreach ($mset as $char)
    {
        $multiSet[$char]++;
        $substringLength++;
    }

    $sum = 0;
    $currentSet = array();
    $result = array();

    for ($i=0;$i<strlen($sequence);$i++)
    {

        if ($i>=$substringLength)
        {
            $c = $sequence[$i-$substringLength];

            if ($currentSet[$c] > $multiSet[$c])
                $sum++;
            else
                $sum--;

            $currentSet[$c]--;
        }


        $c = $sequence[$i];

        if ($currentSet[$c] < $multiSet[$c])
            $sum++;
        else
            $sum--;

        $currentSet[$c]++;

        echo $sum."<br>";


        if ($sum==$substringLength)
            $result[] = $i+1-$substringLength;
    }


    return $result;
}

答案 2 :(得分:0)

使用散列。对于multiset中的每个字符,分配一个UNIQUE素数。通过将与数字相关联的素数乘以该数字的频率,计算任何字符串的哈希值。

示例:CATTA。设C = 2,A = 3,T = 5.哈希= 2 * 3 * 5 * 5 * 3 = 450

散列多重集(将其视为字符串)。现在浏览输入字符串,并计算长度为k的每个子字符串的散列(其中k是多集中的字符数)。检查此哈希是否与多集哈希匹配。如果是,那就是这样的事情。

可以在线性时间内非常容易地计算哈希值,如下所示:

让multiset = {A,A,B,C},A = 2,B = 3,C = 5.

Multiset hash = 2 * 2 * 3 * 5 = 60

让text = CABBAACCA

(i)CABB = 5 * 2 * 3 * 3 = 90

(ii)现在,下一个字母是A,丢弃的字母是第一个,C。所以新的哈希=(90/5)* 2 = 36

(iii)现在,A被丢弃,A也被添加,所以新的哈希=(36/2)* 2 = 36

(iv)现在丢弃B,并添加C,因此hash =(36/3)* 5 = 60 = multiset hash。因此,我们发现了一个这样的必然发生 - BAAC

这个程序显然需要O(n)时间。