📜  最长递增子序列| DP-3

📅  最后修改于: 2021-04-24 04:27:33             🧑  作者: Mango

我们已经讨论了重叠子问题和最佳子结构属性。
现在,让我们讨论最长增长子序列(LIS)问题,作为可以使用动态编程解决的示例问题。
最长增长子序列(LIS)问题是找到给定序列的最长子序列的长度,以使子序列的所有元素都按升序排序。例如,{10、22、9、33、21、50、41、60、80}的LIS长度为6,LIS为{10、22、33、50、60、80}。

最长增长子序列

例子:

Input: arr[] = {3, 10, 2, 1, 20}
Output: Length of LIS = 3
The longest increasing subsequence is 3, 10, 20

Input: arr[] = {3, 2}
Output: Length of LIS = 1
The longest increasing subsequences are {3} and {2}

Input: arr[] = {50, 3, 10, 7, 40, 80}
Output: Length of LIS = 4
The longest increasing subsequence is {3, 7, 40, 80}

方法1递归。
最佳子结构:设arr [0..n-1]为输入数组,L(i)为以索引i结尾的LIS的长度,以使arr [i]为LIS的最后一个元素。
然后,L(i)可以递归写为:

L(i) = 1 + max( L(j) ) where 0 < j < i and arr[j] < arr[i]; or
L(i) = 1, if no such j exists.

为了找到给定数组的LIS,我们需要返回max(L(i)),其中0 形式上,以索引i结尾的最长增长子序列的长度将比以i之前的索引结尾的所有最长增长子序列的长度的最大值大1,其中arr [j] 因此,我们看到LIS问题满足最佳子结构属性,因为可以使用子问题的解决方案来解决主要问题。
下面给出的递归树将使方法更清晰:

Input  : arr[] = {3, 10, 2, 11}
f(i): Denotes LIS of subarray ending at index 'i'

(LIS(1)=1)

      f(4)  {f(4) = 1 + max(f(1), f(2), f(3))}
  /    |    \
f(1)  f(2)  f(3) {f(3) = 1, f(2) and f(1) are > f(3)}
       |      |  \
      f(1)  f(2)  f(1) {f(2) = 1 + max(f(1)}
              |
            f(1) {f(1) = 1}

下面是递归方法的实现:

C++
/* A Naive C/C++ recursive implementation
of LIS problem */
#include
#include
 
/* To make use of recursive calls, this
function must return two things:
1) Length of LIS ending with element arr[n-1].
    We use max_ending_here for this purpose
2) Overall maximum as the LIS may end with
    an element before arr[n-1] max_ref is
    used this purpose.
The value of LIS of full array of size n
is stored in *max_ref which is our final result
*/
int _lis( int arr[], int n, int *max_ref)
{
    /* Base case */
    if (n == 1)
        return 1;
 
    // 'max_ending_here' is length of LIS
    // ending with arr[n-1]
    int res, max_ending_here = 1;
 
    /* Recursively get all LIS ending with arr[0],
    arr[1] ... arr[n-2]. If arr[i-1] is smaller
    than arr[n-1], and max ending with arr[n-1]
    needs to be updated, then update it */
    for (int i = 1; i < n; i++)
    {
        res = _lis(arr, i, max_ref);
        if (arr[i-1] < arr[n-1] && res + 1 > max_ending_here)
            max_ending_here = res + 1;
    }
 
    // Compare max_ending_here with the overall
    // max. And update the overall max if needed
    if (*max_ref < max_ending_here)
    *max_ref = max_ending_here;
 
    // Return length of LIS ending with arr[n-1]
    return max_ending_here;
}
 
// The wrapper function for _lis()
int lis(int arr[], int n)
{
    // The max variable holds the result
    int max = 1;
 
    // The function _lis() stores its result in max
    _lis( arr, n, &max );
 
    // returns max
    return max;
}
 
/* Driver program to test above function */
int main()
{
    int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };
    int n = sizeof(arr)/sizeof(arr[0]);
    printf("Length of lis is %dn",
        lis( arr, n ));
    return 0;
}


