首页 > 其他分享 >子序列与子数组(子串)

子序列与子数组(子串)

时间:2022-10-17 15:36:45浏览次数:71  
标签:子串 nums int max 序列 tails 数组 与子 dp

子序列与子数组(子串)

区别

  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;
}

标签:子串,nums,int,max,序列,tails,数组,与子,dp
From: https://www.cnblogs.com/jentreywang/p/16799332.html

相关文章

  • 三 数组
    三数组​​三数组​​​​1数组的概述​​​​2一维数组的使用​​​​3多维数组的使用​​​​4数组算法​​​​5Arrays工具类的使用​​总结于​​尚硅谷视频​​......
  • 数组的扩展
    扩展运算符console.log(...[1,2,3])//123//你甚至可以在后面放置表达式constarr=[...(x>0?['a']:[]),'b',];//替代apply方法//ES5用法varargs=......
  • 数组输出26字母
    #include<string.h> #include<limits.h>#include<float.h>#include<stdio.h>#pragmawarning(disable :4996)usingnamespacestd;#defineLEN26intmain(){  ......
  • 数据结构—数组和链表
    数组数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间并在此空间存储元素就像是一排出租屋,从001到100每个房间都有固定编号通过编号就可以快速找到租房......
  • 消除游戏的格子的index和二维数组row-column的换算
       index = row x MaxColumn + col            // 一个5x4的矩阵的index            // 0,1,2,3,            //......
  • C语言:删除已经排序的整型数组中的重复值
    #include<stdio.h>//每找到一个重复的元素,则最末尾前移一位,去重范围缩小一位//找到重复元素后,此时数组下标之后的元素向前移一位main(){inta[]={1,1,1,1,2,2,......
  • Java如何将两个数组合并为一个数组呢?
    转自:​​http://www.java265.com/JavaJingYan/202204/16502899232926.html​​数组:   数组(Array)是有序的元素序列。若将有限个类型相同的变量的集合命名,那么这个名称......
  • 数组
    三、数组3.1数组理解数组属于引用数据类型的变量。数组的元素,既可以是基本数据类型,也可以是引用数据类型。创建数组对象会在内存中开辟一整块连续的空间。数组......
  • 数据结构-串、数组、广义表
    @目录XDOJ0224矩阵相乘XDOJ0250螺旋填数XDOJ0257公共子串XDOJ0287求矩阵中的马鞍点XDOJ0288求一个顺序串的next函数值XDOJ0296中心对称的字符串XDOJ0309矩阵加法运......
  • 1441. 用栈操作构建数组
    本题非常简单,一个简单的模拟题解题思路:如果两个相邻数字相差不为1,那么对两个数字的差值减1进行“Push”和“Pop”如果两个相邻数字相差不1,那么直接“Push”即可......