前言
本博客为个人复习时总结用,无商业目的,其大多数内容皆为博主整理所得,并非原创。侵删。
字符串:
KMP算法:字符串匹配。
维护一个数组next,使得每次匹配失败后,并不将j指针移到头部,而是进行next[j]的操作。
next数组计算:
public static int[] getNext(String ps) {
char[] p = ps.toCharArray();
int[] next = new int[p.length];
next[0] = -1;
int j = 0;
int k = -1;
while (j < p.length - 1) {
// 证明 1
if (k == -1 || p[j] == p[k]) {
next[++j] = ++k;
} else {
k = next[k];
}
}
return next;
}
关键思想:
证明 1
当P[k] == P[j]时,
有next[j+1] == next[j] + 1
其实这个是可以证明的:
因为在P[j]之前已经有P[0 ~ k-1] == p[j-k ~ j-1]。(next[j] == k)
这时候现有P[k] == P[j],我们是不是可以得到P[0 ~ k-1] + P[k] == p[j-k ~ j-1] + P[j]。
即:P[0 ~ k] == P[j-k ~ j],即next[j+1] == k + 1 == next[j] + 1。
证明2:如果不相等,则
如图:当x与p相同时,next(x+1)=next(x)+1,已证明,
当x与k不等时,将跳到next[k]位置,原因是,next[k]代表 x往前k个 与 0开始k个相匹配。因为,红色与红色相同,所以黄色与黄色相同,也就是说 1 号黄色与 4 号黄色 相同,此时应比较next[k]与x,如果相同,则同上,否则继续迭代至1.
总代码:https://www.cnblogs.com/yjiyjige/p/3263858.html
Manacher最长回文子串
核心思想:Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1;
维护一个最大长度,mx和id,代表以id为中心的最长回文字符串的长度半径为mx。
遍历i ,记录以i为中心的回文数半径长度len[i]。更新mx与id。
当i在id+mx右边时,也就是i在其回文数外部时,len[i] = 1 ,并且要进行while的一个两遍扩展。
当i在id + mx 左边时,分情况,如果其关于id对称位置,也就是2id-i位置处的长度与mx比较。
如果未越界,也就是其回文字符串在[id-mx,id+mx]处时,由对称可知,长度不变。
否则,将其长度设为len[2id-i],并进行遍历。如下图:
总代码:
void Manacher(char s[],int len)
{
int l=0;
Ma[l++]='$';
Ma[l++]='#';
for(int i=0;i<len;i++)
{
Ma[l++]=s[i];
Ma[l++]='#';
}
Ma[l]=0;
int mx=0,id=0;
for(int i=0;i<l;i++)
{
Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1;
//这里做个注释,邝斌本人的代码有点精简。这里假设长度没有超过[id-mx,id+mx],也会进行一次判断,进行一次就会发现不匹配,所以没有复杂度。
while(Ma[i+Mp[i]]==Ma[i-Mp[i]])Mp[i]++;
if(i+Mp[i]>mx)
{
mx=i+Mp[i];
id=i;
}
}
}
动规
硬币找零
使用dp数组,将每一个小于money的值计算出来(使用最小硬币数),放入dp数组中,然户计算减去各种面值的钱的数量加一,取最小。
字符串相似度/编辑距离
维护一个二维dp数组,其中dp[i][j]代表对比到source[i]与target[j]转换需要的操作。
然后计算到dp[i-1][j-1]到dp[i][j]的最小操作数
关键代码:
for(int i=0;i<=n;i++)
{
ed[i][0]=i;
}
for(int i=0;i<=m;i++)
{
ed[0][i]=i;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int cost;
if(source[i-1]==target[j-1])
cost=0;
else
cost=1;
ed[i][j]=min( ed[i-1][j-1]+cost,min( ed[i-1][j]+1 , ed[i][j-1]+1 ) );
}
}
最长公共子序列(Longest Common Subsequence,lcs)
维护一个二维dp,dp[i][j]代表source[i]与target[j]的最长公共子序列长度,如果i=j;则
dp[i][j] = dp[i-1][j-1]+1;
扩展:
打印路径;
可以维护一个b数组,进行路径记录。
最长递增子序列(LIS)
在求以ai为末元素的最长递增子序列时,找到所有序号在L前面且小于ai的元素aj,即j<i且aj<ai。如果这样的元素存在,那么对所有aj,都有一个以aj为末元素的最长递增子序列的长度f(j),把其中最大的f(j)选出来,那么f(i)就等于最大的f(j)加上1,即以ai为末元素的最长递增子序列,等于以使f(j)最大的那个aj为末元素的递增子序列最末再加上ai;如果这样的元素不存在,那么ai自身构成一个长度为1的以ai为末元素的递增子序列。
维护一个一维dp,直接上代码,很简单:
for(int j,i=0;i<n;++i){
int maxn=0;
for(j=0;j<i;++j) //这里利用了递推的原理、
if(num[i]>num[j]) //由前面的最长递增子序列推出后面的最长递增子序列、
maxn=maxn>dp[j]?maxn:dp[j];
dp[i]=maxn+1;
if(dp[i]>x) x=dp[i]; //x记录的是当前的最大值、
}
最大连续子序列和
维护两个数 max 与 当前值,进行一个O(n)的复杂度计算。
01背包
维护一个二维dp[i][j]代表j重量下,0-i号物品最大价值。
if(j<w[i])//包装不进
{
V[i][j]=V[i-1][j];
}
else//能装
{
if(V[i-1][j]>V[i-1][j-w[i]]+v[i])//不装价值大
{
V[i][j]=V[i-1][j];
}
else//前i-1个物品的最优解与第i个物品的价值之和更大
{
V[i][j]=V[i-1][j-w[i]]+v[i];
}
}
位运算与二进制:
二进制数性质:可组成任何数 1 10 100 1000
n&n-1 二进制数中1的个数。