子序列与子数组(子串)

子序列与子数组(子串)

区别

  1. 子序列
    • 元素在原数组的下标不连续,相对位置不可改变
    • 例如
      • [1,2,3,4,5] -> [1,3,5]
  2. 子数组(子串)
    • 元素在原数组的下标连续,相对位置不可变
    • 例如
      • [1,2,3,4,5] -> [1,2,3]

特别的,子数组(子串)是子序列的一种特殊形式(当恰好取出的子序列是一连串的相邻元素时)

子序列常见问题与解法

  1. 最长上升子序列(LIS, Longest Increasing Subsequence)

    https://blog.csdn.net/lxt_Lucia/article/details/81206439

    子序列中的元素严格单调递增,即 nums[i-1] < nums[i] 总是成立

    LIS可能有多个,但是LIS的长度是固定的

    • [1, 7, 3, 5, 9, 4, 8]
    • res1 = [1, 3, 5, 8]
    • res2 = [1, 3, 5, 9]
  2. 最长不升子序列

    子序列中的元素总是满足 nums[i-1] >= nums[i]

  3. 最长下降子序列

    子序列中的元素总是满足严格单调递减 nums[i-1] > nums[i]

  4. 最长不降子序列

    子序列中的元素总是满足 nums[i-1] <= nums[i]

动态规划 - O(N^2)

定义dp数组

dp[i] 表前 i 个数以 nums[i] 结尾的最长上升子序列长度

base case
dp[i] = 1

状态转移

寻找左侧比 nums[i] 小的元素 nums[j], 在其基础上+1,没找到则维持1

dp[i] = max(dp[i], dp[j] + 1), (forEach j < i : nums[i] > nums[j])

// nums!=null && nums.length > 0
public int lis(int[] nums) {
    int n = nums.length;
    int[] dp = new int[n];
    
    // base case
    Arrays.fill(dp, 1);
    
    // find max
    int max = 1;
    
    // dp
    for (int i = 1; i < n; i++) {
        for (int j = i-1; j >= 0; j--) {
            if (nums[i] > nums[j]) 
                dp[i] = Math.max(dp[i], dp[j] + 1);
        }
        max = Math.max(max, dp[i]);
    }
    
    return max;
}

贪心+二分O(NlogN)

参考题解

P1020 导弹拦截

核心策略

将问题转化为单调问题,利用二分解决单调问题

int[] tails = new int[n+1];

tails[i] 表示所有长度为 i 的 LIS ,其末尾元素的最小值

PS:如果是递减趋势的问题,则改为末尾元素最大值即可

显然 tails[] 单调递增, tails[i-1] < tails[i]

int res 记录LIS长度,初始时 res <-1

更新规则

当 curr = nums[i] > tails[res]: tails[++res] = curr

否则 寻找第一个 tails[j] > curr 令 tails[j]<-curr

最终的结果res就是LIS长度,tails维护了各长度LIS的尾部(最右侧)最小值

// nums!=null && nums.length > 0
public int lis(int[] nums) {
    int n = nums.length;
    int[] tails = new int[n+1];
    int res = 1; // 记录 LIS 长度
    tails[res] = nums[0]; // 初始化,将 nums[0] 作为 res=1 的LIS末尾元素
    
    int curr; // 记录遍历的值
    for (int i = 1; i < n; i++) {
        if ((curr = nums[i]) > tails[res]) {
            tails[++res] = curr;
        } else {
            // 二分搜索第一个 tails[j] > curr
            // 搜索区间 [lo, hi)
            // tails[] 单调递增
            int lo = 0, hi = res;
            int mid;
            while (lo < hi) {
                mid = lo + ((hi-lo) >> 1); // 注意位运算优先级低于+-
                if (tails[mid] == curr) {
                    lo = mid + 1;
                } else if (tails[mid] < curr) {
                    lo = mid + 1;
                } else { // tails[mid] > hi
                    hi = mid;
                }
            }// lo 就是要找的边界
            tails[lo] = curr;
        }
    }
    
    return res;
}

Dilworth定理

笔者实在看不懂,这里列出参考,直接给出结论

子序列的数量和子序列的长度存在对应关系

  • 最长不升(>=)子序列的长度 = 最小上升(<)子序列的个数
  • 最长上升(<)子序列的长度 = 最小不升(>=)子序列的个数
  • 最长不降(<=)子序列的长度 = 最小下降(>)子序列的个数
  • 最长下降(>)子序列的长度 = 最小不降(<=) 子序列的个数

该定理用于求解 满足条件的子序列的个数

例题

P1020导弹拦截-洛谷

导弹拦截-牛客

子数组(子串)的常见问题与解法

遇到的有

  1. 两个数组的最长公共部分

    718. 最长重复子数组

  2. 两个字符串的最长公共子串

动态规划

718. 最长重复子数组为例,寻找两个一维数组的最长公共子数组

输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是 [3,2,1] 。
[0,1,1,1,1]
[1,0,1,0,1]

2

不同于子序列的动态规划解法,子数组(子串)具备连续性,与前面的不连续则需要断开连续

分析如下:

对于子串 nums1[0...i] 和 nums[0...j]

判断

nums1[i] == nums2[j] :

  • 如果 nums1[i-1] == nums2[j-1] 则 基于这一 子串长度+1

  • 否则 子串长度为 1

nums1[i] != nums2[j] :

  • 子串长度为0

需要一个变量记录这一过程中的最大值

// 时间复杂度 O(n1*n2)
// nums1 != null && nums1.length > 0
// nums2 != null && nums2.length > 0
public int findLength(int[] nums1, int[] nums2) {
    int n1 = nums1.length, n2 = nums2.length;
    int[][] dp = new int[n1][n2];
    
    // 记录最大公共长度
    int max = 0;
    
    // base case
    // 第 0 行
    for (int j = 0; j < n2; j++) {
        dp[0][j] = nums1[0] == nums2[j]? 1: 0;
        max = Math.max(max, dp[0][j]);
    }
        
    // 第 0 列
    for (int i = 1; i < n1; i++) {
        dp[i][0] = nums1[i] == nums2[0]? 1: 0;
        max = Math.max(max, dp[i][0]);
    }
    
    // dp
    for (int i = 1; i < n1; i++) {
        for (int j = 1; j < n2; j++) {
            if (nums1[i] == nums2[j]) {
                dp[i][j] = dp[i-1][j-1] > 0? dp[i-1][j-1] + 1: 1;
            }// else dp[i][j] = 0
            
            max = Math.max(max, dp[i][j]);
        }
    }
    return max;
}
posted @ 2022-10-17 15:19  jentreywang  阅读(305)  评论(0编辑  收藏  举报