【动态规划】【字符串】【前缀和】1639通过给定词典构造目标字符串的方案数

简介: 【动态规划】【字符串】【前缀和】1639通过给定词典构造目标字符串的方案数

作者推荐

【动态规划】【字符串】【行程码】1531. 压缩字符串

本文涉及知识点

动态规划汇总

C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频

1639. 通过给定词典构造目标字符串的方案数

给你一个字符串列表 words 和一个目标字符串 target 。words 中所有字符串都 长度相同 。

你的目标是使用给定的 words 字符串列表按照下述规则构造 target :

从左到右依次构造 target 的每一个字符。

为了得到 target 第 i 个字符(下标从 0 开始),当 target[i] = words[j][k] 时,你可以使用 words 列表中第 j 个字符串的第 k 个字符。

一旦你使用了 words 中第 j 个字符串的第 k 个字符,你不能再使用 words 字符串列表中任意单词的第 x 个字符(x <= k)。也就是说,所有单词下标小于等于 k 的字符都不能再被使用。

请你重复此过程直到得到目标字符串 target 。

请注意, 在构造目标字符串的过程中,你可以按照上述规定使用 words 列表中 同一个字符串 的 多个字符 。

请你返回使用 words 构造 target 的方案数。由于答案可能会很大,请对 109 + 7 取余 后返回。

(译者注:此题目求的是有多少个不同的 k 序列,详情请见示例。)

示例 1:

输入:words = [“acca”,“bbbb”,“caca”], target = “aba”

输出:6

解释:总共有 6 种方法构造目标串。

“aba” -> 下标为 0 (“acca”),下标为 1 (“bbbb”),下标为 3 (“caca”)

“aba” -> 下标为 0 (“acca”),下标为 2 (“bbbb”),下标为 3 (“caca”)

“aba” -> 下标为 0 (“acca”),下标为 1 (“bbbb”),下标为 3 (“acca”)

“aba” -> 下标为 0 (“acca”),下标为 2 (“bbbb”),下标为 3 (“acca”)

“aba” -> 下标为 1 (“caca”),下标为 2 (“bbbb”),下标为 3 (“acca”)

“aba” -> 下标为 1 (“caca”),下标为 2 (“bbbb”),下标为 3 (“caca”)

示例 2:

输入:words = [“abba”,“baab”], target = “bab”

输出:4

解释:总共有 4 种不同形成 target 的方法。

“bab” -> 下标为 0 (“baab”),下标为 1 (“baab”),下标为 2 (“abba”)

“bab” -> 下标为 0 (“baab”),下标为 1 (“baab”),下标为 3 (“baab”)

“bab” -> 下标为 0 (“baab”),下标为 2 (“baab”),下标为 3 (“baab”)

“bab” -> 下标为 1 (“abba”),下标为 2 (“baab”),下标为 3 (“baab”)

示例 3:

输入:words = [“abcd”], target = “abcd”

输出:1

示例 4:

输入:words = [“abab”,“baba”,“abba”,“baab”], target = “abba”

输出:16

提示:

1 <= words.length <= 1000

1 <= words[i].length <= 1000

words 中所有单词长度相同。

1 <= target.length <= 1000

words[i] 和 target 都仅包含小写英文字母。

动态规划

动态规划的状态表示

vCan[i][j] 如果包括x,words有几个元素的下标j 是’a’+i。

pre[j] 表示words[?][0,j)和target[0,i)匹配。dp[j]表示 words[?][0,j)和target[0,i+1)匹配。

动态规划的转移方程

dp[j] = ∑ k = 0 j \Large\sum_{k=0}^{j}k=0jpre[k]

vCan[i]必须包括j。

动态规划的初始值

pre[0]=1,其它全为0。

动态规划的填表顺序

i从小到大,j从小到大。

动态规划的返回值

pre的和。

代码

核心代码