Java
/* A Naive Java Program for LIS Implementation */
class LIS
{
   static int max_ref; // stores the LIS
 
   /* To make use of recursive calls, this function must return
   two things:
   1) Length of LIS ending with element arr[n-1]. We use
      max_ending_here for this purpose
   2) Overall maximum as the LIS may end with an element
      before arr[n-1] max_ref is used this purpose.
   The value of LIS of full array of size n is stored in
   *max_ref which is our final result */
   static int _lis(int arr[], int n)
   {
       // base case
       if (n == 1)
           return 1;
 
       // 'max_ending_here' is length of LIS ending with arr[n-1]
       int res, max_ending_here = 1;
 
        /* Recursively get all LIS ending with arr[0], arr[1] ...
           arr[n-2]. If   arr[i-1] is smaller than arr[n-1], and
           max ending with arr[n-1] needs to be updated, then
           update it */
        for (int i = 1; i < n; i++)
        {
            res = _lis(arr, i);
            if (arr[i-1] < arr[n-1] && res + 1 > max_ending_here)
                max_ending_here = res + 1;
        }
 
        // Compare max_ending_here with the overall max. And
        // update the overall max if needed
        if (max_ref < max_ending_here)
           max_ref = max_ending_here;
 
        // Return length of LIS ending with arr[n-1]
        return max_ending_here;
   }
 
    // The wrapper function for _lis()
    static int lis(int arr[], int n)
    {
        // The max variable holds the result
         max_ref = 1;
 
        // The function _lis() stores its result in max
        _lis( arr, n);
 
        // returns max
        return max_ref;
    }
 
    // driver program to test above functions
    public static void main(String args[])
    {
        int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };
        int n = arr.length;
        System.out.println("Length of lis is "
                           + lis(arr, n) + "\n");
    }
 }
/*This code is contributed by Rajat Mishra*/


Python
# A naive Python implementation of LIS problem
 
""" To make use of recursive calls, this function must return
 two things:
 1) Length of LIS ending with element arr[n-1]. We use
 max_ending_here for this purpose
 2) Overall maximum as the LIS may end with an element
 before arr[n-1] max_ref is used this purpose.
 The value of LIS of full array of size n is stored in
 *max_ref which is our final result """
 
# global variable to store the maximum
global maximum
 
def _lis(arr , n ):
 
    # to allow the access of global variable
    global maximum
 
    # Base Case
    if n == 1 :
        return 1
 
    # maxEndingHere is the length of LIS ending with arr[n-1]
    maxEndingHere = 1
 
    """Recursively get all LIS ending with arr[0], arr[1]..arr[n-2]
       IF arr[n-1] is maller than arr[n-1], and max ending with
       arr[n-1] needs to be updated, then update it"""
    for i in xrange(1, n):
        res = _lis(arr , i)
        if arr[i-1] < arr[n-1] and res+1 > maxEndingHere:
            maxEndingHere = res +1
 
    # Compare maxEndingHere with overall maximum. And
    # update the overall maximum if needed
    maximum = max(maximum , maxEndingHere)
 
    return maxEndingHere
 
def lis(arr):
 
    # to allow the access of global variable
    global maximum
 
    # lenght of arr
    n = len(arr)
 
    # maximum variable holds the result
    maximum = 1
 
    # The function _lis() stores its result in maximum
    _lis(arr , n)
 
    return maximum
 
# Driver program to test the above function
arr = [10 , 22 , 9 , 33 , 21 , 50 , 41 , 60]
n = len(arr)
print "Length of lis is ", lis(arr)
 
# This code is contributed by NIKHIL KUMAR SINGH


C#
using System;
 
/* A Naive C# Program for LIS Implementation */
class LIS
{
   static int max_ref; // stores the LIS
  
