单词拆分java与填表法_【算法题】字符串单词拆分

题目:

给定一个非空字符串 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代码:

classSolution:def wordBreak(self, s: str, wordDict: List[str]) ->bool:

n=len(s)

dp=[False]*(n+1)

dp[0]=Truefor i inrange(n):for j in range(i+1,n+1):if(dp[i] and (s[i:j]inwordDict)):

dp[j]=Truereturn dp[-1]

Java代码

classSolution {public boolean wordBreak(String s, ListwordDict) {int len=s.length();boolean[] dp=new boolean[len+1];

dp[0]=true;

HashSet set=new HashSet<>();//为了快速查字典,放到一个无重复的集合里for(String str:wordDict){

set.add(str);

}for(int i=0;i

dp[j+1]=true;

}

}return dp[len];

}

}

力扣139题:https://leetcode-cn.com/problems/word-break/

HARD版本题目

单词拆分 II

给定一个非空字符串 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代码:

classSolution {

LinkedList stack=new LinkedList<>();

ArrayList list=new ArrayList<>();public List wordBreak(String s, ListwordDict) {int len=s.length();boolean[] dp=new boolean[len+1];

dp[0]=true;

Set set=new HashSet<>();for(String str:wordDict){

set.add(str);

}for(int i=0;i

}//这里用了小巧思:想求dp[j],//只要j以前能有一种切法符合单词都可查就可以break了,//之后继续去求dp[j+1]

for(int j=1;j<=len;j++){for(int i=0;i

dp[j]=true;break;

}

}

}

solution(s,len-1,dp,set);returnlist;

}public void solution(String s,int end,boolean[] dp,Setset){if(end<0){

StringBuilder nsbr=newStringBuilder();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 importdequeclassSolution:def wordBreak(self, s: str, wordDict: List[str]) ->List[str]:

size=len(s)assert size >0

word_set= {word for word inwordDict}

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

breakres=[]

queue=deque()

self.__dfs(s, size, word_set, res, queue, dp)returnresdef __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 inword_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/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于字符串的回文子串计算,也可以用动态规划来实现。动态规划的思路是从小规模子问逐步推导到大规模问,最终得到问的解。 具体来说,我们可以用一个二维数组 `dp[i][j]` 表示字符串从第 i 个字符到第 j 个字符是否为回文子串。当 `dp[i][j]` 为 true 时,表示字符从 i 到 j 的子串是回文子串,否则不是。 那么,如何推导 `dp[i][j]` 的值呢?我们可以根据回文子串的定义,即一个字符串正着读和倒着读都一样,可以得到以下状态转移方程: ``` dp[i][j] = (s.charAt(i) == s.charAt(j) && dp[i+1][j-1]) ``` 这个方程的意思是,当 s[i] 等于 s[j] 时,如果 s[i+1] 到 s[j-1] 的子串也是回文子串,那么s[i]到s[j] 的子串也是回文子串。这是因为回文子串的两端字符相等,且去掉两端字符后剩余部分是回文子串,那么整个串就是回文子串。 最终,回文子串的总数即为 dp 数组值为 true 的元素个数。 根据上述思路,可以得到如下的 Java 代码实现: ``` public int countSubstrings(String s) { int n = s.length(); boolean[][] dp = new boolean[n][n]; int count = 0; // 从右下角开始填表,即先计算长度为 1 的子串,再计算长度为 2 的子串,以此类推 for (int i = n - 1; i >= 0; i--) { for (int j = i; j < n; j++) { // 计算 dp[i][j] 的值 dp[i][j] = s.charAt(i) == s.charAt(j) && (j - i < 3 || dp[i+1][j-1]); if (dp[i][j]) { count++; } } } return count; } ``` 这段代码,我们使用一个二维数组 `dp` 来记录回文子串的状态。首先,我们从右下角开始填表,即先计算长度为 1 的子串,再计算长度为 2 的子串,以此类推。在计算 `dp[i][j]` 的值时,我们使用上述的状态转移方程,判断字符串从第 i 个字符到第 j 个字符是否为回文子串,并累加回文子串的个数。 希望这些代码能对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值