题目链接:https://leetcode.com/problems/word-break/
要求给字符串进行切分(break)是的得到的单词都在字典中。
思路一:首先想到的暴力解法:在每个可能的位置将字符串切分为两部分,然后检查这两部分是否都存在于字符串中,如果某部分字符串不存在,则将此字符串继续进行切分,直到存在或者长度为1为止,这是个极其暴力的算法,存在大量重复操作,代码如下:
class Solution {
public static HashSet<String> hs;
public boolean wordBreak(String s, List<String> wordDict) {
hs=new HashSet<String>();
Iterator<String> iter=wordDict.iterator();
while(iter.hasNext())
hs.add(iter.next());
return recursive(s);
}
public static boolean recursive(String s)
{
if(hs.contains(s))
return true;
String s1=null;
String s2=null;
boolean isbreak=false;
for(int i=0;i<=s.length()-2;i++)
{
s1=s.substring(0,i+1);
s2=s.substring(i+1,s.length());
isbreak =isbreak || (recursive(s1) && recursive(s2));
}
return isbreak;
}
}
算法复杂度高达O(2^n),自然会想到用动态规划来讲中间结果缓存下来,于是想到用二维数组来保存枚举的子字符串的
然后又想了一个稍微简化的解法:
图示如下:
"aaaaa"
["aaaa","aa"]
|
0 |
1 |
2 |
3 |
4 |
0 |
|
1 |
|
1 |
|
1 |
|
|
1 |
|
1 |
2 |
|
|
|
1 |
|
3 |
|
|
|
|
1 |
4 |
|
|
|
|
|
class Solution {
public static HashSet<String> hs;
public static boolean[][] memo;
public static int n;
public static boolean flag;
public boolean wordBreak(String s, List<String> wordDict) {
flag=false;
n=s.length();
memo=new boolean[n][n];
Iterator<String> iter=wordDict.iterator();
hs=new HashSet<>();
while(iter.hasNext())
hs.add(iter.next());
for(int i=0;i<s.length();i++)
{
for(int j=i;j<s.length();j++)
{
String st=s.substring(i,j+1);
if(hs.contains(st))
memo[i][j]=true;
}
}
recursive(0);
return memo[0][n-1];
}
public static void recursive(int j)
{
if(j>=n)
{
return;
}
for(int i=j;i<n;i++)
{
if(!flag && memo[j][i])
{
memo[0][i]=true;
if(i==n-1)
flag=true;
recursive(i+1);
}
}
}
}
这个解法也TLE了,我根据TLE的测试用例发现还是枚举子字符串这一步太消耗时间了,并且遇到连续相同的字母组成的字符串时,递归那一步时间复杂度也较高,因为我将子字符串作为动态规划的单元记录下来,然后通过相邻关系拼接进行动态规划,所以可以考虑将目标字符串的前缀串是否可以break在字典中作为动态规划的单元。代码如下:
class Solution {
public boolean wordBreak(String s, List<String> wordList)
{
if (s == null || s.length() == 0)
return false;
int n = s.length();
Set<String> set = new HashSet<>();
for (String word : wordList)
set.add(word);
boolean[] dp = new boolean[n];
for (int i = 0; i < n; i++) //外循环是填写dp数组的过程
{
for (int j = 0; j <= i; j++) //内循环是找符合条件的前缀和存在于字典中的子字符串(前缀串与该子字符串组成当前结尾于i位置的前缀串)
{
String sub = s.substring(j, i + 1);
if (set.contains(sub) && (j == 0 || dp[j - 1]))
{
dp[i] = true;
break;
}
}
}
return dp[n - 1];
}
}
这条题目解的过程真艰辛,不过动态规划一般是基于暴力递归搜索来优化的,只是优化的角度需要精致的思考。