JHHK

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

【进阶】20、动态规划(四)

目录

最长公共子串(Longest Common Substring)

一、最长公共子串 - 题目

最长公共子串(Longest Common Substring)
子串是连续的子序列
求两个字符串的最长公共子串长度
ABCBA 和 BABCA 的最长公共子串是 ABC,长度为 3

二、最长公共子串 – 思路

假设 2 个字符串分别是 str1、str2
i ∈ [1, str1.length]
j ∈ [1, str2.length]
假设 dp(i, j) 是以 str1[i – 1]、str2[j – 1] 结尾的最长公共子串长度
dp(i, 0)、dp(0, j) 初始值均为 0
如果 str1[i – 1] = str2[j – 1],那么 dp(i, j) = dp(i – 1, j – 1) + 1
如果 str1[i – 1] ≠ str2[j – 1],那么 dp(i, j) = 0
最长公共子串的长度是所有 dp(i, j) 中的最大值 max { dp(i, j) }

三、最长公共子串 – 实现

/// 返回最长公共子串长度
/// @param str1 串1
/// @param str2 串2
+(int)lcSubstringWithString1:(NSString*)str1 string2:(NSString*)str2{
    if (str1 == nil || str1.length == 0) return 0;
    if (str2 == nil || str2.length == 0) return 0;

    
    //dp[i][j] str1[i-1] str[j-1] 的最长公共子串长度
    NSMutableArray<NSMutableArray*> * dp = [NSMutableArray array];
    for (int i=0; i<=str1.length; i++) {
        dp[i] = [NSMutableArray array];
        for (int j = 0; j<=str2.length; j++) {
            dp[i][j] = @(0);
        }
    }
    
    int max = 0;
    for (int i = 1; i<=str1.length; i++) {
        for (int j = 1; j<=str2.length; j++) {
//            if ([str1 characterAtIndex:i-1] == [str2 characterAtIndex:j-1]) {
//                dp[i][j] = @([dp[i-1][j-1] intValue]+1);
//            }else{
//                 dp[i][j] = @(0);
//            }
            
            if ([str1 characterAtIndex:i-1] != [str2 characterAtIndex:j-1]) continue;
            int n = [dp[i-1][j-1] intValue]+1;
            dp[i][j] = @(n);
            max = MAX(max, n);
        }
    }
    
    return max;
}

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

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

img

四、最长公共子串 – 一维数组实现

1、实现方式一

+(int)lcSubstring2WithString1:(NSString*)str1 string2:(NSString*)str2{
    if (str1 == nil || str1.length == 0) return 0;
    if (str2 == nil || str2.length == 0) return 0;

    
    //dp[j] str1[i-1] str[j-1] 的最长公共子串长度 二维数组优化成一维数组
    NSMutableArray * dp = [NSMutableArray array];
    for (int j = 0; j<=str2.length; j++) {
        dp[j] = @(0);
    }
    
    int max = 0;
    for (int i = 1; i<=str1.length; i++) {
        int cur = 0;
        for (int j = 1; j<=str2.length; j++) {
            int leftTop = cur;
            cur = [dp[j] intValue];
            if ([str1 characterAtIndex:i-1] != [str2 characterAtIndex:j-1]){
                dp[j] = @(0);
            }else{
                int n = leftTop+1;
                dp[j] = @(n);
                max = MAX(max, n);
            }
        }
    }
    
    return max;
}

2、实现方式二(优化)

+(int)lcSubstring3WithString1:(NSString*)str1 string2:(NSString*)str2{
    
    if (str1 == nil || str1.length == 0) return 0;
    if (str2 == nil || str2.length == 0) return 0;
    
    //dp[j] str1[i-1] str[j-1] 的最长公共子串长度 二维数组优化成一维数组
    NSMutableArray * dp = [NSMutableArray array];
    for (int j = 0; j<=str2.length; j++) {
        dp[j] = @(0);
    }

    int max = 0;
    for (int i = 1; i<=str1.length; i++) {
        for (int j =(int)str2.length; j>=1; j--) {
            if ([str1 characterAtIndex:i-1] != [str2 characterAtIndex:j-1]){
                dp[j] = @(0);
            }else{
                int n = [dp[j-1] intValue]+1;
                dp[j] = @(n);
                max = MAX(max, n);
            }
        }
    }
      
    return max;
}

