python查找字符串出现次数_python-查找字符串中子序列出现的次数

python-查找字符串中子序列出现的次数

例如,假设字符串是pi的前10位数字1.*2.*3,子序列为123。请注意,该序列出现了两次:

3141592653

1 2 3

1 2 3

这是一个面试问题,我无法回答,也无法想到一种有效的算法,这困扰着我。 我觉得应该可以使用一个简单的正则表达式,但是像1.*2.*3这样的正则表达式不会返回每个子序列。 我在Python中的幼稚实现(每1个计数后的2个计数为3)已经运行了一个小时,但还没有完成。

9个解决方案

113 votes

这是一个经典的动态编程问题(通常不使用正则表达式解决)。

我的幼稚实现(每个1计数为2,每个3计数)已经运行了一个小时,但还没有完成。

那将是一种穷举的搜索方法,它会以指数的时间运行。 (我很惊讶它运行了几个小时)。

这是一个动态编程解决方案的建议:

递归解决方案的概述:

(很长的道歉,但是每个步骤都非常简单,请耐心等待;-)

如果子序列为空,则找到匹配项(没有数字可匹配!),我们返回1

如果输入序列为空,则我们的数字已用完,无法找到匹配项,因此返回0

(序列和子序列都不为空。)

(假设“ abcdef”表示输入序列,“ xyz”表示子序列。)

将result设置为0

将bcdef和xyz的匹配数添加到result中(即,丢弃第一个输入数字并递归)

如果前两位数字匹配,即a = x

将bcdef和yz的匹配数加到result中(即,匹配第一个子序列数字,然后递归其余的子序列数字)

返回result

这是对输入1221/12的递归调用的说明(粗体字的子序列,·表示空字符串。)

xtiRG.png

动态编程

如果天真地实施,则某些(子)问题会被多次解决(例如上图中的?/ 2)。 动态编程通过记住先前解决的子问题(通常在查找表中)的结果来避免这种冗余计算。

在这种情况下,我们用

[序列长度+ 1]行,以及

[子序列的长度+ 1]列:

8MXJc.png

这个想法是我们应该在相应的行/列中填写221/2的匹配项数。 完成后,我们应该在单元1221/12中拥有最终解决方案。

我们开始使用我们立即知道的内容(“基本情况”)填充表:

如果没有剩余的子序列数字,则我们有1个完全匹配项:

YDJhP.png

如果没有序列号,我们将没有任何匹配项:

QXruq.png

然后,我们按照以下规则从上至下/从左至右填充表格:

在单元格[row] [col]中写入在[row-1] [col]处找到的值。

直观上,这意味着“ 221/2的匹配数包括21/2的所有匹配”。

如果行行的序列和列col的subseq以相同的数字开头,请将在[row-1] [col-1]中找到的值添加到刚刚写入[row] [col]的值中。

直观上,这意味着“ 1221/12的匹配数还包括221/12的所有匹配”。

l71gT.png

最终结果如下所示:

qxDvE.png

并且右下角单元格的值确实为2。

在代码中

不是在Python中(我很抱歉)。

class SubseqCounter {

String seq, subseq;

int[][] tbl;

public SubseqCounter(String seq, String subseq) {

this.seq = seq;

this.subseq = subseq;

}

public int countMatches() {

tbl = new int[seq.length() + 1][subseq.length() + 1];

for (int row = 0; row < tbl.length; row++)

for (int col = 0; col < tbl[row].length; col++)

tbl[row][col] = countMatchesFor(row, col);

return tbl[seq.length()][subseq.length()];

}

private int countMatchesFor(int seqDigitsLeft, int subseqDigitsLeft) {

if (subseqDigitsLeft == 0)

return 1;

if (seqDigitsLeft == 0)

return 0;

char currSeqDigit = seq.charAt(seq.length()-seqDigitsLeft);

char currSubseqDigit = subseq.charAt(subseq.length()-subseqDigitsLeft);

int result = 0;

if (currSeqDigit == currSubseqDigit)

result += tbl[seqDigitsLeft - 1][subseqDigitsLeft - 1];

result += tbl[seqDigitsLeft - 1][subseqDigitsLeft];

return result;

}

}

