Leetcode——139 Word Break && 140 Word BreakII

Word Break

1.问题描述

给出一个字符串数组作为字典dict与一个目标字符串,在数组中查找字符串能否拼接成目标字符串。举个例子,s = "leetcode", dict = ["leet", "code"],那么s可以由字典中的leet和code组成;再举一个例子,s = "cars", dict = ["car", "ca","rs"],那么s可以由字典中的ca和rs组成。

2.思路一(BFS)

BFS需要一个队列来实现。首先根据在dict中查找s的前缀,如果有,加入队列中,作为遍历的“根”节点。比如上述的第二个例子,先入队的有"car"和"ca"两项;

当队列不为空时,队头top出列,令一个临时字符串temp是从s与top匹配后的字符开始到结束;如果此时temp是空,说明已经匹配完了,直接返回true,如果不为空,则进一步在dict中查找temp的前缀,如果有,加入队列中。

当队列为空且没有返回true时,说明匹配不成功,返回false。

按照这种做法,例子2首先入队"car"和"ca",第一个出队的是"car",temp是"s",在dict中查找不到字符串"s",就没有新的字符串入队;下一个出队的是"ca",那么temp是"rs",在dict中找到"rs"入队,下一步"rs"出队后,temp是空,返回true。

提交后,运行奔溃了,检查之后发现是我截取temp的方法有问题,我使用是s.substr(s.find(top) + top.size());使用find方法在字符串中找到top所在位置的话,在以下情况会出现问题:当s = "bb" 且dict = ["a","b","bbb","bbbb"]时,b入队后,s.find(top)一直都是在位置0,所以,会进入死循环。还有一个重要的问题,是没有使用一个visited数组表示是否访问过。

改正方法是,队列的元素不使用字符串,而是使用位置信息,还是以s = "cars"和dict = ["car", "ca","rs"]为例子,一开始将3和2入队,因为car长度是3,ca长度是2;接下来3出队,visited[3]置为1,将temp的取值就是s.substr(3),截取top后面的部分,然后再进行匹配和入队处理。循环直到队头等于字符串s的长度时,可以返回true,若队列为空还未返回true,则说明没有匹配成功,返回false。

3.代码展示一

bool wordBreak(string s, vector<string>& wordDict) {
	queue<int> qu;
	for (int i = 0; i < wordDict.size(); i++)
		if (s.length() >= wordDict[i].length() && s.find(wordDict[i]) == 0)
			qu.push(wordDict[i].length());
	int visited[s.length()];
	memset(visited, 0, sizeof(visited));
	while (!qu.empty()) {
		int top = qu.front();
		qu.pop();
		if (top == s.length())
			return true;
		if (visited[top] == 0) {
			visited[top] = 1;
			string temp = s.substr(top);
			for (int i = 0; i < wordDict.size(); i++)
			    if (temp.length() >= wordDict[i].length() && temp.find(wordDict[i]) == 0)
					qu.push(top + wordDict[i].length());
		}	
	}
	return false;
}

4.思路二(动态规划)

这道题也可以使用动态规划来实现。要求字符串s是否能匹配成功,子问题可以定义成字符串的前i个字符能否匹配到,所以使用一维数组dp[i]表示前i个字符能否匹配成功。

数组dp有s.length()+1个元素,全初始化为0,dp[0]初始化为1.当计算dp[i]时,如果dp[i-1]是1,首先看第i个字符能否在dict中找到,如果可以,那么dp[i]就是1;如果不行,就继续判断dp[i-2]是否为1,如果是,那么就看后面两个字符组成的字符串s.substr(i-2, 2)能否在dict中找到,如果可以,dp[i]也是1,依次继续循环,直到dp[0]为1时,看字符串s.substr(0, i)能否匹配上,如果可以,则dp[i]也是1.

所以dp[i]的计算取决于dp[i-1]和第i个字符,或者dp[i-2]和字符串(i-1,i),或者dp[i-3]和字符串(i-2,i),....直到dp[0]和字符串(0,i)。


5.代码展示二