3、实现方式三(进一步优化)

+(int)lcSubstring4WithString1:(NSString*)str1 string2:(NSString*)str2{
    
    if (str1 == nil || str1.length == 0) return 0;
    if (str2 == nil || str2.length == 0) return 0;
    
    
    NSString * rowStr = str1;
    NSString * colStr = str2;
    if (str1.length < str2.length) {
        rowStr = str2;
        colStr = str1;
    }
    
    //dp[j] str1[i-1] str[j-1] 的最长公共子串长度 二维数组优化成一维数组
    //长度短的做列,减少空间占用
    NSMutableArray * dp = [NSMutableArray array];
    for (int col = 0; col<=colStr.length; col++) {
        dp[col] = @(0);
    }
    
    int max = 0;
    for (int row = 1; row<=rowStr.length; row++) {
        for (int col =(int)colStr.length; col>=1; col--) {
            if ([rowStr characterAtIndex:row-1] != [colStr characterAtIndex:col-1]){
                dp[col] = @(0);
            }else{
                int n = [dp[col-1] intValue]+1;
                dp[col] = @(n);
                max = MAX(max, n);
            }
        }
    }
    return max;
}

空间复杂度:O(k) , k = min{n, m}
时间复杂度:O(n ∗ m)

0-1背包

一、背包-题目

有 n 件物品和一个最大承重为 W 的背包,每件物品的重量是 𝑤i、价值是 𝑣i
在保证总重量不超过 W 的前提下,选择某些物品装入背包,背包的最大总价值是多少?
注意:每个物品只有 1 件,也就是每个物品只能选择 0 件或者 1 件

二、思路

假设 values 是价值数组,weights 是重量数组
编号为 k 的物品,价值是 values[k],重量是 weights[k],k ∈ [0, n)
假设 dp(i, j) 是 有前 i 件物品可选,最大承重为 j 时的最大总价值,i ∈ [1, n],j ∈ [1, W]
dp(i, 0)、dp(0, j) 初始值均为 0

分为两种情况,最后一件物品能够装下,不能够装下:
如果 j < weights[i – 1] 不能够装下:
那么 dp(i, j) = dp(i – 1, j)

如果 j ≥ weights[i – 1] 能够装下:
选择装入: value1 = dp(i – 1, j – weights[i – 1]) + values[i – 1]
选择不装: value2 = dp(i – 1, j)
那么 dp(i, j) = max { value1,value2 } 。

三、代码实现

1、暴力递归实现

/// 0-1背包问题(背包装的物品的最大价值)
/// @param values 价值数组
/// @param weights 重量数组,与价值数组一一对应,表示同一件物品
/// @param capacity 背包容量
//暴力递归实现
+(int)knapsackWithValues:(NSArray*)values
                 weights:(NSArray*)weights
                capacity:(int)capacity{
    
    if (values == nil || values.count == 0)             return 0;
    if (weights == nil || weights.count == 0)           return 0;
    if (capacity < 1 || values.count != weights.count)  return 0;
    
    
    return [self __knapsackWithValues:values
                              weights:weights
                             capacity:capacity
                               frontN:(int)weights.count];
}


