牛客NC68跳台阶(斐波那契数列非递归)
分析过程:某一个状态可能是怎么得来的。(NC34求路径问题也一样)
比如说跳上第n级台阶(n>2)可能是通过跳2级台阶来的,也可能是跳1级台阶来的。那么跳上第n级台阶的方法数就是“最后一步跳2级”和“最后一步跳1级”的方法数之和,即F(n)=F(n-1)+F(n-2)。
使用递归方法的时间复杂度高。
非递归方法,用了斐波那契数列的规律。要跳几级台阶就是斐波那契数列1,2,3,5,8…中的第几个数。
即:
int jumpFloor(int number) {
//1,2,3,5,8,13
int a=1,b=1;
for(int i=1;i<number;i++){
int temp=a;
a = a+b;
b=temp;
}
return a;
}
动态规划
动态规划:一般以二维数组来作为记忆,以免重复计算一些子问题,比较难找的是给二维数组的每个元素赋予什么意义(也就是表示什么),然后找到初始的一些状态,再开始对二维数组赋值。
分析的关键是:
1.确定二维数组的每个元素的意义
2.确定初始状态(并赋值)
3.每一个元素是怎么来的(和之前的状态的关系),即分解成子问题
牛客NC34求路径
题目:一个机器人在m×n大小的地图的左上角(起点)。机器人每次向下或向右移动。机器人要到达地图的右下角(终点)。可以有多少种不同的路径从起点走到终点?
构建二维数组dp,dp[i][j]的意义是走到(i,j)这一格有多少种方案。
已知的是,走到第一行或是第一列的格子、都只有一种走法。所以dp的第一行、第一列赋值为零(初始状态)。由于只能向下或向右移动,则到第(i,j)格的上一状态可能是(i-1,j)或是(i,j-1)。故dp[i][i]=dp[i-1][j]+dp[i][j-1]。
int uniquePaths(int m, int n) {
// write code here
int dp[100][100];
//第一列
for(int i=0;i<m;i++){
dp[i][0]=1;
}
//第一行
for(int i=0;i<n;i++){
dp[0][i]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
LeetCode 64 类似,只是求的是最短路径。把dp[i][j]定义成表示到该格的最短路径长度即可。
初值为第一行、第一列。
其关系式为 dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j]
LeetCode 1143 最长公共子序列
题目:给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
构建二维数组dp,
每个元素的意义:dp[i][j]表示到text1的第i个字符和text2的第j个字符为止,最长的公共子序列的长度。
和之前的状态的关系:
开始比较之前,最长公共子序列长度为0,故dp的第一行和第一列都可赋值为零(初始状态)。
当text1的第i个字符和text2的第j个字符相等时,dp[i][j]=dp[i-1][j-1]+1,dp[i-1][j-1]=dp[i-1][j]=dp[i][j-1]。
不相等时,dp[i][j]=max(dp[i-1][j], dp[i][j-1])。
要注意:1.数组的边界 2.取字符串中的字符可以直接用str[i]
int longestCommonSubsequence(string text1, string text2) {
int len1=text1.size();
int len2=text2.size();
int dp[len1+1][len2+1];
//dp[i][j]表示到text1的第i个字符和text2的第j个字符,一共有多少个公共字符
for(int i=0;i<=len1;i++){
dp[i][0]=0;
}
for(int j=0;j<=len2;j++){
dp[0][j]=0;
}
int i,j;
for(i=1;i<=len1;i++){
for(j=1;j<=len2;j++){
if(text1[i-1]==text2[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[i-1][j-1];
}
LeetCode 300 最长递增子序列
牛客NC91 最长递增子序列
LeetCode 5 最长回文子串
string longestPalindrome(string s) {
int len=s.size();
int dp[len][len];
string ans;
//dp[i][j],i<=j
//dp[i][i]=1
//dp[i][i+1]=(s[i]==s[i+1])
//dp[i][j]=dp[i+1][j-1] && s[i]==s[j]
for(int i=0;i<len;i++){
dp[i][i]=true;
}
for(int i=0;i<len-1;i++){
dp[i][i+1]=(s[i]==s[i+1]);
}
//怎么样斜着遍历..
//k是i与j的差值
for(int k=0;k<len;k++){
for(int i=0;i+k<len;i++){
int j=i+k;
if(k==0) dp[i][j]=1;
else if(k==1) dp[i][j]=(s[i]==s[i+1]);
else dp[i][j] = dp[i+1][j-1] && s[i]==s[j];
if(dp[i][j] && (k+1)>ans.size()){
ans=s.substr(i,k+1);
}
}
}
return ans;
}
LeetCode 198
动态规划 空间优化
LeetCode 413
空间优化到常数
动态规划解法,最开始使用dp[i][j]来记录从第i个数字到第j个数字是否是等差数列。结果stackoverflow。
由于双层for循环中,内层循环时dp[i][j]与其他i无关,故可以使用一维数组来记录,用dp[j]来代替dp[i][j]。
再继续优化可以发现,dp[i][j]的状态,其实只与dp[i][j-1]有关,所以使用一个变量flag即可。
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& nums) {
if(nums.size()<3) return 0;
//dp[i][j]记录是不是等差
int n=nums.size();
int flag;//代替dp数组
int dis,i=0,count=0;
for(int i=0;i<n-1;i++){
for(int j=i+1;j<n;j++){
if((j-i)==1){
dis=nums[j]-nums[i];
flag=1;
}else{
flag=flag && (nums[j]-nums[j-1]==dis);
if(flag==0) break;
else count++;
}
}
}
return count;
}
};
LeetCode 718
最重要的是确定dp[i][j]的意义,确定递推式。
int findLength(vector<int>& nums1, vector<int>& nums2) {
int len1=nums1.size();
int len2=nums2.size();
//dp[i][j]表示以nums1[i]与nums2[j]为结尾,有多少个连续相同的数
//初始化,dp[0][j]=0,dp[i][0]=0
int ans=0;
int dp[len1+1][len2+1];
for(int i=0;i<=len1;i++){
dp[i][0]=0;
}
for(int j=0;j<=len2;j++){
dp[0][j]=0;
}
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(nums1[i-1]==nums2[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=0;
}
ans=max(ans,dp[i][j]);
}
}
return ans;
}
dp[i][j]只和上一行的dp[i-1][j-1]有关,可以进行空间优化。空间优化成一维数组dp[i]来进行记录,最开始全初始化为0,每一轮循环时更新dp[j],如果nums1[i-1]==nums2[j-1]为真,dp[j]=dp[j-1]+1。
int findLength(vector<int>& nums1, vector<int>& nums2) {
int len1=nums1.size();
int len2=nums2.size();
//dp[i][j]表示以nums1[i]与nums2[j]为结尾,有多少个连续相同的数
//初始化,dp[0][j]=0,dp[i][0]=0
//dp[i][j]只和上一行的dp[i-1][j-1]有关,空间优化
int ans=0;
int dp[len2+1];
for(int j=0;j<=len2;j++){
dp[j]=0;
}
for(int i=1;i<=len1;i++){
for(int j=len2;j>=1;j--){
if(nums1[i-1]==nums2[j-1]){
dp[j]=dp[j-1]+1;
}else{
dp[j]=0;
}
ans=max(ans,dp[j]);
}
}
return ans;
}
368 最大整除子集
注意:最后要求的结果不是长度,而是具体的方案。这里使用了一个额外的数组来记录状态的转移轨迹。(可以参考算法导论DP第一个例子,切杆,补充了要输出具体方案的时候应该怎么处理)