Reverse Words in a String | 2014-03-05 | 14.2% | Medium | |
Word Break II | 2013-10-05 | 16.7% | Hard | |
Word Break | 2013-10-04 | 21.4% | Medium | |
Word Ladder II | 2013-02-10 | 11.6% | Hard | |
Word Ladder | 2013-02-10 | 18.4% | Medium | |
Word Search | 2012-04-18 | 19.6% | Medium | |
Length of Last Word | 2012-03-27 | 28.9% | Easy | |
Substring with Concatenation of All Words | 2012-02-23 | 18.1% | Hard |
1 min window
本题和数据结构联系紧密
维护一个map<char, int> t,保持每个字母的数量。
维护一个queue,保持当前的有效的字母的下标。
维护一个map<char, int> cnt,保持当前队列queue中的字母和数量。
每次从S遍历一个char,如果是有效的字母:
- 修改cnt[char],加1。
- 当计数值刚好等于t[char]的时候,说明有一个新的字母就位了, m++。当m==n的时候,说明所有的字母都就位了。
- 然后从队首取得一个char,查看该char是否是多余的(cnt[char] 超过了t[char],如果是,丢出队列 并更新cnt[char])
- 如果m==n,即所有的字母都就位,则用当前的下表和队首的下标构成的区间去更新结果(最短长度和下标志)。
string minWindow(string S, string T) {
// int cnt[26];
map<char, int> cnt;
map<char, int> t;
for(int i=0; i<T.length(); i++)
t[T[i]]++;
int n = t.size();
if(!n) return "";
queue<int> que;
int m = 0, mm = -1, mi = -1;
for(int i=0; i<S.length(); i++){
char c = S[i];
if(t.count(c)){
cnt[c]=cnt[c]+1;
if(cnt[c] == t[c]) m++;
que.push(i);
while(!que.empty()){
int j = que.front();
char d = S[j];
if(cnt[d] > t[d]) {
cnt[d] --;
que.pop();
}
else break;
}
if(m==n){
assert(!que.empty());
int j = que.front();
if(mm<0 || mm>i-j+1){
mi = j, mm = i-j+1;
}
}
}
}
if(mm<0) return "";
return S.substr(mi, mm);
}
2 Word Break 1
DP:
res[i] = E(res[0<=j<i] && isW[j][i-1] )
class Solution {
public:
bool wordBreak(string s, unordered_set<string> &dict) {
int n = s.length();
if(!n) return dict.empty();
vector<vector<bool> > w(n, vector<bool>(n, false));
for(int i =0; i<n; i++){
string t = "";
for(int j = i; j<n; j++){
t+= s[j];
w[i][j] = dict.count(t);
}
}
vector<bool> t(n+1, false);
t[0] = true;
for(int i =1; i<=n; i++){
for(int j =0; j<i; j++){
if(t[j] && w[j][i-1]) {
t[i] = true;
break;
}
}
}
return t[n];
}
};
3 Word Break II
cut - tree............................
这道题关键是要剪枝,剪枝方法为
- res[i]保存i~n-1区间是否有效(存在被正确地划分的方案),
- 每次dp到i-1的时候,首先判断i~n-1是否有效,如果无效则剪枝。
class Solution {
public:
vector<string> wordBreak(string s, unordered_set<string> &dict) {
int n = s.length();
if(!n) return vector<string>();
vector<vector<bool> > isW(n, vector<bool>(n, false));
for(int i = 0; i<n; i++){
string tmp = "";
for(int j = i; j<n; j++){
tmp+= s[j];
if(dict.count(tmp)) isW[i][j] = true;
}
}
vector<bool> res(n+1, false);
res[n] = true;
for(int i = n-1; i>=0; i--){
for(int j = i+1; j<=n; j++){
if(res[j] && isW[i][j-1]){
res[i] = true;
break;
}
}
}
vector<vector<string> > t(n+1);
t[0].push_back("");
for(int i =1; i<=n; i++){
if(!res[i]) continue; //cut ...
for(int j = 0; j<i; j++){//0~j-1, j~i-1
if(t[j].size() && isW[j][i-1]){
for(int k = 0; k<t[j].size(); k++){
string tmp = t[j][k];
if(j>0) //[j, i-1] is not the first word
tmp+=" "+s.substr(j, i-j);
else // [j, i-1] is the first word
tmp+=s.substr(j, i-j);
t[i].push_back(tmp);
}
}
}
}
return t[n];
}
};
或者
class Solution {
public:
vector<string> wordBreak(string s, unordered_set<string> &dict) {
int n = s.length();
if(!n) return vector<string>();
vector<vector<bool> > isW(n, vector<bool>(n, false));
for(int i = 0; i<n; i++){
string tmp = "";
for(int j = i; j<n; j++){
tmp+= s[j];
if(dict.count(tmp)) isW[i][j] = true;
}
}
vector<bool> res(n+1, false);
res[n] = true;
for(int i = n-1; i>=0; i--){
for(int j = i+1; j<=n; j++){
if(res[j] && isW[i][j-1]){
res[i] = true;
break;
}
}
}
vector<vector<vector<int> > > t(n+1);
t[0].push_back(vector<int>(1, 0));
for(int i =1; i<=n; i++){
if(!res[i]) continue; //cut ...
for(int j = 0; j<i; j++){//0~j-1, j~i-1
if(t[j].size() && isW[j][i-1]){
for(int k = 0; k<t[j].size(); k++){
vector<int> tmp = t[j][k];
tmp.push_back(i-j);
t[i].push_back(tmp);
}
}
}
}
vector<string> ss;
for(int i = 0; i<t[n].size(); i++){
string tmp="";
int l = 0, k = 0;
for(int j=0; j<t[n][i].size(); j++){
k = t[n][i][j];
if(!k) continue;
tmp+=s.substr(l, k);
if(j<t[n][i].size()-1) tmp+=" ";
l+= k;
}
ss.push_back(tmp);
}
return ss;
}
};
3 Word Ladder II
这道题要求找最短的s-》t的变换,如果把每个字符串看成一个节点,就相当于找s-》t节点的最短路径。
一般做法是采用“最短路径约束”
最短路径约束
- 首先求出图中到所有点的 单源(s)最短路径d[node]
- 然后再dfs搜索路径,搜索过程中只遍历满足d[j] == d[i]+1的边<i, j> ,得到的s->t的路径就是最短的 (最短路径约束)
对图中所有点做单源最短路径,很容易想到对源点s做bfs,然后记录每个顶点到s的距离d[i]。
那么在做单源最短路径之前,是不是需要建立起图来?
如果先建图,那么最容易想到任取两点,查看二者是否可达。
错误做法 n*n+bfs + graph simplify + dfs
这个题先试了一下用图表示每个字符串,建立起5000规模的图。遗憾的是
超时了。
代码如下:
class Solution {
public:
inline bool isNb(string &a, string &b){//判断相邻,时间代价O(n^2)太长了~~
int cnt = 0;
for(int i = 0; i<a.length(); i++){
if(a[i]!=b[i]) cnt++;
if(cnt>1) return false;
}
return cnt == 1;
}
vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
map<string, int> num; vector<string> strs;
strs.push_back(start), num[start] = strs.size() - 1;
typedef unordered_set<string>::iterator IT;
for(IT it = dict.begin(); it!=dict.end(); it++){
if(!num.count(*it)){
strs.push_back(*it), num[*it] = strs.size() - 1;
}
}
if(!num.count(end)){
strs.push_back(end), num[end] = strs.size() - 1;
}
int n = strs.size();
vector<vector<int> > graph(n);
int s = 0, t = num[end];
for(int i = 0; i<strs.size(); i++){
for(int j = i; j<strs.size(); j++){
string &a = strs[i], &b = strs[j];
if(isNb(a, b)) graph[i].push_back(j), graph[j].push_back(i);
}
}
// BFS to find d[i]
vector<int> d(n, -1);
queue<int> que;
d[0] = 1, que.push(0);
while(!que.empty()){
int i = que.front(); que.pop();
if(i == t) break;
for(int j =0; j<graph[i].size(); j++){
int k = graph[i][j];
if(d[k]<0) {
que.push(k);
d[k] = d[i]+1;
}
}
}
// simplify the graph using d[i]
vector<vector<string> > res;
vector<string> tmp;
for(int i =0; i<n; i++){
vector<int> tmp;
for(int j =0; j<graph[i].size(); j++){
int k = graph[i][j];
if(d[k] == d[i]+1) tmp.push_back(k);
}
graph[i] = tmp;
}
tmp.push_back(start);
dfs(strs, d, graph, res, tmp, 0, t);
return res;
}
void dfs(const vector<string> &strs, const vector<int> &d, const vector<vector<int> > &graph, vector<vector<string> > &res, vector<string> &cur, int idx, int t){
if(cur.size() == d[t]){
if(idx == t){
res.push_back(cur);
}
return;
}
for(int i =0; i<graph[idx].size(); i++){
int j = graph[idx][i];
cur.push_back(strs[j]);
dfs(strs, d, graph, res, cur, j, t);
cur.pop_back();
}
}
};
上述代码会超时,原因是建图调用isNb(str, str) 了N^2次。而且graph[i][j]中包括太多实际上不会在单源(s)最短路径中出现的点。
后来考虑变换str, 查找邻居节点。这样建图是O(n)的
但是建完图,bfs的时候还要记录d[i],然后得到的图里有些边<i, j>是不满足d[j] = d[i] +1的,如果留下,会对后续dfs搜索路径造成复杂度。所以还需要bfs的时候化简图。而这个思路也超时。
正确的做法
- 先不建图,bfs的时候再搜索每个节点i的邻居j,判断<i,j>是否满足最短路径约束,满足则把<i, j>加入到图中。
- 然后对这个最简的图做dfs
vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
map<string, int> num; vector<string> strs;
strs.push_back(start), num[start] = strs.size() - 1;
typedef unordered_set<string>::iterator IT;
for(IT it = dict.begin(); it!=dict.end(); it++){
if(!num.count(*it)){
strs.push_back(*it), num[*it] = strs.size() - 1;
}
}
if(!num.count(end)){
strs.push_back(end), num[end] = strs.size() - 1;
}
int n = strs.size();
int s = num[start], t = num[end];
vector<vector<int> > graph(n);
vector<int> d(n, -1);
queue<int> que;
d[s] = 1, que.push(s);
while(!que.empty()){
int i = que.front();
que.pop(); //@error 3: forget to pop after peek ...........
if(d[i] == d[t]) break; //@error 2: we should break when the t's layer reached, not when t reached
string &ss = strs[i];
for(int j = 0; j<ss.length(); j++){
char c = ss[j];
for(char k = 'a'; k <= 'z'; k++){
if(k == c) continue;
ss[j] = k; //@error 4: forget this...............
if(num.count(ss)){
int l = num[ss];
if(d[l] < 0) {
d[l] = d[i] + 1;
//graph[i].push_back(l); //@errror 1:here, not only when d[l]<0 l is i's child, but also when d[l] == d[i]+1
que.push(l);
//if(l == t) goto END_BFS; @error 2:
}
if(d[l] == d[i]+1)
graph[i].push_back(l);//@error 1:
}
}
ss[j] = c;
}
}
vector<vector<string> > res;
vector<int> tmp;
tmp.push_back(s);
dfs(strs, graph, d[t], t, res, tmp);
return res;
}
void dfs(const vector<string> &strs, const vector<vector<int> > &graph, const int md, const int t,
vector<vector<string> > &res, vector<int> &cur){
int idx = cur[cur.size()-1];
if(cur.size() == md){
if(idx == t){
vector<string> tmp;
for(int i = 0; i < cur.size(); i++){
tmp.push_back(strs[cur[i]]);
}
res.push_back(tmp);
}
return;
}
for(int i =0; i<graph[idx].size(); i++){
int j = graph[idx][i];
cur.push_back(j);
dfs(strs, graph, md, t, res, cur);
cur.pop_back();
}
}
};
短短一个bfs,掉进无数坑。 最近记忆力衰退,什么都忘记。。。。。
4 valid number
最好写一个子函数,用来读取和验证一个number_pattern( 各种数字和正负号(只读取一次));并传一个参数告诉他是否可以有点符号。
number_pattern = [+,-][0~9]*.[0~9]*
number = number_pattern [e number_pattern]
class Solution {
public:
int getNumber(const char *s, bool hasDot){ //读取number_pattern = [+,-][0~9]*.[0~9]*,并要求至少出现一个数字([0~9]).对于无效输入返回-1
int i = 0;
if(s[i] == '+' || s[i] == '-') i++;
int j = i;
while(s[i]<='9'&&s[i]>='0') i++;
int k1 = i - j;
if(s[i]=='.') {
if(!hasDot) { return -1;}
i++;
int j = i;
while(s[i]<='9'&&s[i]>='0') i++;
int k2 = i - j;
if(!k1 && !k2) { return -1;}
}
else if(!k1) { return -1;} //没有读取到数字(中间卡在哪个无效字符上了)
return i;
}
bool isNumber(const char *s) {// number = number_pattern [e number_pattern]
int i =0;
while(s[i]==' ') i++;
bool t ;
int k1 = getNumber(s+i, true);
if(k1<0) return false;//无效number_pattern
i += k1;
if(s[i]=='e') //读取到e
{
i++;
int k2 = getNumber(s+i, false);//e右边的number_pattern不许有dot
if(k2<0) return false; //无效number_pattern
i += k2;
if(!k1 || !k2) return false;
}
while(s[i]==' ') i++;
return s[i] == 0;
}
};
5 Substring with all words
实际上也是一个queue<int>保存匹配到的单词,
- 当匹配到新的单词t之后,cnt[t]++,如果个数cnt[t]超了,则需要从que弹出一段,直到弹到第一个该单词t。
- 如果队列大小为n=L.size(),说明找到一个匹配
- 当未匹配到时,que清空,cnt[]置为0.
str unique + KMP strs matches + queue , cnt
class Solution {
public:
void kmp(string &s, string &word, vector<int> &res){
vector<int> nxt(word.length()+2);
nxt[0] = -1, nxt[1] = 0;
for(int i=1; word[i]; i++){
int t = nxt[i];
while(t>=0 && word[t] != word[i])
t = nxt[t];
nxt[i+1] = t+1;
}
for(int i =0, j = 0; i<s.length(); i++){
while(j>=0 && s[i]!=word[j]) j = nxt[j];
j++;
if(j==word.length()) res.push_back(i-j+1);
}
}
vector<int> findSubstring(string S, vector<string> &L) {
vector<int> res;
if(L.size()==0) return res;
// unique L to ul and wc
sort(L.begin(), L.end());
vector<string> ul; //unique L
vector<int> wc;
int ln = 1;
for(int i=1; i<=L.size(); i++){
if(i== L.size() || L[i]!=L[i-1]){
ul.push_back(L[i-1]);
wc.push_back(ln);
ln = 1;
}else ln ++;
}
vector<int> matches(S.length(), -1);
for(int i=0; i<ul.size(); i++){
string & word = ul[i];
vector<int> res;
kmp(S, word, res);
for(int j =0; j<res.size(); j++){
matches[res[j]] = i;
}
}
int n = L.size(), l = L[0].length();
queue<int> que;
vector<int> cnt(n, 0);
for(int i =0; i<l; i++){
que = queue<int>(), cnt = vector<int>(n, 0);
for(int j = i; j<S.length(); j+=l){
if(matches[j]>=0){
int t = matches[j];
que.push(t);
cnt[t]++;
if(cnt[t]>wc[t]){
//while(que.front()!=t) que.pop(); //@error 1: should update cnt when que changes
//que.pop();
while(!que.empty()){
int s = que.front(); que.pop();
cnt[s] --;
if(s==t) break;
}
}
if(que.size()==n) res.push_back(j-(n-1)*l); //@error 3: j-n*l+1
}
//@error 2: lost: when some word no match, then clear que
else que = queue<int>(), cnt = vector<int>(n, 0);
}
}
return res;
}
};
或者不用kmp, 用hash查找
Hash+ que/cnt
class Solution {
public:
vector<int> findSubstring(string S, vector<string> &L) {
vector<int> res;
if(L.size()==0) return res;
int N=L[0].length();
#define rep(i,n) for(int i=0;i<n;i++)
map <string,int > words;
map <int,int > cnt;
rep(i,L.size()) {
if(words.find(L[i])==words.end()) words[L[i]]=i,cnt[i]=1;
else {
int j=words[L[i]];
cnt[j]++;
}
}
vector<int> tag(S.length(),-1);
rep(i,S.length()){
string tmp=S.substr(i,N);
if(words.find(tmp)!=words.end()){
tag[i]=words[tmp];
}
}
rep(i,N){
int j=i;
deque<int> cur;
int begin=j;//begin go ahead n step when cur[] pop n elements
while(j<S.length()){
//a new word to append
if(tag[j]>=0){
deque<int>::iterator it;
if(count(cur.begin(),cur.end(),tag[j])==cnt[tag[j]]){//overflow of word j
it=find(cur.begin(),cur.end(),tag[j]);
begin+=(it-cur.begin()+1)*N;
cur.erase(cur.begin(),it+1);
}
cur.push_back(tag[j]);
//if a new concatness is found after push_back
if(cur.size()==L.size()) {
res.push_back(begin);
begin+=N;
cur.pop_front();
}
}
//a nonword: divide the string
else{//-1
begin=j+N;
cur.clear();
}
j+=N;
}
}
return res;
}
};