LintCode第107题-单词拆分-新版

描述

给定字符串 s 和单词字典 dict,判断是否可以利用字典 dict 中的出现的单词拼接出 s,其中字典 dict 中的单词可以重复使用。

因为我们已经使用了更强大的数据,所以普通的DFS方法无法解决此题

s.length<=1e5

dict.size<=1e5

样例 1:

输入:

复制代码
s = "lintcode"
dict = ["lint", "code"]

输出:

复制代码
true

解释:

lintcode可以分成lint和code。

样例 2:

输入:

复制代码
s = "a"
dict = ["a"]

输出:

复制代码
true

解释:

a在dict中

思路:判断dict是否可以拼出目标字符串s.首先判断使用哪种算法

简单来说 常用的求方案总数 最值 可行性大多数用动态规划 即不是具体的方案

也可以细分通过最优子结构和重叠子问题判断

最优子结构(切最后一段)

把字符串 s[0..i) 能否拆分,拆到"最后一段"来想:

存在某个切分点 j<i,若

  • 前缀 s[0..j) 可拆(这是一个更小的同类问题),并且

  • 最后一段 s[j..i) 在词典里,

    s[0..i) 也可拆。

    这直接给出状态与转移:

  • dp[i] = OR_{j<i}( dp[j] && inDict(s[j..i)) ) OR即逻辑或

  • 等价文字写法就是存在某个 j(0 ≤ j < i),使得前缀 s[0..j) 可拆且最后一段 s[j..i) 在字典里,那么 dp[i] 为真

重叠子问题

不同的 i 会反复查询相同的前缀可行性,比如 dp[10]dp[11] 都会用到 dp[7]dp[8] 等。DP一次算好可以重复利用

知道用动态规划后那么下面就是动态规划的四要素

//状态 dp[i]

//状态转移方程 递推一下

判断"前 i 个字符可拼"就看"它的最后一刀切在哪个 j"。

"左边可拼 + 右边是词" ⇒ 整体可拼。

所以在代码里对所有 j

if (dp[j] && wordSet.contains(s.substring(j, i))) {

dp[i] = true; break;

}

其中状态转移方程为:dp[i]=dp[j]&&wordSet.contains(s.substring(j,i));

//初始化dp[0]=true;

//边界条件s.length()<Set.size()&&s.length()>=0

代码如下:

public class Solution {

/**

* @param s: A string

* @param wordSet: A dictionary of words dict

* @return: A boolean

*/

public boolean wordBreak(String s, Set<String> wordSet) {

// 健壮性

if (s == null || wordSet == null) return false;

int n=s.length();

boolean dp[]=new boolean[n+1];//易错点1 容易少写一个 为n因为dp[0]占据了一位 dp[i] 表示:前 i 个字符 即

//s = "leet",n = 4

//我们需要 dp[0], dp[1], dp[2], dp[3], dp[4] 共 5 个状态:

//dp[0]=空串,dp[4]=整串结果 →自然是 n+1 即dp[]数组是状态表示 表示长度为 n 的串,有 //n+1 个切分点(含头尾)

dp[0]=true;

int maxWordLength=0;//最长单词长度

int minWordLength=Integer.MAX_VALUE;//最短单词长度

//maxWordLength:保证不检查比任何词都长的片段 → 收紧 j 的起点。

//minWordLength:保证不检查比任何词都短的片段 → 可以提前从 i=minWordLength 开跑,//并收紧 j 的终点 剪枝截取片段是从j至i

//分别统计单词的最长长度和最短长度

for(String str:wordSet)

{

int currentWordLength=str.length();

if (currentWordLength == 0)

{

continue;

}

if(maxWordLength<currentWordLength)

{

maxWordLength=currentWordLength;

}

if(minWordLength>currentWordLength)

{

minWordLength=currentWordLength;

}

}

for(int i=minWordLength;i<=n;i++)

{

int jStart = Math.max(0, i - maxWordLength);

for (int j = jStart; j < i; j++)

{

if(dp[j]&&wordSet.contains(s.substring(j,i)))

{

dp[i] = true;

break;

}

}

}

return dp[n];

}

}