复杂

这种“填表”方法的好处是,弄清楚复杂性很简单。 每个单元格完成的工作量是恒定的,并且我们有序列长度的行和子序列长度的列。 因此,复杂度为O(MN),其中M和N表示序列的长度。

aioobe answered 2019-10-11T19:07:30Z

14 votes

好答案,噢! 为了补充您的答案,一些可能的Python实现:

# straightforward, naïve solution; too slow!

def num_subsequences(seq, sub):

if not sub:

return 1

elif not seq:

return 0

result = num_subsequences(seq[1:], sub)

if seq[0] == sub[0]:

result += num_subsequences(seq[1:], sub[1:])

return result

# top-down solution using explicit memoization

def num_subsequences(seq, sub):

m, n, cache = len(seq), len(sub), {}

def count(i, j):

if j == n:

return 1

elif i == m:

return 0

k = (i, j)

if k not in cache:

cache[k] = count(i+1, j) + (count(i+1, j+1) if seq[i] == sub[j] else 0)

return cache[k]

return count(0, 0)

# top-down solution using the lru_cache decorator

# available from functools in python >= 3.2

from functools import lru_cache

def num_subsequences(seq, sub):

m, n = len(seq), len(sub)

@lru_cache(maxsize=None)

def count(i, j):

if j == n:

return 1

elif i == m:

return 0

return count(i+1, j) + (count(i+1, j+1) if seq[i] == sub[j] else 0)

return count(0, 0)

# bottom-up, dynamic programming solution using a lookup table

def num_subsequences(seq, sub):

m, n = len(seq)+1, len(sub)+1

table = [[0]*n for i in xrange(m)]

def count(iseq, isub):

if not isub:

return 1

elif not iseq:

return 0

return (table[iseq-1][isub] +

(table[iseq-1][isub-1] if seq[m-iseq-1] == sub[n-isub-1] else 0))

for row in xrange(m):

for col in xrange(n):

table[row][col] = count(row, col)

return table[m-1][n-1]

# bottom-up, dynamic programming solution using a single array

def num_subsequences(seq, sub):

m, n = len(seq), len(sub)

table = [0] * n

for i in xrange(m):

previous = 1

for j in xrange(n):

current = table[j]

if seq[i] == sub[j]:

table[j] += previous

previous = current

return table[n-1] if n else 1

Óscar López answered 2019-10-11T19:07:54Z

7 votes

一种方法是使用两个列表。 将它们称为111111222222333333和OneTwos。

逐个字符地浏览字符串。

每当您看到数字111111222222333333时,请在OneTwos列表中进行输入。

每当您看到数字111111222222333333时,请遍历OneTwos列表,并将条目添加到123列表中。

每当您看到数字111111222222333333时,请遍历OneTwos列表并输出123。

在一般情况下,该算法将非常快,因为它是单次通过字符串,而多次是通过通常较小的列表。 但是,病理病例会杀死它。 想象一个像111111222222333333这样的字符串,但是每个数字重复了数百次。

Jim Mischel answered 2019-10-11T19:08:51Z

2 votes

from functools import lru_cache

def subseqsearch(string,substr):

substrset=set(substr)

#fixs has only element in substr

fixs = [i for i in string if i in substrset]

@lru_cache(maxsize=None) #memoisation decorator applyed to recs()

def recs(fi=0,si=0):

if si >= len(substr):

return 1

r=0

for i in range(fi,len(fixs)):

if substr[si] == fixs[i]:

r+=recs(i+1,si+1)

return r

return recs()

#test

from functools import reduce

def flat(i) : return reduce(lambda x,y:x+y,i,[])

N=5

string = flat([[i for j in range(10) ] for i in range(N)])

substr = flat([[i for j in range(5) ] for i in range(N)])

print("string:","".join(str(i) for i in string),"substr:","".join(str(i) for i in substr),sep="\n")

print("result:",subseqsearch(string,substr))

输出(立即):

string:

00000000001111111111222222222233333333334444444444

substr:

0000011111222223333344444

result: 1016255020032

