刷题一时爽,一直刷题一直爽~
整理这几天刷的有关leetcode字符串部分的题解。
5. Longest Palindromic Substring
题意:清晰明了,给定一个字符串,求最长回文字串。
解题过程:开局除了暴力想不出其他的解法(扣脚…),但下一秒突然想到了kmp算法中的next数组,总觉得很像,next数组是求最长、相等的真前缀和真后缀,这个是求最长、中心对称相等的字串,然后搜了一下,有一种算法是马拉车算法(Manacher),能奇迹般的把时间复杂度降到线性。
Manacher算法:
1、几个名词,p,C,R:
p:由于是O(n)阶的算法,所以很明显,这个是用来遍历一遍字符串的。
C:这个是回文字符的对称中心。
R:这个是回文字符的右边界。
2、字符的预处理:
回文字符存在奇回文和偶回文。奇回文还好说,是关于某一个字符对称,而偶回文则不存在对称字符,为了能统一处理方便些,采取两个两个字符之间加一个#号的操作(这个操作也是很强的…),这样不管是奇回文还是偶回文都会关于某一个字符对称,如 “aba” 处理为 “#a#b#a#”。
3、求最大回文长度数组:
和kmp算法类似,Manacher算法之所以能线性阶,也在于他有一个能缩减时间的数组。把每次遍历到的元素的最长回文长度或者是回文半径记下来,那么他为啥能快一点捏?答案就在下面啦~
4、算法的过程:
从0开始遍历字符串,根据R(即最右回文右边界)和对称中心C来计算当前位置的最长回文半径。
然后在遍历的时候会发生两种情况:
p在R的左边 || p在R的右边
【1】
p在R的左边即最长回文字符包含当前位置,这个直接暴力拓展回文串就可以了。
【2】
第二种情况又分三种情况——
由于最长回文右边界和左边界包含着当前位置,那么在求最长回文半径的时候直接对称过去,根据对称过去的p’来判断。
1)p’位置的回文半径长度在R‘里面——直接暴力拓展
2)p’位置的回文半径长度在R‘上——那么这就是当前的值,无法再往两边拓展(由于最右回文边界已经确定,如果你仍然能往两边拓展,说明最右回文半径有误)
3)p’位置的回文半径长度在R’左边——此时应该截取一部分下来,这个要画图理解一下
代码:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char s[1005],s1[2010]; //原字串以及预处理的字串
int r[2010]={-1,}; //回文半径数组
int i,R = -1,C = -1,len; //最右回文右边界及对称中心
cin.getline(s,1005);
len = strlen(s);
for (i = 0 ; i < 2*len ; i += 2) //初始化拓展序列
s1[i] = '#',s1[i+1]= s[i/2]; s1[i] = '#';
int ans = -1;
for (i = 0; i <= 2*len ; i++)
{
r[i] = R > i ? min(r[2*C-i],R-i+1) : 1 ;
while(i+r[i] <= 2*len && i - r[i] > -1)
{
if(s1[i-r[i]] != s1[i+r[i]]) break;
radius[i] ++;
}
if(i + r[i] > R)
{
R = i + r[i]-1;
C = i;
}
ans = max(ans,r[i]); //更新最大值
}
cout<<ans-1<<endl;
return 0;
}
6. ZigZag Conversion
题意:这个看样例大概能晓得。给出一串字符串,它是以z字形的顺序写出来的,我们要恢复它,一行一行的输出。
解题过程:我认真的数规律…果然还是太菜了…这样的话就要遍历newRow次,但是可以直接开一个字符数组,然后就只用便利一遍,空间换时间。
我的AC代码:
string convert(string s, int numRows) {
//要求的是一行一行输出N形的图形
string a;
int k=0;
int group = numRows*2 - 2;
if(group==0)
return s;
int t = 0;
//这个是分组,然后除以组数余一就是第一行输出的数
while(t<numRows)
{
for(int i=0; i<s.size(); i++)
{
if((i+t)%group==0||((i-t)%group==0&&(i-t)>=0))
a += s[i];
}
t++;
}
//cout << a << endl;
return a;
}
14. Longest Common Prefix
题意:给一个数组,数组中的每个元素都是字符串,求出所有字符串的最长公共前缀。
解题过程:还有比这个更简单的题嘛~直接找就好了,开一个字符串记下公共前缀,然后先把第一个字符赋值给他,然后开始比较。
我的AC代码:
string longestCommonPrefix(vector<string>& strs) {
//找最长公共前缀
string prefix;
if(strs.size() == 0) //空的容器
return "";
prefix = strs[0]; //假设最长公共前缀是第一个
for(int i=1; i<strs.size() && prefix.size()>0; i++) //以免出现某一个字符串是空的时候仍在比较
{
//首先要保证prefix的长度要比遍历到的
if(prefix.size() < strs[i].size())
prefix = prefix.substr(0, strs[i].size());
for(int j=0; j<prefix.size(); j++)
if(strs[i][j] != prefix[j])
{
prefix = prefix.substr(0, j);
break;
}
}
return prefix;
}
28. Implement strStr()
题意:给两个字符串,然后找出第二个字符串在第一个字符串第一次出现的位置
解题过程:妈耶完全的模板题,直接套kmp算法
我的AC代码:
class Solution {
public:
int next[100000];
void getnext(string needle)
{
//先对模式求next数组
int len = needle.size();
int k=-1, i;
next[0] = -1;
i = 0;
while(i<len)
{
if(k==-1 || needle[i]==needle[k])
{
i++;
k++;
next[i] = k;
}
else
k = next[k];
}
}
int strStr(string haystack, string needle) {
//kmp算法
int len2=needle.size(), len1=haystack.size();
getnext(needle);
//开始匹配
int i = 0, j = 0;
while(i < len1 && j < len2)
{
if(j==-1 || haystack[i] == needle[j])
{
++i;
++j;
}
else
j = next[j];
}
if(j == len2) //说明遍历到最后了
return i-j;
else
return -1;
}
};
借这道题也认真回顾了一下kmp算法,大师级别的算法棒棒的~
58. Length of Last Word
题意:求最后一个单词的长度。单词和单词之间都是用空格分割的,若存在则返回长度。不存在则返回-1.
解题过程:一定要记得从右往左开始扫描字符,基本操作基本操作。而且!一定要看懂题再去做,不然太浪费时间了,题目强调的是以空格分隔单词,所以其实完全不用去考虑单词内部是一个啥情况,直接去找空格就可以了。
我的wa代码:
class Solution {
public:
int lengthOfLastWord(string s) {
int k,len,i,j;
//求最后一个单词的长度,肯定是从右往左哦,别犯傻哈
len = s.size();
if(len==0)
return 0;
for(i=len; i>=0; i--)
{
if((s[i]>='A' && s[i]<='Z') || (s[i]>='a' && s[i]<='z'))
break;
}
if(i==0)
return 1;
for(j=i-1; j>=0; j--)
{
//开始遍历到不是字母的部分
if((s[j]<'A' || s[j]>'z') || (s[j]>'Z' && s[j]<'a'))
break;
}
return (i-j);
}
};
千万不要想太多了,没有这么复杂,直接与空格有关!
AC代码:
class Solution {
public:
int lengthOfLastWord(string s) {
int j = s.size() - 1;
while (j >= 0 && s[j] == ' ') --j;
int i = j;
while (i >= 0 && s[i] != ' ') --i;
return j - i;
}
};