题解:学而思编程 单词的划分
【题目来源】
学而思编程:单词的划分
【题目描述】
有一个很长的由小写字母组成字符串。为了便于对这个字符串进行分析,需要将它划分成若干个部分,每个部分都必须是字典中的一个单词。出于减少分析量的目的,我们希望划分出的单词数越少越好。你就是来完成这一划分工作的。
给出字符串和字典,输出最少的划分单词数。
【输入】
第 \(1\) 行,\(1\) 个字符串。(字符串的长度不超过 \(1000\))
第 \(2\) 行,\(1\) 个整数 \(n\),表示字典中单词的个数。(\(n≤100\))
第 \(3\) 到 \(n+2\) 行,每行列出一个字典中的单词。(单词长度不超过 \(100\))
【输出】
【输入样例】
realityour
5
real
reality
it
your
our
【输出样例】
2
【核心思想】
-
问题分析:给定一个长度为 \(m\) 的字符串 \(s\) 和一个包含 \(n\) 个单词的字典,要求将字符串划分成若干个部分,每个部分都必须是字典中的单词,求最少划分单词数。这是一个线性DP问题,关键在于如何高效判断子串是否在字典中并进行状态转移。
-
算法选择:
- 线性DP:定义
dp[i]为组成前 \(i\) 个字符所需的最少单词数 - 子串匹配:遍历所有可能的子串,检查是否在字典中
- 逆向枚举:从位置 \(i\) 向前枚举子串起点 \(j\),减少重复计算
- 线性DP:定义
-
关键步骤:
- 初始化:
dp[0] = 0(空字符串需要0个单词)dp[i] = INF(初始化为较大值)- 将字符串前加一个空格,使下标从1开始
- 状态转移(遍历每个位置 \(i\) 从 \(1\) 到 \(m\)):
- 从位置 \(i\) 向前枚举子串起点 \(j\)(\(j\) 从 \(i\) 到 \(1\))
- 提取子串 \(s[j..i]\)(长度为 \(i-j+1\))
- 检查子串是否在字典中:
- 若在字典中:
dp[i] = min(dp[i], dp[j-1] + 1) - 解释:前 \(j-1\) 个字符需要
dp[j-1]个单词,加上当前子串1个单词
- 若在字典中:
- 结果:
dp[m](整个字符串的最少单词数)
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(m^2 \cdot n \cdot L)\),其中 \(m\) 为字符串长度,\(n\) 为字典大小,\(L\) 为单词平均长度
- 外层循环 \(O(m)\),内层循环 \(O(m)\),字符串比较 \(O(L)\),字典查找 \(O(n)\)
- 空间复杂度:\(O(m + n \cdot L)\),DP数组和字典存储
- 时间复杂度:\(O(m^2 \cdot n \cdot L)\),其中 \(m\) 为字符串长度,\(n\) 为字典大小,\(L\) 为单词平均长度
-
线性DP的核心思想:
- 状态定义:
dp[i]表示前 \(i\) 个元素(或位置 \(i\))的最优解 - 递推关系:通过枚举前一个分割点,将问题分解为子问题
- 子串匹配:通过逆向枚举起点,检查所有可能的子串是否在字典中
- 最优子结构:当前最优解由之前某个位置的最优解转移而来
- 适用于字符串分割、单词拆分、最优划分类问题
- 状态定义:
【算法标签】
线性DP-一维
【代码详解】
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
int n, dp[1005], m; // n: 词典大小,dp: 动态规划数组,m: 字符串长度
string s, ls[105]; // s: 目标字符串,ls: 词典字符串数组
// 检查子串是否在词典中
bool chk(string s)
{
for (int i = 1; i <= n; i++)
{
if (s == ls[i])
{
return 1; // 找到匹配
}
}
return 0; // 未找到匹配
}
int main()
{
cin >> s >> n; // 读入目标字符串和词典大小
m = s.size(); // 获取字符串长度
s = ' ' + s; // 在字符串前加一个空格,使下标从1开始
memset(dp, 0x3f, sizeof(dp)); // 初始化dp数组为较大值
dp[0] = 0; // 空字符串需要0个单词
for (int i = 1; i <= n; i++)
{
cin >> ls[i]; // 读入词典中的单词
}
for (int i = 1; i <= m; i++) // 遍历目标字符串的每个位置
{
for (int j = i; j >= 1; j--) // 从位置i向前遍历可能的子串
{
// 检查子串s[j:i]是否在词典中
if (chk(s.substr(j, i - j + 1)))
{
// 如果子串在词典中,更新dp[i]的最小值
// dp[i] = min(当前最小值, dp[j-1] + 1)
dp[i] = min(dp[i], dp[j - 1] + 1);
}
}
}
cout << dp[m]; // 输出组成整个字符串所需的最小单词数
return 0;
}
【运行结果】
realityour
5
real
reality
it
your
our
2
浙公网安备 33010602011771号