【问题标题】:Counting the number of ways to make up a string计算组成字符串的方法数
【发布时间】:2021-11-30 16:46:52
【问题描述】:

我刚刚开始学习动态规划,并且能够解决一些基本问题,例如斐波那契、背包和其他一些问题。遇到问题 下面,我被卡住了,不知道如何继续前进。让我感到困惑的是,在这种情况下,基本情况是什么,以及重叠的问题。不知道 这使我无法发展关系。在这个例子中,它们不像我之前解决的那样明显。

假设给定一个字符串 origString、一个字符串 toMatch 和一个大于或等于 0 的数字 maxNum。我们如何计算有多少种方法可以将字符串 origString 的非空和非重叠子字符串的 maxNum 个数取为组成字符串 toMatch?

示例:

如果 origString = "ppkpke",并且 toMatch = "ppke"

maxNum = 1:countWays("ppkpke", "ppke", 1) 将给出 0,因为 toMatch 不是 origString 的子字符串。

maxNum = 2: countWays("ppkpke", "ppke", 2) 将给出 4,因为由 "ppkpke" 组成的 2 个子字符串的 4 种不同组合可以生成 "ppke"。 这些字符串是 "ppk" & "e", "pp" & "ke" , "p" & "pke" (不包括 "p") 和 "p" & "pke" (不包括 "k")

【问题讨论】:

  • 这与Sampling indices from a list with constraints 非常相似,只是您只要求计数而不是结果列表。
  • 例如,如果您想要总路数而不为 k 选择特定值,则 from Bio import pairwise2; print(len(pairwise2.align.globalxx('ppkpke', 'ppke'))) 将打印 4。
  • 请说明countWays("ppke", "ppke", 2) 的预期输出。

标签: algorithm dynamic-programming


【解决方案1】:

首先要注意的是,尽管我的解决方案恰好与小型测试集的预期输出相匹配,但它很可能是错误的。您可以仔细检查您可能拥有的其他示例等。

算法遍历较长的字符串并尝试将较短的字符串展开。算法的增量状态由 3 个元素的元组组成:

  1. 长字符串坐标i(origString[i] == toMatch[j])
  2. 短字符串坐标j(origString[i] == toMatch[j])
  3. 我们进入那个^^^状态的方法数

然后我们只是一遍又一遍地沿着字符串走,使用存储的、先前发现的状态,并以典型的动态编程方式总结实现每个状态的方式的总数。

要使状态算作解决方案,j 必须在短字符串的末尾并且动态算法的迭代次数必须等于我们想要的子字符串数那时(因为每次迭代都添加了一个子字符串)。

从作业中我并不完全清楚 maxNum 是否真的意味着类似“exactNum”,即正好有那么多子字符串,或者我们是否应该对所有较低或相等数量的子字符串求和。所以函数返回一个类似{#substrings:#decompositions}的字典,这样就可以根据需要调整输出了。

#!/usr/bin/env python

def countWays(origString, toMatch, maxNum):
  origLen = len(origString)
  matchLen = len(toMatch)
  state = {}
  for i in range(origLen):
    for j in range(matchLen):
      o = i + j
      if origString[o] != toMatch[j]:
        break
      state[(o, j)] = 1
  sums = {}
  for n in range(1, maxNum):
    if not state:
      break
    nextState = {}
    for istart, jstart in state:
      prev = state[(istart, jstart)]
      for i in range(istart + 1, origLen):
        for j in range(jstart + 1, matchLen):
          o = i + j - jstart - 1
          if origString[o] != toMatch[j]:
            break
          nextState[(o, j)] = prev + nextState.get((o, j), 0)
    sums[n] = sum(state[(i, j)] for i, j in state if j == matchLen - 1)
    state = nextState
  sums[maxNum] = sum(state[(i, j)] for i, j in state if j == matchLen - 1)
  return sums

result = countWays(origString='ppkpke', toMatch='ppke', maxNum=5)

print('for an exact number of substrings:', result)
print(' for up to a number of substrings:', {
  n: s for n, s in ((m, sum(result[k] for k in range(1, m + 1)))
                    for m in range(1, 1 + max(result.keys())))})

这个^^^ 代码是一个快速而丑陋的黑客,仅此而已。有很大的改进空间,包括(但不限于)生成器函数的使用(yield),@memoize 的使用等。以下是一些输出:

for an exact number of substrings: {1: 0, 2: 4, 3: 8, 4: 4, 5: 0}
 for up to a number of substrings: {1: 0, 2: 4, 3: 12, 4: 16, 5: 16}

存储更多动态状态(例如,为每个n 保留它)然后重构和漂亮打印(有效)确切的字符串(de)将是一个有趣(并且非常具有挑战性)的练习被计数的作品。

【讨论】:

  • 为了比较,当我运行[countWays('ppkpke', 'ppke', k) for k in range(6)]时,我的解决方案返回[0, 0, 4, 4, 4, 4]
  • @Stef 这听起来不对,至少对于 3 个子字符串来说是这样,因为 it is possible to find 8 decompositions there 或者我误解了任务。
  • 还不错;我的解决方案不允许相邻的子字符串,所以它不算(p p) (k) (e);但允许“k 或更少”子字符串而不是“恰好 k”子字符串。
  • 我修复了我的代码,使其只允许“恰好 k”个子字符串。现在它返回 [0, 0, 4, 0, 0, 0]
  • OP 已发表评论。你的解释是正确的。我编辑了我的代码,现在我也得到了 [0, 0, 4, 8, 4, 0]
【解决方案2】:

这是一个递归解决方案。

比较sourcetarget的第一个字符,如果相等,则选择接受它(在两个字符串中前进1个字符)或不接受它(在source中前进1个字符)但不在target)。每次创建新的子字符串时,k 的值都会递减;还有一个额外的变量continued,如果我们正在构建子字符串,则为 True,否则为 False。

def countWays(source, target, k, continued=False):
  if len(target) == 0:
    return (k == 0)
  elif (k == 0 and not continued) or len(source) == 0:
    return 0
  elif source[0] == target[0]:
    if continued:
      return countWays(source[1:], target[1:], k, True) + countWays(source[1:], target[1:], k-1, True) + countWays(source[1:], target, k, False)
    else:
      return countWays(source[1:], target[1:], k-1, True) + countWays(source[1:], target, k, False)
  else:
    return countWays(source[1:], target, k, False)

print(countWays('ppkpke', 'ppke', 1))
# 0
print(countWays('ppkpke', 'ppke', 2))
# 4
print(countWays('ppkpke', 'ppke', 3))
# 8
print(countWays('ppkpke', 'ppke', 4))
# 4
print(countWays('ppkpke', 'ppke', 5))
# 0

【讨论】:

  • 谢谢,我很感激。据我了解,该解决方案可能包括子字符串
  • @TabPo 好的,我通过添加一个递归调用来允许相邻的子字符串,以防continued 为真。
猜你喜欢
  • 1970-01-01
  • 2018-04-20
  • 2021-01-24
  • 1970-01-01
  • 1970-01-01
  • 2011-08-17
  • 1970-01-01
  • 2020-10-03
相关资源
最近更新 更多