JHHK

欢迎来到我的个人网站
行者常至 为者常成

【进阶】19、动态规划(三)

目录

最长公共子序列(Longest Common Subsequence,LCS)

一、问题

求两个序列的最长公共子序列长度
[1, 3, 5, 9, 10] 和 [1, 4, 9, 10] 的最长公共子序列是 [1, 9, 10],长度为 3

ABCBDAB 和 BDCABA 的最长公共子序列长度是 4,可能是
✓ ABCBDABBDCABA > BDAB
✓ ABCBDAB BDCABA > BDAB
✓ ABCBDAB 和 BDCABA > BCAB
✓ ABCBDAB 和 BDCABA > BCBA

二、思路

img

代码实现

一、递归实现

+(int)lcsnums1:(NSArray*)nums1 nums2:(NSArray*)nums2{
    
    if (nums1 == nil || nums1.count == 0) return 0;
    if (nums2 == nil || nums2.count == 0) return 0;
    
    return [self __lcsnums1:nums1
                         n1:(int)nums1.count
                      nums2:nums2
                         n2:(int)nums2.count];
}



/// 返回最长公共子序列的长度
/// @param nums1 序列1
/// @param n1 序列1长度
/// @param nums2 序列2
/// @param n2 序列2长度
+(int)__lcsnums1:(NSArray*)nums1 n1:(int)n1 nums2:(NSArray*)nums2 n2:(int)n2{
    
    //递归基
    if (n1 == 0 || n2 == 0) return 0;
    
    if ([nums1[n1-1] intValue] == [nums2[n2-1] intValue]){
        return [self __lcsnums1:nums1 n1:n1-1 nums2:nums2 n2:n2-1]+1;
    }else{
        int length1 = [self __lcsnums1:nums1 n1:n1 nums2:nums2 n2:n2-1];
        int length2 = [self __lcsnums1:nums1 n1:n1-1 nums2:nums2 n2:n2];
        return MAX(length1, length2);
    }
}

空间复杂度:O(k) , k = min{n, m},n、m 是 2 个序列的长度
时间复杂度:O(2^n) ,当 n = m 时

img

出现了重复的递归调用

二、非递归实现

+(int)lcs2nums1:(NSArray*)nums1 nums2:(NSArray*)nums2{
    
    if (nums1 == nil || nums1.count == 0) return 0;
    if (nums2 == nil || nums2.count == 0) return 0;

    //dp[i][j]:num1[i-1] 和 num2[j-1] 最长公共子序列的长度
    NSMutableArray<NSMutableArray*> * dp = [NSMutableArray array];
    for (int row = 0; row<=nums1.count; row++) {
        dp[row] = [NSMutableArray array];
        for (int col = 0; col <= nums2.count; col++) {
            dp[row][col] = @(0);
        }
    }
    

    for (int row = 1; row <= nums1.count; row++) {
        for (int col = 1; col <= nums2.count; col++) {
            //状态转移方程
            if ([nums1[row-1] intValue] == [nums2[col-1] intValue]) {
                dp[row][col] = @([dp[row-1][col-1] intValue] + 1);
            }else{
                int length1 = [dp[row][col-1] intValue];
                int length2 = [dp[row-1][col] intValue];
                dp[row][col] = @(MAX(length1, length2));
            }
        }
    }
    
    return [dp[nums1.count][nums2.count] intValue];
}

空间复杂度:O(n ∗ m)
时间复杂度:O(n ∗ m)

dp 数组的计算结果如下所示

img

三、非递归实现(滚动数组)

可以使用滚动数组优化空间复杂度

+(int)lcs3nums1:(NSArray*)nums1 nums2:(NSArray*)nums2{
    
    if (nums1 == nil || nums1.count == 0) return 0;
    if (nums2 == nil || nums2.count == 0) return 0;

    //dp[i][j]:num1[i-1] 和 num2[j-1] 最长公共子序列的长度
    //只用2行:上一行 当前行
    NSMutableArray<NSMutableArray*> * dp = [NSMutableArray array];
    for (int row = 0; row<=1; row++) {
        dp[row] = [NSMutableArray array];
        for (int col = 0; col <= nums2.count; col++) {
            dp[row][col] = @(0);
        }
    }
    

    for (int row = 1; row <= nums1.count; row++) {
        int preRow = (row-1) % 2;
        int curRow = row % 2;

        for (int col = 1; col <= nums2.count; col++) {
            //状态转移方程
            if ([nums1[row-1] intValue] == [nums2[col-1] intValue]) {
                dp[curRow][col] = @([dp[preRow][col-1] intValue] + 1);
            }else{
                int length1 = [dp[curRow][col-1] intValue];
                int length2 = [dp[preRow][col] intValue];
                dp[curRow][col] = @(MAX(length1, length2));
            }
        }
    }
    
    return [dp[nums1.count % 2][nums2.count] intValue];
}

四、非递归实现(一维数组)

可以将 二维数组 优化成 一维数组,进一步降低空间复杂度

+(int)lcs4nums1:(NSArray*)nums1 nums2:(NSArray*)nums2{
    
    if (nums1 == nil || nums1.count == 0) return 0;
    if (nums2 == nil || nums2.count == 0) return 0;

    //dp[i][j]:num1[i-1] 和 num2[j-1] 最长公共子序列的长度
    //只用一维数组
    NSMutableArray * dp = [NSMutableArray array];
    for (int col = 0; col <= nums2.count; col++) {
        dp[col] = @(0);
    }
    
    for (int row = 1; row <= nums1.count; row++) {
        int cur = 0;
        for (int col = 1; col <= nums2.count; col++) {
            int leftTop = cur;
            cur = [dp[col] intValue];//记录当前值,作为下一轮的leftTop
            //状态转移方程
            if ([nums1[row-1] intValue] == [nums2[col-1] intValue]) {
                dp[col] = @(leftTop + 1);
            }else{
                int length1 = [dp[col-1] intValue]; //左面
                int length2 = [dp[col] intValue];   //上面
                dp[col] = @(MAX(length1, length2)); //覆盖
            }
        }
    }
    return [dp[nums2.count] intValue];
}

五、非递归实现(一维数组-使用较短的序列)

可以空间复杂度优化至 O(k) , k = min{n, m}

+(int)lcs5nums1:(NSArray*)nums1 nums2:(NSArray*)nums2{
    
    if (nums1 == nil || nums1.count == 0) return 0;
    if (nums2 == nil || nums2.count == 0) return 0;

    //取出较短的数组作为列
    NSArray * rowNums = nums1;
    NSArray * colNums = nums2;
    if (nums1.count < nums2.count) {
        colNums = nums1;
        rowNums = nums2;
    }
    
    
    
    //dp[i][j]:num1[i-1] 和 num2[j-1] 最长公共子序列的长度
    //只用一维数组
    NSMutableArray * dp = [NSMutableArray array];
    for (int col = 0; col <= colNums.count; col++) {
        dp[col] = @(0);
    }
    
    for (int row = 1; row <= rowNums.count; row++) {
        int cur = 0;
        for (int col = 1; col <= colNums.count; col++) {
            int leftTop = cur;
            cur = [dp[col] intValue];//记录当前值,作为下一轮的leftTop
            //状态转移方程
            if ([rowNums[row-1] intValue] == [colNums[col-1] intValue]) {
                dp[col] = @(leftTop + 1);
            }else{
                int length1 = [dp[col-1] intValue]; //左面
                int length2 = [dp[col] intValue];   //上面
                dp[col] = @(MAX(length1, length2)); //覆盖
            }
        }
    }
    return [dp[colNums.count] intValue];
}

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.