前言
总结花时间蛮长的,比做题慢多了。~
10. Regular Expression Matching
算是比较难的一道DP题,会有几个地方比较难想:
- 状态比较难确定, F [ i , j ] F[i,j] F[i,j]该怎么表示会有疑惑
- 状态转移方程难确定,很容易写出错的转移方程
- 初始状态难确定,初始状态又是一个子DP题。
- F[0][0]=true
- F[0…m,0] //的状态如下:
for i=1 to m
F[i,0]=false; - F[0,0…n-1,n]//的状态如下:
//重写按照一个新的dp小问题求解:
for i=1 to n
F[0,i]=F[0,i-1] if p[i-1] == ‘’ and F[0,i-1]
=F[0,i-2] if i-2 >=0 and p[i-1] == '’ and F[0,i-2]
一开始我是为了不麻烦自己,设$F[i,j]$代表当前s中以i位置结尾和p中以j位置结尾的子串匹配的情况,结果到更新初始状态时候遇到了很大麻烦,在类似`"d","a*b*c*d"`的`case`中很难处理初始状态。
改变定义为$F[i,j]$代表s前i和p前j个子符子串的匹配情况,**初始状态的更新可以当成一个新的dp问题解决。**最后再看看前面标注的信息:
```c
F[i,j]=F[i,j-2] if p[j-1] == '*' //很容易多加 && s[i-1] != p[j-2] 而出错
这个状态代表p中x*
直接跳过,**而这种直接跳过的状态只依赖p[j-1] == '*'
是否成立,**这里极易出错,想不清楚。然后就是代码实现的一些技巧,省略了if
判断语句简写了很多逻辑代码,这种只有在状态为布尔值时,才能实现的编程简化。
class Solution {
public:
bool isMatch(string s, string p){
vector<vector<int>> F(s.size()+1);
for(int i=0;i<=s.size();++i)
F[i].resize(p.size()+1,0);
F[0][0]=true;
for(int j=1;j<=p.size();++j)
F[0][j]=F[0][j-1] && p[j-1] == '*' || j-2>=0 && F[0][j-2] && p[j-1] == '*';
for(int i=1;i<=s.size();++i){
for(int j=1;j<=p.size();++j){
if(p[j-1] != '*')
F[i][j]=F[i-1][j-1] && (s[i-1] == p[j-1] || p[j-1] == '.');
else
//&& s[i-1] != p[j-2]
F[i][j]=j-2>=0 && F[i][j-2]&& s[i-1] != p[j-2] || F[i-1][j] && (s[i-1] == p[j-2] ||p[j-2] == '.');
}
}
return F[s.size()][p.size()];
}
};
14. Longest Common Prefix
这道题看题解之后,觉得有几个比较好的地方值得总结。首先抽象问题为
F
(
S
)
=
F
(
.
.
(
F
(
S
1
,
S
2
)
,
S
3
)
.
.
.
,
S
n
−
1
)
F(S)=F(..(F(S1,S2),S3)...,Sn-1)
F(S)=F(..(F(S1,S2),S3)...,Sn−1),这对应了横向扫描的办法,
L
C
P
(
S
1
…
S
n
)
=
L
C
P
(
L
C
P
(
S
1
…
S
k
)
,
L
C
P
(
S
k
+
1
…
S
n
)
)
LCP(S1…Sn)=LCP(LCP(S1…Sk),LCP(Sk+1…Sn))
LCP(S1…Sn)=LCP(LCP(S1…Sk),LCP(Sk+1…Sn))对应了分治的方法,最后还有的垂直的解法也是我所采用的。但是最后的扩展提高中提到了**前缀树**的解法,原文使用的java
实现的前缀树,我改写的C++版本如下所示:
struct Node {
bool containsKey(char ch) {
return ptr[ch -'a'] != NULL;
}
Node * get(char ch) {
return ptr[ch -'a'];
}
void put(char ch, Node * node) {
ptr[ch -'a'] = node;
}
void setEnd() {
isend = true;
}
Node(int x=false,int y=26) : isend(x), R(y){ ptr.resize(R,NULL);}
int R;
vector<Node * > ptr;
bool isend;
};
class Solution {
public:
void insert(string word,Node * root) {
Node * node = root;
for (int i = 0; i < word.length(); ++i) {
char c = word[i];
if (!node->containsKey(c-'a')) {
node->put(c-'a',new Node());
}
node = node->get(c-'a');
}
//下一个节点的isend为true
node->setEnd();
}
Node * searchPrefix(string word,Node * root) {
Node * node = root;
for (int i = 0; i < word.length(); ++i) {
char c = word[i];
if (!node->containsKey(c-'a')) {
return NULL;
}
else
node = node->get(c-'a');
}
return node;
}
bool search(string word,Node * root) {
Node * node = searchPrefix(word,root);
return node != NULL && node->isend;
}
bool startsWith(string prefix,Node * root) {
Node * node = searchPrefix(prefix,root);
return node != NULL;
}
}
至于前缀树的几个题,到以后的阶段三再处理,现在不拓展太多。
19. Remove Nth Node From End of List
这个题一般都能想到two pass
的解法,我一直在想one pass
的解法到底怎么做,最后题解提示:寻找从后面数第k个元素,可以用双指针的方法,两个指针一直保持k的距离,则后面一个指针到达末尾的时候,前一个指针就是第k个元素。但是为了删除第k个元素,所以需要找到第k+1个元素。我实现的c++版本如下:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode * first=NULL,* second=NULL;
int i;
for(i=0,first = head;i<n-1;++i)
first=first->next;
if(first->next== NULL)
return head->next;
for(second=head;first->next->next != NULL; first=first->next,second=second->next);
second->next=second->next->next;
return head;
}
};
小结
10-20还有其他题没有总结,所以还没有结束。本篇总结了一个较复杂的dp问题,抽象问题的方式及前缀树的概念,最后学习到了one pass
删除一个链表第k项(从后数)的方法。