+(int)__knapsackWithValues:(NSArray*)values
                   weights:(NSArray*)weights
                  capacity:(int)capacity
                    frontN:(int)n{
    
    if(n < 1)      return 0;
    if (capacity < 1)   return 0;
    
    //最后一件不可选
    int value = 0;
    if (capacity < [weights[n-1] intValue]) {
        //最大承重:capacity,
        //前i  件:n-1
        //的最大价值
        value = [self __knapsackWithValues:values
                                      weights:weights
                                     capacity:capacity
                                       frontN:n-1];

        
    //最后一件可选
    }else{
        
        //最大承重为:capacity - 最后一件重量,
        //前i件   :n-1
        //的最大价值
        int value1 = [self __knapsackWithValues:values
                                        weights:weights
                                       capacity:capacity - [weights[n-1] intValue]
                                         frontN:n-1];
        
        //最大承重为:capacity,
        //前i件   :n
        //的最大价值
        value1 =  value1 + [values[n-1] intValue];
        
        
        //最大承重:capacity,
        //前i  件:n-1
        //的最大价值
        int value2 = [self __knapsackWithValues:values
                                        weights:weights
                                       capacity:capacity
                                         frontN:n-1];


        
        value = MAX(value1, value2);
    }
    
    return value;
}

二、非递归实现 - 二维数组

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

img

+(int)knapsack2WithValues:(NSArray*)values
                  weights:(NSArray*)weights
                 capacity:(int)capacity{
    
    if (values == nil || values.count == 0)             return 0;
    if (weights == nil || weights.count == 0)           return 0;
    if (capacity < 1 || values.count != weights.count)  return 0;

    //dp[i][j] 前i个物品,最大承重为j 的最大价值
    NSMutableArray<NSMutableArray*> * dp = [NSMutableArray array];
    for (int i = 0; i<=weights.count; i++) {
        dp[i] = [NSMutableArray array];
        for (int j = 0; j<=capacity; j++) {
            dp[i][j] = @(0);
        }
    }
    
    
    //状态转移方程
    for (int i = 1; i<=weights.count; i++) {
        for (int j = 1; j<=capacity; j++) {
            
            if (j < [weights[i-1] intValue]) {//不可装
                dp[i][j] = dp[i-1][j];
                
            }else{//可装
                
                //可装 - 并且选择装入
                int value1 = [dp[i-1][j-[weights[i-1] intValue]] intValue];
                value1 += [values[i-1] intValue];
                
                //可装 - 但是选择不装
                int value2 = [dp[i-1][j] intValue];

                dp[i][j] = @(MAX(value1, value2));
            }
           
        }
    }
    
    return [dp[weights.count][capacity] intValue];
}

三、非递归实现 - 一维数组

dp(i, j) 都是由 dp(i – 1, k) 推导出来的,也就是说,第 i 行的数据是由它的上一行第 i – 1 行推导出来的
因此,可以使用一维数组来优化
另外,由于 k ≤ j ,所以 j 的遍历应该由大到小,否则导致数据错乱

+(int)knapsack3WithValues:(NSArray*)values
                  weights:(NSArray*)weights
                 capacity:(int)capacity{
    
    if (values == nil || values.count == 0)             return 0;
    if (weights == nil || weights.count == 0)           return 0;
    if (capacity < 1 || values.count != weights.count)  return 0;

    //dp[i][j] 前i个物品,最大承重为j 的最大价值
    NSMutableArray<NSNumber*> * dp = [NSMutableArray array];
    for (int j = 0; j<=capacity; j++) {
        dp[j] = @(0);
    }
    
    
    //状态转移方程
    for (int i = 1; i<=weights.count; i++) {
        
//        下面这种写法不可行
        
//        int cur = 0;
//        for (int j = 1; j<=capacity; j++) {
//            if (j < [weights[i-1] intValue]) {//不可装
//                dp[j] = dp[j];
//            }else{//可装
//                #warning 这样写是不行了 dp[j-weights[i-1] 的值已经被覆盖 拿不到了
//                int leftTop = cur;
//                cur = [dp[j - [weights[i-1] intValue] + 1] intValue];//注意此处的位置
//
//                //可装 - 并且选择装入
//                int value1 = leftTop + [values[i-1] intValue];
//                //可装 - 但是选择不装
//                int value2 = [dp[j] intValue];
//                dp[j] = @(MAX(value1, value2));
//            }
//        }
        
        for (int j = capacity; j>=1; j--) {
            if (j < [weights[i-1] intValue]) {//不可装
                dp[j] = dp[j];
            }else{//可装
                
                //可装 - 并且选择装入
                int value0 =  [dp[j - [weights[i-1] intValue]] intValue];
                int value1 = value0 + [values[i-1] intValue];
                //可装 - 但是选择不装
                int value2 = [dp[j] intValue];
                dp[j] = @(MAX(value1, value2));
            }
        }
        
        
    }
    
    return dp[capacity].intValue;
}

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