Luka Rahne answered 2019-10-11T19:09:15Z

0 votes

我的快速尝试:

def count_subseqs(string, subseq):

string = [c for c in string if c in subseq]

count = i = 0

for c in string:

if c == subseq[0]:

pos = 1

for c2 in string[i+1:]:

if c2 == subseq[pos]:

pos += 1

if pos == len(subseq):

count += 1

break

i += 1

return count

print count_subseqs(string='3141592653', subseq='123')

编辑:如果1223 == 2和更复杂的情况,这也应该是正确的:

def count_subseqs(string, subseq):

string = [c for c in string if c in subseq]

i = 0

seqs = []

for c in string:

if c == subseq[0]:

pos = 1

seq = [1]

for c2 in string[i + 1:]:

if pos > len(subseq):

break

if pos < len(subseq) and c2 == subseq[pos]:

try:

seq[pos] += 1

except IndexError:

seq.append(1)

pos += 1

elif pos > 1 and c2 == subseq[pos - 1]:

seq[pos - 1] += 1

if len(seq) == len(subseq):

seqs.append(seq)

i += 1

return sum(reduce(lambda x, y: x * y, seq) for seq in seqs)

assert count_subseqs(string='12', subseq='123') == 0

assert count_subseqs(string='1002', subseq='123') == 0

assert count_subseqs(string='0123', subseq='123') == 1

assert count_subseqs(string='0123', subseq='1230') == 0

assert count_subseqs(string='1223', subseq='123') == 2

assert count_subseqs(string='12223', subseq='123') == 3

assert count_subseqs(string='121323', subseq='123') == 3

assert count_subseqs(string='12233', subseq='123') == 4

assert count_subseqs(string='0123134', subseq='1234') == 2

assert count_subseqs(string='1221323', subseq='123') == 5

Jakub answered 2019-10-11T19:09:46Z

0 votes

psh。 O(n)解决方案更好。

通过构建一棵树来思考它:

沿着字符串迭代如果字符为“ 1”,则在树的根部添加一个节点。如果字符为“ 2”,则向每个第一级节点添加一个子级。如果字符为“ 3”,则向每个第二级节点添加一个子级。

返回第三层节点的数量。

这将是空间效率低下的,所以为什么我们不只存储每个深度的节点数:

infile >> in;

long results[3] = {0};

for(int i = 0; i < in.length(); ++i) {

switch(in[i]) {

case '1':

results[0]++;

break;

case '2':

results[1]+=results[0];

break;

case '3':

results[2]+=results[1];

break;

default:;

}

}

cout << results[2] << endl;

datdo answered 2019-10-11T19:10:36Z

0 votes

如何计算数字数组中所有三元序列1..2..3。

快速而简单

注意,我们不需要查找所有序列,只需要计数它们即可。 因此,所有搜索序列的算法都过于复杂。

扔掉不是1,2,3的每个数字。 结果将为char数组A

使并行int数组B为0。 从头开始运行A,对A中的每个2进行计数,然后计算A中的3。 将这些数字放入B的适当元素中。

使并行的int数组C为0,从A的每个1的结束计数开始A到其位置之后的B的总和。 将结果放入C中的适当位置。

计算C的总和。

就这些。 复杂度为O(N)。 实际上,对于正常的数字行,将花费缩短源代码行时间大约两倍的时间。

如果该序列将更长,例如M个成员,则该过程可以重复M次。 复杂度将是O(MN),其中N已经是缩短的源字符串的长度。

Gangnus answered 2019-10-11T19:11:53Z

0 votes

对于这个问题,我有一个有趣的O(N)时间和O(M)空间解决方案。

N是文本的长度,M是要搜索的图案的长度。我将向您解释该算法,因为我是用C ++实现的。

让我们假设给出的输入与您提供的一样3141592653并找到其计数为123的模式序列。我将首先获取一个将字符映射到它们在输入模式中位置的哈希图。 我还采用了大小为M的数组,该数组最初初始化为0。

string txt,pat;

cin >> txt >> pat;

int n = txt.size(),m = pat.size();

int arr[m];

map mp;

map ::iterator it;

f(i,0,m)