   /* To make use of recursive calls, this function must return
   two things:
   1) Length of LIS ending with element arr[n-1]. We use
      max_ending_here for this purpose
   2) Overall maximum as the LIS may end with an element
      before arr[n-1] max_ref is used this purpose.
   The value of LIS of full array of size n is stored in
   *max_ref which is our final result */
   static int _lis(int[] arr, int n)
   {
       // base case
       if (n == 1)
           return 1;
  
       // 'max_ending_here' is length of LIS ending with arr[n-1]
       int res, max_ending_here = 1;
  
        /* Recursively get all LIS ending with arr[0], arr[1] ...
           arr[n-2]. If   arr[i-1] is smaller than arr[n-1], and
           max ending with arr[n-1] needs to be updated, then
           update it */
        for (int i = 1; i < n; i++)
        {
            res = _lis(arr, i);
            if (arr[i-1] < arr[n-1] && res + 1 > max_ending_here)
                max_ending_here = res + 1;
        }
  
        // Compare max_ending_here with the overall max. And
        // update the overall max if needed
        if (max_ref < max_ending_here)
           max_ref = max_ending_here;
  
        // Return length of LIS ending with arr[n-1]
        return max_ending_here;
   }
  
    // The wrapper function for _lis()
    static int lis(int[] arr, int n)
    {
        // The max variable holds the result
         max_ref = 1;
  
        // The function _lis() stores its result in max
        _lis( arr, n);
  
        // returns max
        return max_ref;
    }
  
    // driver program to test above functions
    public static void Main()
    {
        int[] arr = { 10, 22, 9, 33, 21, 50, 41, 60 };
        int n = arr.Length;
        Console.Write("Length of lis is "
                           + lis(arr, n) + "\n");
    }
 }


Javascript


C++
/* Dynamic Programming C++ implementation
   of LIS problem */
#include
using namespace std;
   
/* lis() returns the length of the longest 
  increasing subsequence in arr[] of size n */
int lis( int arr[], int n )
{
    int lis[n];
  
    lis[0] = 1;  
 
    /* Compute optimized LIS values in
       bottom up manner */
    for (int i = 1; i < n; i++ )
    {
        lis[i] = 1;
        for (int j = 0; j < i; j++ ) 
            if ( arr[i] > arr[j] && lis[i] < lis[j] + 1)
                lis[i] = lis[j] + 1;
    }
 
    // Return maximum value in lis[]
    return *max_element(lis, lis+n);
}
   
/* Driver program to test above function */
int main()
{
    int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };
    int n = sizeof(arr)/sizeof(arr[0]);
    printf("Length of lis is %d\n", lis( arr, n ) );
 
    return 0;
}


Java
/* Dynamic Programming Java implementation
   of LIS problem */
 
class LIS
{
    /* lis() returns the length of the longest
       increasing subsequence in arr[] of size n */
    static int lis(int arr[],int n)
    {
          int lis[] = new int[n];
          int i,j,max = 0;
 
          /* Initialize LIS values for all indexes */
           for ( i = 0; i < n; i++ )
              lis[i] = 1;
 
           /* Compute optimized LIS values in
              bottom up manner */
           for ( i = 1; i < n; i++ )
              for ( j = 0; j < i; j++ )
                         if ( arr[i] > arr[j] &&
                                  lis[i] < lis[j] + 1)
                    lis[i] = lis[j] + 1;
 
           /* Pick maximum of all LIS values */
           for ( i = 0; i < n; i++ )
              if ( max < lis[i] )
                 max = lis[i];
 
            return max;
    }
 
    public static void main(String args[])
    {
        int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };
            int n = arr.length;
            System.out.println("Length of lis is "
                              + lis( arr, n ) + "\n" );
    }
}
/*This code is contributed by Rajat Mishra*/


Python
# Dynamic programming Python implementation
# of LIS problem
 