bool wordBreak(string s, vector<string>& wordDict) {
	vector<bool> dp(s.size() + 1, false);
	dp[0] = true;

	for (int i = 1; i <= s.size(); i++) {
		for (int j = i - 1; j >= 0; j--) {
			if (dp[j]) {
				string word = s.substr(j, i - j);
				for (int k = 0; k < wordDict.size(); k++)
					if (word == wordDict[k]) {
						dp[i] = true;
						break;
					}
			}
		}
	}
	return dp[s.size()];
}

Word Break II

1.问题描述

上一道题的升级版,给出一个字符串数组作为字典dict与一个目标字符串,不仅要确定是否匹配,还要将所有的组合情况都列举出来。

2.思路

我的思路是,先使用上一题动态规划的方法确定是否能够组成目标字符串,同时将可以匹配的子字符串以pair<start,end>记录下,其中start表示子字符串在目标字符串中的起点,end表示子字符串在目标字符串中的终点,将各个子字符串的pair信息存放在vector容器nums中。

如果是可以组成目标字符串的,那么接下来就是列举所有组合情况。

举个例子,vector容器nums中的数据可能是<0,3>, <0,4>, <3,7>, <4,7>, <7,10>,我们可以把nums看作是边集,也就是我们的目标是找到从起点0到终点10的所有路径。

一开始才用DFS的做法,同时记录路径,出现的情况是在记录<0,3>, <3,7>, <7,10>的路径后,这三条边置为已访问,但是路径<0,4>, <4,7>, <7,10>也需要走<7,10>这条路。所以应该是对DFS进行修改。对下图进行分析:


因为集合是边集,所以按照边的思路来思考。使用一个临时的vector容器temp来记录路径。首先将起点0推入temp中。

  • 一开始访问边0→3,如果3没有在temp中,将3加入temp,然后递归;
  • 接下来访问边3→7,如果7没有在temp中,将7加入temp,然后递归;
  • 接下来访问边7→10,如果10没有在temp中,将10加入temp,然后递归;
  • 因为10和终点相同,所以将记录的路径temp加入路径集中,并把temp的最后一个值pop_back出来;这样一来temp中的值就是0, 3, 7;
  • 回到起点为7的位置,没有其他路,所以结束对起点为7的边集循环,将7从temp中推出;依次,将3也推出来。
  • 此时temp只有0,会访问边0→4,将4加入temp中继续执行。

这个过程不需要isvisit数组来记录是否已访问过,原因是边集的循环是递进的,所以不会出现走重复的路。


3.代码展示

void dfs(vector<pair<int, int> > &nums, int start, int end)
{
	if (start == end)
	{
		vec.push_back(temp);
		temp.pop_back();
		return;
	}

	for (int i = 0; i < nums.size(); i++)
	{
		if (start == nums[i].first && find(temp.begin(), temp.end(), nums[i].second) == temp.end())
		{
			temp.push_back(nums[i].second);
			dfs(nums, nums[i].second, end);
		}
	}
	temp.pop_back();
}
vector<string> wordBreak_II(string s, vector<string>& wordDict) {
	vector<bool> dp(s.size() + 1, false);
	dp[0] = true;
	vector<string> res;
	vector<pair<int, int> > nums;
	//动态规划确定字符串s是否能由wordDict内的字符串组成
	for (int i = 1; i <= s.size(); i++) {
		for (int j = i - 1; j >= 0; j--) {
			if (dp[j]) {
				string word = s.substr(j, i - j);
				for (int k = 0; k < wordDict.size(); k++)
					if (word == wordDict[k]) {
						dp[i] = true;
						pair<int, int> temp(j, i);
						nums.push_back(temp);
						break;
					}
			}
		}
	}
	//如果可以,寻找所有路径
	if (dp[s.size()]) {
		temp.push_back(0);
		dfs(nums, 0, s.length());

		for (int i = 0; i < vec.size(); i++)
		{
			string str = s.substr(0, vec[i][1]);
			for (int j = 1; j < vec[i].size() - 1; j++) {
				str += " ";
				str += s.substr(vec[i][j], vec[i][j + 1] - vec[i][j]);
			}
			res.push_back(str);
		}
	}
	return res;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值