template<int MOD = 1000000007>
class C1097Int
{
public:
  C1097Int(long long llData = 0) :m_iData(llData% MOD)
  {
  }
  C1097Int  operator+(const C1097Int& o)const
  {
    return C1097Int(((long long)m_iData + o.m_iData) % MOD);
  }
  C1097Int& operator+=(const C1097Int& o)
  {
    m_iData = ((long long)m_iData + o.m_iData) % MOD;
    return *this;
  }
  C1097Int& operator-=(const C1097Int& o)
  {
    m_iData = (m_iData + MOD - o.m_iData) % MOD;
    return *this;
  }
  C1097Int  operator-(const C1097Int& o)
  {
    return C1097Int((m_iData + MOD - o.m_iData) % MOD);
  }
  C1097Int  operator*(const C1097Int& o)const
  {
    return((long long)m_iData * o.m_iData) % MOD;
  }
  C1097Int& operator*=(const C1097Int& o)
  {
    m_iData = ((long long)m_iData * o.m_iData) % MOD;
    return *this;
  }
  bool operator<(const C1097Int& o)const
  {
    return m_iData < o.m_iData;
  }
  C1097Int pow(long long n)const
  {
    C1097Int iRet = 1, iCur = *this;
    while (n)
    {
      if (n & 1)
      {
        iRet *= iCur;
      }
      iCur *= iCur;
      n >>= 1;
    }
    return iRet;
  }
  C1097Int PowNegative1()const
  {
    return pow(MOD - 2);
  }
  int ToInt()const
  {
    return m_iData;
  }
private:
  int m_iData = 0;;
};
class Solution {
public:
  int numWays(vector<string>& words, string target) {
    m_c = words[0].size();
    vector<vector<int>> vCan(26,vector<int>(m_c));
    for (int i = 0; i < m_c; i++)
    {
      for (int j = 0; j < words.size(); j++)
      {
        const int index = words[j][i] - 'a';
        vCan[index][i]++;
      }
    }
    vector<C1097Int<>> pre(m_c + 1);
    pre[0] = 1;
    for (int i = 0; i < target.size(); i++)
    {
      vector<C1097Int<>> dp(m_c + 1);
      C1097Int<> biSum = 0;
      int k = 0;
      for (int j = 0 ; j < m_c ; j++ )
      {
        while (k <= j)
        {
          biSum += pre[k++];
        }
        dp[j+1] = biSum*vCan[target[i]-'a'][j];
      }
      pre.swap(dp);
    }
    return std::accumulate(pre.begin(), pre.end(), C1097Int<>()).ToInt();
  }
  int m_c;
};

核心代码