# lis returns length of the longest
# increasing subsequence in arr of size n
def lis(arr):
    n = len(arr)
 
    # Declare the list (array) for LIS and
    # initialize LIS values for all indexes
    lis = [1]*n
 
    # Compute optimized LIS values in bottom up manner
    for i in range (1 , n):
        for j in range(0 , i):
            if arr[i] > arr[j] and lis[i]< lis[j] + 1 :
                lis[i] = lis[j]+1
 
    # Initialize maximum to 0 to get
    # the maximum of all LIS
    maximum = 0
 
    # Pick maximum of all LIS values
    for i in range(n):
        maximum = max(maximum , lis[i])
 
    return maximum
# end of lis function
 
# Driver program to test above function
arr = [10, 22, 9, 33, 21, 50, 41, 60]
print "Length of lis is", lis(arr)
# This code is contributed by Nikhil Kumar Singh


C#
/* Dynamic Programming C# implementation of LIS problem */
 
using System ;
class LIS
{
    /* lis() returns the length of the longest increasing
    subsequence in arr[] of size n */
    static int lis(int []arr,int n)
    {
        int []lis = new int[n];
        int i,j,max = 0;
 
        /* Initialize LIS values for all indexes */
        for ( i = 0; i < n; i++ )
            lis[i] = 1;
 
        /* Compute optimized LIS values in bottom up manner */
        for ( i = 1; i < n; i++ )
            for ( j = 0; j < i; j++ )
                        if ( arr[i] > arr[j] && lis[i] < lis[j] + 1)
                    lis[i] = lis[j] + 1;
 
        /* Pick maximum of all LIS values */
        for ( i = 0; i < n; i++ )
            if ( max < lis[i] )
                max = lis[i];
 
            return max;
    }
 
    public static void Main()
    {
        int []arr = { 10, 22, 9, 33, 21, 50, 41, 60 };
            int n = arr.Length;
            Console.WriteLine("Length of lis is " + lis( arr, n ) + "\n" );
    }
 
    // This code is contributed by Ryuga
}


输出:

Length of lis is 5

复杂度分析:

  • 时间复杂度:这种递归方法的时间复杂度是指数级的,因为存在子问题重叠的情况,如上面的递归树形图所示。
  • 辅助空间: O(1)。除内部堆栈空间外,没有用于存储值的外部空间。

方法2动态编程。
我们可以看到上述递归解决方案中有很多子问题可以一遍又一遍地解决。因此,此问题具有“重叠子结构”属性,可以通过使用“记忆化”或“制表”来避免相同子问题的重新计算。
方法的模拟将使事情变得清晰:

Input  : arr[] = {3, 10, 2, 11}
LIS[] = {1, 1, 1, 1} (initially)

逐级仿真:

  1. arr [2]> arr [1] {LIS [2] = max(LIS [2],LIS [1] +1)= 2}
  2. arr [3]
  3. arr [3]
  4. arr [4]> arr [1] {LIS [4] = max(LIS [4],LIS [1] +1)= 2}
  5. arr [4]> arr [2] {LIS [4] = max(LIS [4],LIS [2] +1)= 3}
  6. arr [4]> arr [3] {LIS [4] = max(LIS [4],LIS [3] +1)= 3}

我们可以通过使用列表来避免重新计算子问题,如下面的代码所示:
下面是上述方法的实现:

C++

/* Dynamic Programming C++ implementation
   of LIS problem */
#include
using namespace std;
   
/* lis() returns the length of the longest 
  increasing subsequence in arr[] of size n */
int lis( int arr[], int n )
{
    int lis[n];
  
    lis[0] = 1;  
 
    /* Compute optimized LIS values in
       bottom up manner */
    for (int i = 1; i < n; i++ )
    {
        lis[i] = 1;
        for (int j = 0; j < i; j++ ) 
            if ( arr[i] > arr[j] && lis[i] < lis[j] + 1)
                lis[i] = lis[j] + 1;
    }
 
    // Return maximum value in lis[]
    return *max_element(lis, lis+n);
}
   
/* Driver program to test above function */
int main()
{
    int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };
    int n = sizeof(arr)/sizeof(arr[0]);
    printf("Length of lis is %d\n", lis( arr, n ) );
 
    return 0;
}