{

mp[pat[i]] = i;

arr[i] = 0;

}

我从背面开始寻找元素,并检查每个元素是否在图案中。 如果该元素在模式中。 我需要做一些事情。

现在,当我从背面开始寻找是否有2和以前的东西时,我没有发现任何3。 这2对我们没有任何价值。因为在它之后最多找到这样的序列12和123不会形成Ryt吗? 认为。同样在目前的位置上,我已经找到了2,它将仅与先前找到的3形成序列123,并且如果我们先前发现了x 3(如果将找到2之前的部分序列),将形成x序列。因此,完整的算法是每当我找到数组中存在的元素时,便检查其在模式(存储在哈希图中)中对应存在的位置j。 我只是增加

arr[j] += arr[j+1];

表示它将有助于在黑麦草之前发现3个序列?如果找到的j是m-1,我会简单地增加它

arr[j] += 1;

检查下面的代码片段

for(int i = (n-1);i > -1;i--)

{

char ch = txt[i];

if(mp.find(ch) != mp.end())

{

int j = mp[ch];

if(j == (m-1))

arr[j]++;

else if(j < (m-1))

arr[j] += arr[j+1];

else

{;}

}

}

现在考虑事实

数组中的每个索引i存储模式S [i,(m-1)]的子字符串作为输入字符串序列出现的次数所以最后打印出arr [0]的值

cout << arr[0] << endl;

带输出的代码(模式中的唯一字符)[http://ideone.com/UWaJQF]

带输出的代码(允许重复的字符)[http://ideone.com/14DZh7]

延期仅当模式具有唯一元素时有效如果模式具有独特的元素,那么复杂度可能会达到O(MN)怎么办解决方案与之相似,不使用DP,只是当出现在模式中的元素出现时,我们只是增加了与之对应的数组位置j,现在我们必须更新模式中所有这些字符的出现,这将导致O(N * maxfrequency 一个角色)

#define f(i,x,y) for(long long i = (x);i < (y);++i)

int main()

{

long long T;

cin >> T;

while(T--)

{

string txt,pat;

cin >> txt >> pat;

long long n = txt.size(),m = pat.size();

long long arr[m];

map > mp;

map > ::iterator it;

f(i,0,m)

{

mp[pat[i]].push_back(i);

arr[i] = 0;

}

for(long long i = (n-1);i > -1;i--)

{

char ch = txt[i];

if(mp.find(ch) != mp.end())

{

f(k,0,mp[ch].size())

{

long long j = mp[ch][k];

if(j == (m-1))

arr[j]++;

else if(j < (m-1))

arr[j] += arr[j+1];

else

{;}

}

}

}

cout <

}

}

可以以类似的方式扩展,而无需在具有重复的字符串中添加DP,但是复杂度会更高O(MN)

Vinayak Sangar answered 2019-10-11T19:13:38Z

0 votes

基于geeksforgeeks.org的动态编程的Javascript答案和aioobe的答案:

class SubseqCounter {

constructor(subseq, seq) {

this.seq = seq;

this.subseq = subseq;

this.tbl = Array(subseq.length + 1).fill().map(a => Array(seq.length + 1));

for (var i = 1; i <= subseq.length; i++)

this.tbl[i][0] = 0;

for (var j = 0; j <= seq.length; j++)

this.tbl[0][j] = 1;

}

countMatches() {

for (var row = 1; row < this.tbl.length; row++)

for (var col = 1; col < this.tbl[row].length; col++)

this.tbl[row][col] = this.countMatchesFor(row, col);

return this.tbl[this.subseq.length][this.seq.length];

}

countMatchesFor(subseqDigitsLeft, seqDigitsLeft) {

if (this.subseq.charAt(subseqDigitsLeft - 1) != this.seq.charAt(seqDigitsLeft - 1))

return this.tbl[subseqDigitsLeft][seqDigitsLeft - 1];

else

return this.tbl[subseqDigitsLeft][seqDigitsLeft - 1] + this.tbl[subseqDigitsLeft - 1][seqDigitsLeft - 1];

}

}

Patrick Assoa Adou answered 2019-10-11T19:14:03Z

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值