Dynamic Programming 之 Longest Increasing Subsequence 问题

首先复习一下可以应用dynamic programming的问题的两个要素:

1)Optimal Substructures: 一个问题的最优解取决于其子问题的最优解。

2)Overlapping Subproblems:我们总是会遇到同一个相同的子问题,于是我们不需要每次遇到都算一次,而是把子问题的解都存放在一个table中,需要的时候去查一下即可。这是时间和存储空间之间的trade off。这也是DM解决问题高效的根本原因。


好,我们来看一下Longest Increasing Subsequence (LIS) 问题。看看为什么它能用DM解决,以及如何解决。

LIS问题的描述是这样的:给你一个元素为int的数组,让你找到一个它的subsequence,这个subsequence必须是不断增大的,而且是所有这样的subsequence中最长的。

举个例子:{2, 3, 1, 5, 4, 7} 的LIS是{2, 3, 5, 7},其长度是4.


我将先给出这个问题的DM解法,然后分析。


程序的输入是一个int数组int arr[],和它的长度n.。

首先声明一个数组m[n]。这个数组中的一个元素m[i]表示的是:以arr[i]这个元素为最后一个元素的LIS。那很显然,m[0]=1:以arr[0]为最后一个元素的LIS就只有一个元素:arr[0]。

接下来使用一个for循环,计算出每一个m[i],计算方法是:

m[i] = max(m[j]) + 1, where j<i and arr[j] < arr[i]。 

得到所有的m[i]之后,用一个循环找到里面的最大值,就是我们输入数组的LIS。

代码如下:

int LIS(int arr, int n){
	int m[n];
	for (int i = 0; i < n; i ++)
		m[i] = 0;

	m[0] = 1;
	for ( i = 1; i < n; i++ ){
      for ( j = 0; j < i; j++ ){
         if ( arr[i] > arr[j] && m[i] < m[j] + 1)
            m[i] = m[j] + 1;
    }

    if (m[i] == 0)
    	m[i] == 1;

    int max = -1;
    for (int i = 0; i < n; i ++){
    	if(m[i] > max)
    		max = m[i];
	}
	return max;
}

这个算法的efficiency为O(n^2)。


下面我们来分析一下这个问题。看过上面的解法,我们很容易发现,这个问题被演化成了另一个问题,那就是我们首先找的不是这个数组的LIS,而是这个数组的LISthat includes arr[i] as its last element,也就是我们程序里的m[i]。当我们找到了所有的m[i]之后,整个数组的LIS肯定在这之中,因为整个数组的LIS肯定以某一个m[i]结尾。如果这道题不是要找LIS,而是要找LIS that includes 某一个元素做其结尾元素,那么其思维过程就简单明了。但是程序上并不会简洁太多,只是少了最后一个for循环,找m[i]中的最大值一步。其实很多时候,DM问题都有这样的特征,只要修改一点点,问题就会变得简单很多(或者难很多),即使只多了一步,也给人的思维过程产生很大的障碍,不知道应该如何应用DM。稍后我们会给出另外一个例子。


好了,下面我们来看,为什么找m[i]的过程可以应用DM。这就要用到我们最开始提到的DM问题的两要素。

首先,optimal substructures:每一个m[i]的计算,都要取决于他的子问题们,即在所有的m[j] (j<i) 中找到arr[j] < arr[i]的最长的那一个,在此基础上加1。明显,所有的m[j]都是其对应m[i]的子问题。

第二,overlapping subproblem:我们需要一遍一遍地算相同的子问题。比如在算m[3]的时候,我们需要m[2],在算m[4]的时候,我们也需要m[2]。事实上在算所有的m[j] (j > 2)中,我们都需要m[2]。那么我们只需要把m[2]存在内存中,而不需要每次都去算一次。

满足了上述两点,用DM来解决这个问题也是理所应当的了。


下面我们来看另一道题:Longest Bitonic Subsequence。所谓bitonic  subsequence就是这个subsequence先增大,后减小。其中增大的部分或减小的部分可以为空,换句话说,如果一个subsequence仅增大或仅减小,也满足条件。

LBS问题是LIS的一个变种。事实上,它比LIS问题仅仅多一点点步骤。我们需要做的是,在LIS计算数组m[]之后,调转arr[],用这个调转的数组做输入,再计算一个inv_m[]。然后我们遍历m[]和inv_m[],找到他们之和最大的那个。

我们可以看到,很多DM问题就是这样,仅仅比naive的DM问题多那么一小步,就变成了一个新的问题。我们需要做的是,识破它们,解决它们。


顺便说一句,LBS问题是微软的面试题。



Ref:

http://www.geeksforgeeks.org/archives/12832

http://www.geeksforgeeks.org/archives/19729

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值