【算法题】字符串单词拆分
题目:
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
- 拆分时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"] 输出: true 解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"] 输出: true 解释: 返回 true 因为"applepenapple"可以被拆分成"apple pen apple"。 注意你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] 输出: false
解题:
题目的要求就是,一个字符串必须刚好被分为几个单词且这几个单词都在词典里,像实例3那样子交叉着是不行的,要独立分出来,其实这样就反而简单了呢。
假如有一个字符串是“youarelovely”,作为一个普通人,先想到是从头开始找第一个字典里的单词,也就是you;
然后再找发现are在字典里,而are前面的you也被确认过了,那么l以前的都在字典里可查;
再接着看,发现lovely也在字典里,而且e及以前的都被确认存在了,那么y及以前的都在字典里可查;
y之后没有了,返回y记录,发现是true,说明这整个字符串在字典里都有迹可循,完工。
以上就是一个动态规划的办法,动态规划需要有一个规则,也就是什么时候赋什么值。明明同学查了一个新词儿是对的,而且这个词儿之前的老词儿都对,那么新词儿及以前的都对,查后面的就行了。设置一个dp一维数组,当前字母及以前的内容都符合,设为true,如果0到i的之前都被确认过存在,而且i到j的刚刚确定存在,则可以说j及以前的都在词典里符合要求。
PS:注意注意!dp[0]要设为true,因为要考虑整个字符串是一个单词的情况时,该单词前面的词儿为空,空气无处不在所以也一定能够在词典里找到(跑火车了)。。。其实是因为dp[i]表示的是i以前字符串是否符合要求,不包括i,因此为了让最后一个字符有存在感,dp长度要设为字符串长度+1。而dp[0]表示的是0以前位置的内容,为了符合if语句(前面的部分为真 且 后面的部分也可查)要设置为true。
Python3代码:
class Solution: def wordBreak(self, s: str, wordDict: List[str]) -> bool: n=len(s) dp=[False]*(n+1) dp[0]=True for i in range(n): for j in range(i+1,n+1): if(dp[i] and (s[i:j]in wordDict)): dp[j]=True return dp[-1]
Java代码
class Solution { public boolean wordBreak(String s, List<String> wordDict) { int len=s.length(); boolean[] dp=new boolean[len+1]; dp[0]=true; HashSet<String> set=new HashSet<>();//为了快速查字典,放到一个无重复的集合里 for(String str:wordDict){ set.add(str); } for(int i=0;i<len;i++){ for(int j=i;j<len;j++){ if(dp[i] && set.contains(s.substring(i,j+1)))//前面的都可查,后面一段也可查 dp[j+1]=true; } } return dp[len]; } }
力扣139题:https://leetcode-cn.com/problems/word-break/
HARD版本题目
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。
说明:
- 分隔时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
示例 1:
输入: s = "catsanddog" wordDict =["cat", "cats", "and", "sand", "dog"]输出:[ "cats and dog", "cat sand dog" ]
让把给的一串字母自己切分,使得每一个部分都在字典里面。
以示例为例思考一下:
还是用之前求的dp,从前面一个个词切还是后面呢?
当dp[i]=true而且i~j部分的词在字典里,就从i砍一刀,分为两个词,然后就该从j开始了,怎么知道j到哪里是一个词呢?
毕竟dp保存的是从0到当前这一整段里都能在字典中找到,可能是一个词也可能是多个,完了,j所在的词儿如果正好在字符串中间,前后都有若干个词,凉凉。
因此,鸡贼的我们要从后往前切。
如果dp[i]=true而且i到end的词可以查到,那么就果断在i砍一刀,然后把最后这个词加入一个list中,就不管i后面的了,对0~i的部分做同样操作,把end设置为i。
直到end=0,无处可砍事已至此,一种切分情况完整保存。
当然根据例子看,有很多种情况,这些路线在我们一个一个动i的时候就能分出来让它们自己砍,没错,又要递归了,就是那永远迷迷糊糊的递归。
这道题科学来讲是“动态规划+回溯”。先得到每个字符位置以前是否合法,也就是dp数组,再从后往前倒着走,每次都在之前算好的合法点切开。
Java代码:
class Solution { LinkedList<String> stack=new LinkedList<>(); ArrayList<String> list=new ArrayList<>(); public List<String> wordBreak(String s, List<String> wordDict) { int len=s.length(); boolean[] dp=new boolean[len+1]; dp[0]=true; Set<String> set=new HashSet<>(); for(String str:wordDict){ set.add(str); } for(int i=0;i<len;i++){ if(set.contains(s.substring(0,i+1)))dp[i+1]=true; } //这里用了小巧思:想求dp[j], // 只要j以前能有一种切法符合单词都可查就可以break了, //之后继续去求dp[j+1] for(int j=1;j<=len;j++){ for(int i=0;i<j;i++){ if(dp[i] && set.contains(s.substring(i,j))) { dp[j]=true; break; } } } solution(s,len-1,dp,set); return list; } public void solution(String s,int end,boolean[] dp,Set<String> set){ if(end<0){ StringBuilder nsbr=new StringBuilder(); for(String n:stack){ nsbr.append(n+" "); } nsbr.deleteCharAt(nsbr.length()-1); list.add(nsbr.toString()); } for(int i=0;i<=end;i++){ if(dp[i]){ //i前面的都合法,那么只要最后一个单词(i~end)也合法就可以切 String last=s.substring(i,end+1); if(set.contains(last)){ stack.addFirst(last); solution(s,i-1,dp,set); //用的是同一个容器,因此当运行到这里时就代表切last的路线已完成 //把last移除,再去找其它的切法 stack.removeFirst(); } } } } }
Python代码:
from collections import deque class Solution: def wordBreak(self, s: str, wordDict: List[str]) -> List[str]: size = len(s) assert size > 0 word_set = {word for word in wordDict} dp = [0 for _ in range(size + 1)]#0~size dp[0] = 1 for i in range(1, size + 1):#i: 1~size for j in range(i):#j: 0~i-1 if dp[j] and s[j:i] in word_set:#0~j-1 true; j~i-1 in dp[i] = 1 break res = [] queue = deque() self.__dfs(s, size, word_set, res, queue, dp) return res def __dfs(self, s, end, word_set, res, queue, dp): if end == 0: res.append(' '.join(queue)) return for i in range(end): #0~end-1 if dp[i]: #0~i-1 true suffix = s[i:end] #i~end-1 if suffix in word_set: queue.appendleft(suffix) self.__dfs(s, i, word_set, res, queue, dp) #0~i-1 queue.popleft()
https://leetcode-cn.com/problems/word-break-ii/

浙公网安备 33010602011771号