template<class T>
void Assert(const T& t1, const T& t2)
{
  assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{ 
  vector<string> words;
  string target;
  {
    Solution sln;
    words = { "acca", "bbbb", "caca" }, target = "aba";
    auto res = sln.numWays(words, target);
    Assert(res, 6);
  }
  {
    Solution sln;
    words = { "abba", "baab" }, target = "bab";
    auto res = sln.numWays(words, target);
    Assert(res, 4);
  }
  {
    Solution sln;
    words = { "abcd" }, target = "abcd";
    auto res = sln.numWays(words, target);
    Assert(res, 1);
  }
  {
    Solution sln;
    words = { "abab", "baba", "abba", "baab" }, target = "abba";
    auto res = sln.numWays(words, target);
    Assert(res, 16);
  }
}

2023年2月

class C1097Int

{

public:

C1097Int(int iData = 0) :m_iData(iData)

{

}
 C1097Int  operator+(const C1097Int& o)const
 {
   return C1097Int((m_iData + o.m_iData) % s_iMod);
 }
 C1097Int&  operator+=(const C1097Int& o)
 {
   m_iData = (m_iData + o.m_iData) % s_iMod;
   return *this;
 }
 C1097Int  operator*(const C1097Int& o)const
 {
   return((long long)m_iData *o.m_iData) % s_iMod;
 }
 C1097Int&  operator*=(const C1097Int& o)
 {
  m_iData =((long long)m_iData *o.m_iData) % s_iMod;
   return *this;
 }
 int ToInt()const
 {
   return m_iData;
 }

private:

int m_iData = 0;;

static const int s_iMod = 1000000007;

};

int operator+(int iData, const C1097Int& int1097)

{

int iRet = int1097.operator+(C1097Int(iData)).ToInt();

return iRet;

}

int& operator+=(int& iData, const C1097Int& int1097)

{

iData = int1097.operator+(C1097Int(iData)).ToInt();

return iData;

}

class Solution {

public:

int numWays(vector& words, string target) {

m_r = words.size();

m_c = words[0].size();

m_vCharNums.assign(m_c, vector(26));

for (int r = 0; r < m_r; r++)

{

for (int c = 0; c < m_c; c++)

{

m_vCharNums[c][words[r][c] - ‘a’]++;

}

}

// 已经处理完的target串 对应words[?]的前pre[i]个字符

vector pre(m_c + 1);

pre[0] = 1;

for (const auto& ch : target)

{

vector dp(m_c + 1);

C1097Int iSub = 0;

for (int j = 1; j <= m_c; j++)

{

iSub += pre[j - 1];

dp[j] = iSub * m_vCharNums[j - 1][ch - ‘a’];

}

pre.swap(dp);

}

return std::accumulate(pre.begin(), pre.end(), C1097Int(0)).ToInt();

}

int m_r;

int m_c;

vector<vector> m_vCharNums;

};


相关文章
|
3月前
|
算法 测试技术 C++
【动态规划】【字符串】1092. 最短公共超序列
【动态规划】【字符串】1092. 最短公共超序列
|
3月前
|
算法 前端开发
根据模式串构造最小数字
根据模式串构造最小数字
19 0
|
7月前
|
算法 测试技术 C#
C++算法前缀和的应用:分割数组的最大值的原理、源码及测试用例
C++算法前缀和的应用:分割数组的最大值的原理、源码及测试用例
|
5月前
|
算法 测试技术 编译器
【算法 | 实验18】在字符矩阵中查找给定字符串的所有匹配项
题目描述 题目 在字符矩阵中查找给定字符串的所有匹配项 给定一个M×N字符矩阵,以及一个字符串S,找到在矩阵中所有可能的连续字符组成的S的次数。所谓的连续字符,是指一个字符可以和位于其上下左右,左上左下,右上右下8个方向的字符组成字符串。用回溯法求解。
35 1
|
4月前
【Leetcode 2707】字符串中的额外字符 —— 动态规划
1. 状态定义:把`s[i−1]`当做是额外字符,`d[i] = d[i−1] + 1` 2. 状态转移方程:遍历所有的`j(j∈[0,i−1])`,如果子字符串`s[j...i−1]`存在于`dictionary`中,那么`d[i] = min d[j] 3. 初始状态`d[0] = 0`,最终答案为`d[n]`
|
4月前
|
算法 JavaScript 测试技术
动态规划 多源路径 字典树 LeetCode2977:转换字符串的最小成本
动态规划 多源路径 字典树 LeetCode2977:转换字符串的最小成本
|
4月前
|
算法 Java C++
数据结构与算法面试题:给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。(提示:使用动态规划或者中心扩散)
数据结构与算法面试题:给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。(提示:使用动态规划或者中心扩散)
44 0
|
5月前
|
算法 Java
给定一个字符串数组,如何找到其中最长的回文子串? 要求:编写一个Java函数,输入一个字符串数组,输出其中最长的回文子串。要求时间复杂度为O(n^2)。可以考虑使用动态规划或中心扩展的方法来优化算法。
给定一个字符串数组,如何找到其中最长的回文子串? 要求:编写一个Java函数,输入一个字符串数组,输出其中最长的回文子串。要求时间复杂度为O(n^2)。可以考虑使用动态规划或中心扩展的方法来优化算法。
42 1
|
5月前
|
算法 测试技术 C#
C++前缀和算法的应用:分割数组的最多方案数 原理源码测试用例
C++前缀和算法的应用:分割数组的最多方案数 原理源码测试用例
|
存储 索引
【题型总结】使用双指针+哈希表寻找符合某种条件的字符串/数字的个数
记录第一出现的字符的位置和最后一个出现的字符的位置,如果相减长度为s1的长度,那么证明是连续出现的子串
68 0