最长上升子序列(LIS)、最长公共子序列(LCS)、最长上升公共子序列(LICS) 等一系列问题的解法 【待完善】

1. 最长上升子序列(LIS)

题意:给定一个序列,求出最长的上升(递增)子序列,注意:不一定要连续

解法:

动态规划
创建一个dp数组,dp[i]表示以i结尾的最长上升子序列的长度,开两层循环求dp数组即可,具体思路看代码理解。

代码:

int LIS(int a[],n)
{
        int dp[MAX];
        for(int i = 0; i < n; i++)
        {
            for(int j = i; j < n; j++)
            {
                if(a[j] > a[i])
                {
                    dp[j] = max(dp[i]+1,dp[j]);
                }
            }
        }
        int maxValue = 0;
        for(int i = 0; i < n; i++)
        {
            maxValue = max(maxValue,dp[i]);
        }
        return maxValue;
 }

2. 最大递增子序列的和

题意:跟上一题差不多,就只是求个和。

代码:

int Max_LIS_Sum(int a[],int n)
{
    int dp[MAX];
	int maxn = a[0];
	for(int i = 0; i < n; i++)
	{
		dp[i] = a[i];
	}
	for(int i = 1; i < n; i++)
	{
		for(int j = 0; j < i; j++)
		{
			if(a[i] > a[j])
			{
				dp[i] = max(dp[i],dp[j] + a[i]);
				if(dp[i] > maxn)
				{
					maxn = dp[i];
				}
			}
		}
	}
	return maxn;
}

3.最大连续和

题意:给定一个数组,求出最大的连续和,注意:这里指定是连续和

解法:

1. 枚举起点和终点 - O(n3)
2. 求出前缀和,然后再枚举起点和终点 - O(n2)
3. 分治法 - O(nlogn)
最大连续和一定是左边最大连续和 or 右边最大连续和 or 左边右边各取一部分的连续和 的最大值,然后具体到左边和右边,又可以分成这三种情况求最大值。
4. 动态规划 - O(n)
最大连续和一定是以i结尾的,所以只要求出每个以i结尾的最大连续和即可,从dp数组里面找出最大值就是序列的最大连续和。
5. 对方法2的优化 - O(n)
若最大连续和以m为起点,n为终点,即sum[n] - sum[m],sum[m]必然是sum[1]…sum[n-1]中的最小值**(不理解)**,所以只需记录下之前的最小值即可。

代码:

// 1. 枚举起点和终点
int GetMaxSum_n3(int * a,int n)
{
    int maxn = -INF;
    for(int i = 0; i < n; i++)
    {
        for(int j = i; j < n; j++)
        {
            int sum = 0;
            for(int k = i; k <= j; k++)
            {
                sum = sum + a[k];
            }
            if(sum > maxn)
            {
                maxn = sum;
            }
        }
    }
    return maxn;
}

// 2. 求出前缀和,然后再枚举起点和终点
int GetMaxSum_n2(int * a,int n)
{
    int sum[MAX];
    memset(sum,0,sizeof(sum));
    sum[0] = 0;
    for(int i = 1; i <= n; i++)
    {
        sum[i] = a[i - 1] + sum[i - 1];
    }
    int maxn = -INF;
    for(int i = 1; i <= n; i++)
    {
        for(int j = i; j <= n; j++)
        {
            if(sum[j] - sum[i - 1] > maxn)
            {
                maxn = sum[j] - sum[i - 1];
            }
        }
    }
    return maxn;
}

//3. 分治法
int Max(int x,int y,int z)
{
    if(x > y)
    {
        if(x > z)
        {
            return x;
        }
        else
        {
            return z;
        }
    }
    else
    {
        if(y > z)
        {
            return y;
        }
        else
        {
            return z;
        }
    }
}
int GetMaxSum_nlogn(int * a,int left,int right)
{
    if(left == right)
    {
        return a[left];
    }
    int mid = left + (right - left) / 2;
    int leftMax = GetMaxSum_nlogn(a,left,mid);      /** 左边最大值 */
    int rigthMax = GetMaxSum_nlogn(a,mid+1,right);  /** 右边最大值 */
    int sum = 0;
    int leftmaxn = a[mid];
    for(int i = mid; i >= left; i--)
    {
        sum = sum + a[i];
        if(sum > leftmaxn)
        {
            leftmaxn = sum;
        }
    }
    sum = 0;
    int rightmaxn = a[mid + 1];
    for(int i = mid + 1; i <= right; i++)
    {
        sum = sum + a[i];
        if(sum > rightmaxn)
        {
            rightmaxn = sum;
        }
    }
    return Max(leftMax,rigthMax,leftmaxn + rightmaxn);
}

// 4. 动态规划
int dp[MAX];
int GetMaxSum_n_1(int * a,int n)
{
    /** 其实这里不用dp数组也可以,只要用原始的a数组记录下就可以了,以达到空间的最大优化 */
    memset(dp,0,sizeof(dp));
    for(int i = 0; i < n; i++)
    {
        dp[i] = a[i];
    }
    int maxn = -INF;
    for(int i = 1; i < n; i++)
    {
        dp[i] = max(0,dp[i - 1]) + dp[i];
        if(dp[i] > maxn)
        {
            maxn = dp[i];
        }
    }
    return maxn;
}