Java

/* Dynamic Programming Java implementation
   of LIS problem */
 
class LIS
{
    /* lis() returns the length of the longest
       increasing subsequence in arr[] of size n */
    static int lis(int arr[],int n)
    {
          int lis[] = new int[n];
          int i,j,max = 0;
 
          /* Initialize LIS values for all indexes */
           for ( i = 0; i < n; i++ )
              lis[i] = 1;
 
           /* Compute optimized LIS values in
              bottom up manner */
           for ( i = 1; i < n; i++ )
              for ( j = 0; j < i; j++ )
                         if ( arr[i] > arr[j] &&
                                  lis[i] < lis[j] + 1)
                    lis[i] = lis[j] + 1;
 
           /* Pick maximum of all LIS values */
           for ( i = 0; i < n; i++ )
              if ( max < lis[i] )
                 max = lis[i];
 
            return max;
    }
 
    public static void main(String args[])
    {
        int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };
            int n = arr.length;
            System.out.println("Length of lis is "
                              + lis( arr, n ) + "\n" );
    }
}
/*This code is contributed by Rajat Mishra*/

Python

# Dynamic programming Python implementation
# of LIS problem
 
# lis returns length of the longest
# increasing subsequence in arr of size n
def lis(arr):
    n = len(arr)
 
    # Declare the list (array) for LIS and
    # initialize LIS values for all indexes
    lis = [1]*n
 
    # Compute optimized LIS values in bottom up manner
    for i in range (1 , n):
        for j in range(0 , i):
            if arr[i] > arr[j] and lis[i]< lis[j] + 1 :
                lis[i] = lis[j]+1
 
    # Initialize maximum to 0 to get
    # the maximum of all LIS
    maximum = 0
 
    # Pick maximum of all LIS values
    for i in range(n):
        maximum = max(maximum , lis[i])
 
    return maximum
# end of lis function
 
# Driver program to test above function
arr = [10, 22, 9, 33, 21, 50, 41, 60]
print "Length of lis is", lis(arr)
# This code is contributed by Nikhil Kumar Singh

C#

/* Dynamic Programming C# implementation of LIS problem */
 
using System ;
class LIS
{
    /* lis() returns the length of the longest increasing
    subsequence in arr[] of size n */
    static int lis(int []arr,int n)
    {
        int []lis = new int[n];
        int i,j,max = 0;
 
        /* Initialize LIS values for all indexes */
        for ( i = 0; i < n; i++ )
            lis[i] = 1;
 
        /* Compute optimized LIS values in bottom up manner */
        for ( i = 1; i < n; i++ )
            for ( j = 0; j < i; j++ )
                        if ( arr[i] > arr[j] && lis[i] < lis[j] + 1)
                    lis[i] = lis[j] + 1;
 
        /* Pick maximum of all LIS values */
        for ( i = 0; i < n; i++ )
            if ( max < lis[i] )
                max = lis[i];
 
            return max;
    }
 
    public static void Main()
    {
        int []arr = { 10, 22, 9, 33, 21, 50, 41, 60 };
            int n = arr.Length;
            Console.WriteLine("Length of lis is " + lis( arr, n ) + "\n" );
    }
 
    // This code is contributed by Ryuga
}

输出:

Length of lis is 5

复杂度分析:

  • 时间复杂度: O(n 2 )。
    由于使用了嵌套循环。
  • 辅助空间: O(n)。
    使用任何数组在每个索引处存储LIS值。

注意:上面的动态编程(DP)解决方案的时间复杂度为O(n ^ 2),并且对于LIS问题有O(N log N)解决方案。我们这里没有讨论O(N log N)解决方案,因为这篇文章的目的是用一个简单的例子来解释动态编程。有关O(N log N)解决方案,请参见以下帖子。
最长递增子序列大小(N log N)

  • 打印数组的LIS
  • 最近基于LIS的文章!