观察二维数组表,得出结论:j 的下界可以从 1 改为 weights[i – 1]

+(int)knapsack4WithValues:(NSArray*)values
                  weights:(NSArray*)weights
                 capacity:(int)capacity{
    
    if (values == nil || values.count == 0)             return 0;
    if (weights == nil || weights.count == 0)           return 0;
    if (capacity < 1 || values.count != weights.count)  return 0;

    //dp[i][j] 前i个物品,最大承重为j 的最大价值
    NSMutableArray<NSNumber*> * dp = [NSMutableArray array];
    for (int j = 0; j<=capacity; j++) {
        dp[j] = @(0);
    }
    
    
    //状态转移方程(优化)
//    for (int i = 1; i<=weights.count; i++) {
//        for (int j = capacity; j>=1; j--) {
//            if (j < [weights[i-1] intValue]) continue;
//            //可装 - 并且选择装入
//            int value0 =  [dp[j - [weights[i-1] intValue]] intValue];
//            int value1 = value0 + [values[i-1] intValue];
//            //可装 - 但是选择不装
//            int value2 = [dp[j] intValue];
//            dp[j] = @(MAX(value1, value2));
//        }
//    }
    
    //状态转移方程(再优化)
    for (int i = 1; i<=weights.count; i++) {
        for (int j = capacity; j>=[weights[i-1] intValue]; j--) {
            //可装 - 并且选择装入
            int value0 =  [dp[j - [weights[i-1] intValue]] intValue];
            int value1 = value0 + [values[i-1] intValue];
            //可装 - 但是选择不装
            int value2 = [dp[j] intValue];
            dp[j] = @(MAX(value1, value2));
        }
    }
    
    return dp[capacity].intValue;
}

0-1背包,恰好装满

有 n 件物品和一个最大承重为 W 的背包,每件物品的重量是 𝑤i、价值是 𝑣i
在保证总重量恰好等于 W 的前提下,选择某些物品装入背包,背包的最大总价值是多少?
注意:每个物品只有 1 件,也就是每个物品只能选择 0 件或者 1 件
dp(i, j) 初始状态调整
dp(i, 0) = 0,总重量恰好为 0,最大总价值必然也为 0
dp(0, j) = –∞(负无穷),j ≥ 1,负数在这里代表无法恰好装满

img

/// 恰好装满时的最大价值(-1代表无法装满)
/// @param values 价值数组
/// @param weights 重量数组,与价值数组一一对应,表示同一件物品
/// @param capacity 背包容量
+(int)knapsack5WithValues:(NSArray*)values
                  weights:(NSArray*)weights
                 capacity:(int)capacity{
    
    if (values == nil || values.count == 0)             return -1;
    if (weights == nil || weights.count == 0)           return -1;
    if (capacity < 1 || values.count != weights.count)  return 0;
    

    //dp[i][j] 前i个物品,最大承重为j 的最大价值
    NSMutableArray<NSNumber*> * dp = [NSMutableArray array];
    
    dp[0] = @(0);//0
    for (int j = 0; j<=capacity; j++) {
        dp[j] = @(INT_MIN);//负无穷
    }
    
    
    
    //状态转移方程(再优化)
    for (int i = 1; i<=weights.count; i++) {
        for (int j = capacity; j>=[weights[i-1] intValue]; j--) {
            //可装 - 并且选择装入
            int value0 =  [dp[j - [weights[i-1] intValue]] intValue];
            int value1 = value0 + [values[i-1] intValue];
            //可装 - 但是选择不装
            int value2 = [dp[j] intValue];
            dp[j] = @(MAX(value1, value2));
        }
    }
    
    return dp[capacity].intValue < 0 ? -1 : dp[capacity].intValue;
}

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.