本文共 3666 字,大约阅读时间需要 12 分钟。
1、最长公共子序列、最长公共子串
最长公共子序列(Longest-Common-Subsequence,LCS)
dp[i][j]:dp[i][j]表示长度分别为i和j的序列X和序列Y构成的LCS的长度
dp[i][j] = 0,如果i=0 或 j=0 dp[i][j] = dp[i-1][j-1] + 1,如果 X[i-1] = Y[i-1] dp[i][j] = max{ dp[i-1][j], dp[i][j-1] },如果 X[i-1] != Y[i-1] LCS长度为 dp[Xlen][Ylen]最长公共子串(Longest-Common-Substring,LCS)
dp[i][j]:表示X[0-i]与Y[0-j]的最长公共子串长度
dp[i][j] = dp[i-1][j-1] + 1,如果 X[i] == Y[j] dp[i][j] = 0,如果 X[i] != Y[j] 初始化:i==0或者j==0,如果X[i] == Y[j],dp[i][j] = 1;否则dp[i][j] = 0。最长公共子串的长度为max(dp[i][j])。
2、数组中最长递增子序列:如在序列1,-1,2,-3,4,-5,6,-7中,最长递增序列为1,2,4,6。
时间复杂度O(N^2)的算法:
LIS[i]:表示数组前i个元素中(包括第i个),最长递增子序列的长度 LIS[i] = max{ 1, LIS[k]+1 }, 0 <= k < i, a[i]>a[k]时间复杂度O(NlogN)的算法:
辅助数组b[],用k表示数组b[]目前的长度,算法完成后k的值即为LIS的长度。 初始化:b[0] = a[0],k = 1 从前到后扫描数组a[],对于当前的数a[i],比较a[i]和b[k-1]: 如果a[i]>b[k-1],即a[i]大于b[]最后一个元素,b[]的长度增加1,b[k++]=a[i]; 如果a[i]<b[k-1],在b[1]...b[k]中二分查找第一个大于a[i]的数b[j],修改b[j]=a[i]。 LIS的长度为k3、计算字符串的相似度(编辑距离)
为了判断字符串的相似程度,定义了一套操作方法来把两个不相同的字符串变得相同,具体的操作方法为: 1.修改一个字符。2.增加一个字符。3.删除一个字符。
比如,对于“abcdefg”和“abcdef”两个字符串来说,可以通过增加/减少一个“g“的方式来达到目的。上面的两种方案,都仅需要一次操作。把这个操作所需要的次数定义为两个字符串的距离,给定任意两个字符串,写出一个算法来计算出它们的距离。
设 L(i,j)为使两个字符串和Ai和Bj相等的最小操作次数。
当ai==bj时 显然 L(i,j) = L(i-1,j-1) 当ai!=bj时 L(i,j) = min( L(i-1,j-1), L(i-1,j), L(i,j-1) ) + 14、8*8的棋盘上面放着64个不同价值的礼物,每个小的棋盘上面放置一个礼物(礼物的价值大于0),一个人初始位置在棋盘的左上角,每次他只能向下或向右移动一步,并拿走对应棋盘上的礼物,结束位置在棋盘的右下角,请设计一个算法使其能够获得最大价值的礼物。
动态规划算法:
dp[i][j] 表示到棋盘位置(i,j)上可以得到的最大礼物值 dp[i][j] = max( dp[i][j-1] , dp[i-1][j] ) + value[i][j] (0<i,j<n)5、给定一个整数数组,求这个数组中子序列和最大的最短子序列,如数组a[]={1,2,2,-3,-5,5}子序列和最大为5,最短的为a[5]。
动态规划
sum[i] = max(sum[i-1]+a[i], a[i]) (sum[0]=a[0],1<=i<=n) len[i] = max(len[i-1]+1, 0) (len[0]=0,1<=i<=n)6、子数组的最大和
状态方程:
Start[i] = max{A[i], Start[i-1]+A[i]} All[i] = max{Start[i], All[i-1]}因为Start[i-1]只在计算Start[i]时使用,而且All[i-1]也只在计算All[i]时使用,所以可以只用两个变量就够了,节省空间。
7、在数组中,数字减去它右边的数字得到一个数对之差。求所有数对之差的最大值。例如在数组{2, 4, 1, 16, 7, 5, 11, 9}中,数对之差的最大值是11,是16减去5的结果。
思路:假设f[i]表示数组中前i+1个数的解,前i+1个数的最大值为m[i]。则状态转移方程:
f[i] = max(f[i-1], m[i-1] - a[i]), m[i] = max(m[i-1],a[i])。问题的解为f[n-1]。上述代码用了两个辅助数组,其实只需要两个变量,前i个数的情况只与前i-1个数的情况有关。在“子数组的最大和问题”中,也使用过类似的技术。
8、从一列数中筛除尽可能少的数使得从左往右看,这些数是从小到大再从大到小的。
双端 LIS 问题,用 DP 的思想可解,目标规划函数 max{ b[i] + c[i] - 1 }, 其中 b[i] 为从左到右,0--i 个数之间满足递增的数字个数;c[i] 为从右到左,n-1--i个数之间满足递增的数字个数。最后结果为 n-max 。
9、从给定的N个正数中选取若干个数之和最接近M
解法:转换成01背包问题求解,从正整数中选取若干个数放在容量为M的背包中。
从给定的N个正数中选取若干个数之和为M
10、将一个较大的钱,不超过1000的人民币,兑换成数量不限的100、50、10、5、2、1的组合,请问共有多少种组合呢?
解法:01背包中的完全背包问题(即每个物品的数量无限制)
dp[i][j]:表示大小为j的价值用最大为money[i]可表示的种类数11、捞鱼问题:20个桶,每个桶中有10条鱼,用网从每个桶中抓鱼,每次可以抓住的条数随机,每个桶只能抓一次,问一共抓到180条的排列有多少种。
分析:看看这个问题的对偶问题,抓取了180条鱼之后,20个桶中剩下了20条鱼,不同的抓取的方法就对应着这些鱼在20个桶中不同的分布,于是问题转化为将20条鱼分到20个桶中有多少中不同的分类方法(这个问题当然也等价于180条鱼分到20个桶中有多少种不同的方法)。
dp[i][j]:前i个桶放j条鱼的方法共分为11种情况:前i-1个桶放j-k(0<=k<=10)条鱼的方法总和。我们可以得到状态方程:f(i,j) = sum{ f(i-1,j-k), 0<=k<=10}
12、n个骰子的点数:把n个骰子扔在地上,所有骰子朝上一面的点数之和为S。输入n,打印出S的所有可能的出现的值。
F(k,n) 表示k个骰子点数和为n的种数,k表示骰子个数,n表示k个骰子的点数和
对于 k>0, k<=n<=6*k F(k,n) = F(k-1,n-6) + F(k-1,n-5) + F(k-1,n-4) + F(k-1,n-3) + F(k-1,n-2) + F(k-1,n-1) 对于 n<k or n>6*k F(k,n) = 0 当k=1时, F(1,1)=F(1,2)=F(1,3)=F(1,4)=F(1,5)=F(1,6)=113、给定三个字符串A,B,C;判断C能否由AB中的字符组成,同时这个组合后的字符顺序必须是A,B中原来的顺序,不能逆序;例如:A:mnl,B:xyz;如果C为mnxylz,就符合题意;如果C为mxnzly,就不符合题意,原因是z与y顺序不是B中顺序。
DP求解:定义dp[i][j]表示A中前i个字符与B中前j个字符是否能组成C中的前(i+j)个字符,如果能标记true,如果不能标记false; 有了这个定义,我们就可以找出状态转移方程了,初始状态dp[0][0] = 1:
dp[i][j] = 1 如果 dp[i-1][j] == 1 && C[i+j-1] == A[i-1] dp[i][j] = 1 如果 dp[i][j-1] == 1 && C[i+j-1] == B[j-1]