// 5. 对方法2的优化
int GetMaxSum_n_2(int * a,int n)
{
    int sum[MAX];
    memset(sum,0,sizeof(sum));
    int maxn = a[0];
    int minn = 0;
    sum[0] = 0;
    for(int i = 1; i <= n; i++)
    {
        sum[i] = a[i - 1] + sum[i - 1];
        if(sum[i] - minn > maxn)
        {
            maxn = sum[i] - minn;
        }
        if(sum[i] < minn)
        {
            minn = sum[i];
        }
    }
    return maxn;
}

4. 最长公共子序列(LCS)

题意:给定两个数组,求出最长公共子序列的长度(有的题目可能还要直接求出具体的序列)。

解法:

1. 递归解法
从两个数组的末尾开始比较,一步步地向前递减,每次做的判断:
如果两个数相等,则都下标都-1,对应的+1
如果两个数不相等,则返回较大的值,具体看代码吧
2. 动态规划解法
思路类似,具体看下面的代码

代码:

/** 递归求解最长公共子序列的长度(LCS) */
int RecursiveForLCSLength(char * x,char * y,int i,int j)
{
    if(i == 0 || j == 0)
    {
        return 0;
    }
    if(i > 0 && j > 0 && x[i] == y[j])
    {
        return RecursiveForLCSLength(x,y,i - 1,j - 1) + 1;
    }
    if(i > 0 && j > 0 && x[i] != y[j])
    {
        return max(RecursiveForLCSLength(x,y,i - 1,j),RecursiveForLCSLength(x,y,i,j - 1));
    }
}

/** 动态规划求解最长公共子序列的长度(LCS) */
int dp[MAX][MAX];
int DpForLCSLength(char * x,char * y,int len_x,int len_y)
{
    memset(dp,0,sizeof(dp));
    /** 赋初值 */
    for(int i = 0; i <= len_x; i++)
    {
        dp[i][0] = 0;
    }
    for(int j = 0; j <= len_y; j++)
    {
        dp[0][j] = 0;
    }
    /** 根据动态规划的状态转移方程来填表 */
    for(int i = 1; i <= len_x; i++)
    {
        for(int j = 1; j <= len_y; j++)
        {
            if(x[i - 1] == y[j - 1])
            {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            }
            else
            {
                dp[i][j] = max(dp[i - 1][j],dp[i][j - 1]);
            }
        }
    }
    /** 由表得出最后的结果LCS */
    return dp[len_x][len_y];
}

如果要求出具体的最长公共子序列,可以用下面的方法:

/** 求出最长公共子序列(利用动态规划的解法 + 借助一个临时数组记录动态规划过程)
 *  创建一个辅助数组,在动态规划填表的时候记录下解的由来:
 *  三种情况:
 *  1、x[i] == y[j] ---> 从左上角得解(dp[i - 1][j - 1]) ---> 2
 *  2、x[i] !=  y[j] && dp[i - 1][j] > dp[i][j - 1] ---> 从上面得解(dp[i - 1][j]) ---> 3
 *  3、x[i] !=  y[j] && dp[i - 1][j] < dp[i][j - 1] ---> 从左边得解(dp[i][j - 1]) ---> 1
*/
void DpForLCS(char * x,char * y,int len_x,int len_y)
{
    /** 其他过程和直接求长度相同,都要填表,只是这里要记录填表的过程*/
    memset(dp,0,sizeof(dp));
    memset(t,0,sizeof(t));
    for(int i = 0; i <= len_x; i++)
    {
        dp[i][0] = 0;
    }
    for(int j = 0; j <= len_y; j++)
    {
        dp[0][j] = 0;
    }
    /** 根据动态规划的状态转移方程来填表 */
    for(int i = 1; i <= len_x; i++)
    {
        for(int j = 1; j <= len_y; j++)
        {
            if(x[i - 1] == y[j - 1])
            {
                dp[i][j] = dp[i - 1][j - 1] + 1;
                t[i][j] = 2;/** 左上角得解 ---> 2 */
            }
            else
			{
           	 	if(dp[i - 1][j] >= dp[i][j - 1])
            	{
                	dp[i][j] = dp[i - 1][j];
                	t[i][j] = 3;/** 上面得解 ---> 3 */
            	}
            	else if(dp[i - 1][j] < dp[i][j - 1])
            	{
                	dp[i][j] = dp[i][j - 1];
               	 	t[i][j] = 1;/** 左边得解 ---> 1 */
            	}
			} 
        }
    }
}

5. 最长递增公共子序列

题意: 两个序列a、b,找出两个序列中所有的既是递增序列又是公共序列,而且找出其中最大长度的。

解法:

【待理解】

代码:

int DP(int m,int n)
{
    memset(dp,0,sizeof(dp));
    for(int i = 0; i < m; i++)
    {
        int maxn = -INF;
        for(int j = 0; j < n; j++)
        {
            dp[i][j] = dp[i - 1][j];
            if(a[i] > b[j] && maxn < dp[i - 1][j])
            {
                maxn = dp[i - 1][j];
            }
            else if(a[i] == b[i])
            {
                dp[i][j] = maxn + 1;
            }
        }
    }
    int maxn = -INF;
    for(int k = 0; k < n; k++)
    {
        if(dp[m][k] > maxn)
        {
            maxn = dp[m][k];
        }
    }
    return maxn;
}
发布了197 篇原创文章 · 获赞 18 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_41708792/article/details/102923784