动态规划——最长递增子序列

题目

我们有一个数字序列包含 n 个不同的数字,如何求出这个序列中的最长递增子序列长度?比如 2, 9, 3, 6, 5, 1, 7 这样一组数字序列,它的最长递增子序列就是 2, 3, 5, 7,所以最长递增子序列的长度是 4。

回溯法

数组长度为n,每一步考虑当前元素要不要加入到最长递增子序列中。如果加上去,最后的长度是多少?如果不加该元素,最后的长度又是多少。这是一个多阶段决策最优化问题。可以用回溯法解决。

	public class IncreaseArray {
    private int[] nums = new int[]{2,9,3,6,5,1,7};
    private int n = nums.length;
    private int maxLen = 0;

    /**
     *
     * @param i
     *          当前处理的元素下标
     * @param lastIdx
     *          最后一个进入到最长递增子序列的元素下标,用于比较
     */
    private void f(int i,int lastIdx,int len){
        if(i==n){
            maxLen = Math.max(maxLen,len);
            return;
        }
        if(lastIdx == -1 || nums[i]>nums[lastIdx]){
            //加入到最长递增子数组中
            f(i+1,i,len+1);
        }
        //不加入到最长递增子数组中
        f(i+1,lastIdx,len);
    }

    private int maxIncreaseArrayLength(){

        f(0,-1,0);
        return maxLen;
    }
 }

备忘录模式

画递归树,查看是否有重复子问题。每个节点状态用(i,j,len)表示。i表示到达地i个元素 ,j表示加入到最长递增子序列的最后一个元素的下标,len表示当前状态下最长递增子序列的长度。

从树中能看出(3,2,1) (3,2,2) 只需要留下 (3,2,2)这个节点就行。因为这个节点的长度最长,而且长度是1还是2与后面递增子序列的添加没有影响。我们的目标又是求最长的递增子序列的长度,所以留 (3,2,2)这个节点就行。

所以我们可以用备忘录模式来解决。

	private int fV2(int i,int lastIdx,int[][] memo){
        if(i==n){
            return 0;
        }
        if(memo[lastIdx+1][i]>=0) return memo[lastIdx+1][i];
        int value = 1;
        if(lastIdx == -1 || nums[i]>nums[lastIdx]){
            //加入到最长递增子数组中
            value = 1+fV2(i+1,i,memo);
        }
        //不加入到最长递增子数组中
        int value1 = fV2(i+1,lastIdx,memo);
        memo[lastIdx+1][i] = Math.max(value,value1);
        return memo[lastIdx+1][i];
    }

    private int maxIncreaseArrayLengthV2(){
        int[][] memo = new int[n][n];
        for(int[] array : memo){
            Arrays.fill(array,-1);
        }
        return fV2(0,-1,memo);
    }

动态规划

从上面的分析中我们知道,当处理第i个元素的时候,要求得第i个元素最长递增子序列的长度是多少,与前面步骤中递增子序列以哪个元素结尾有关系。那么前面步骤中递增子序列可能的结尾是第0个元素,第1个元素,第2个元素…第i-1个元素。如果我们能分别知道以这些元素结尾的最长递增子序列的长度,就可以推导出以第i个元素为结尾的最长递增子序列的长度。

我们用max_lcs[i]来记录以第i个元素为结尾的最长递增子序列的长度。
那么设j=0,1,2…i-1,当nums[i]>nums[j]的时候,max_lcs[j]+1就是一个可能的值。从这些值中取最大值,就是max_lcs[i]的值。

max_lcs[i]=max(max_lcs[j]+1),0<=j<i && nums[i]>nums[j]。

最后再从max_lcs数组中查找最大值,就是答案。

	private int maxIncreaseArrayLengthDp(){
        if(nums==null || nums.length == 0) return 0;
        int[] dp = new int[n];//表示以第i个元素作为结尾的最长递增子序列的最大长度
        int maxLen = 1;
        for(int i=0;i<n;i++){
            int value = 1;
            for(int j=i-1;j>=0;j--){
                if(nums[i]>nums[j]){
                    value = Math.max(value,dp[j]+1);
                }
            }
            dp[i] = value;
            maxLen = Math.max(maxLen,dp[i]);
        }
        return maxLen;
    }

写在后面:这道题目在回溯法中知道,当处理第i个元素的时候,与前面步骤中递增子序列以哪个元素结尾有关系。得出这个关系很重要。而且能够将状态转移表简化为max_lcs这个一位数组也是关键。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值