logo

Longest Increasing Subsequence (LIS)

Givet en array arr[] av storlek N , är uppgiften att hitta längden på den längsta ökande delsekvensen (LIS), dvs. den längsta möjliga delsekvensen där elementen i delsekvensen sorteras i ökande ordning.

LIS

Längst ökande efterföljd



Exempel:

Inmatning: arr[] = {3, 10, 2, 1, 20}
Produktion: 3
Förklaring: Den längsta ökande följdsekvensen är 3, 10, 20

Inmatning: arr[] = {50, 3, 10, 7, 40, 80}
Produktion: 4
Förklaring: Den längsta ökande undersekvensen är {3, 7, 40, 80}



d flip flop

Inmatning: arr[] = {30, 20, 10}
Produktion: 1
Förklaring: De längst ökande undersekvenserna är {30}, {20} och (10)

Inmatning: arr[] = {10, 20, 35, 80}
Produktion: 4
Förklaring: Hela arrayen är sorterad

Längsta ökande sekvens med Rekursion :

Idén att gå igenom inmatningsmatrisen från vänster till höger och hitta längden på den längsta ökande subsekvensen (LIS) som slutar med varje element arr[i]. Låt längden som hittas för arr[i] vara L[i]. I slutet returnerar vi maximalt av alla L[i]-värden. Nu uppstår frågan, hur beräknar vi L[i]? För detta använder vi rekursion, vi betraktar alla mindre element till vänster om arr[i], beräknar rekursivt LIS-värde för alla mindre element till vänster, tar maximalt av alla och lägger till 1 till det. Om det inte finns något mindre element till vänster om ett element returnerar vi 1.



Låta L(i) vara längden på LIS som slutar på index i så att arr[i] är det sista elementet i LIS. Sedan kan L(i) skrivas rekursivt som:

byta namn på katalogen i linux
  • L(i) = 1 + max(L(j) ) där 0
  • L(i) = 1, om inget sådant j existerar.

Formellt, längden på LIS som slutar på index i , är 1 större än maxlängden för alla LIS som slutar på något index j Så att arr[j] var j .

Vi kan se att ovanstående återkommande relation följer optimal understruktur fast egendom.

Illustration:

dfs algoritm

Följ illustrationen nedan för en bättre förståelse:

Tänk på arr[] = {3, 10, 2, 11}

L(i): Betecknar LIS för subarray som slutar på position 'i'

Rekursionsträd

Rekursionsträd

Följ stegen nedan för att implementera idén ovan:

  • Skapa en rekursiv funktion.
  • För varje rekursivt samtal, Iterera från i = 1 till den aktuella positionen och gör följande:
    • Hitta den möjliga längden på den längst ökande delsekvensen som slutar på den aktuella positionen om den föregående sekvensen slutade på i .
    • Uppdatera högsta möjliga längd därefter.
  • Upprepa detta för alla index och hitta svaret

Nedan följer implementeringen av det rekursiva tillvägagångssättet:

giltiga identifierare i java
C++
// A Naive C++ recursive implementation // of LIS problem #include  using namespace std; // 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;  } // Jämför max_ending_here med // totala max. Och uppdatera // totala max vid behov om (*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]);  // Function call  cout << 'Length of lis is ' << lis(arr, n);  return 0; }>
C
// A Naive 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;  } // Jämför max_ending_here med den totala // max. Och uppdatera det totala maxvärdet om det behövs om (*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]);  // Function call  printf('Length of lis is %d', lis(arr, n));  return 0; }>
Java
// A Naive Java Program for LIS Implementation import java.io.*; import java.util.*; class LIS {  // Stores the LIS  static int max_ref;  // 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;  } // Jämför max_ending_here med det totala max. Och // uppdatera det totala maxvärdet om det behövs om (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;  // Function call  System.out.println('Length of lis is '  + lis(arr, n));  } } // This code is contributed by Rajat Mishra>
Pytonorm
# A naive Python implementation of LIS problem # Global variable to store the maximum global maximum # 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 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[i-1] is smaller than arr[n-1], and # max ending with arr[n-1] needs to be updated, # then update it for i in range(1, n): res = _lis(arr, i) if arr[i-1] < arr[n-1] and res+1>maxEndingHere: maxEndingHere = res + 1 # Jämför maxEndingHere med totalt max. Och # uppdatera det totala maxvärdet om det behövs max = max(maximum, maxEndingHere) return maxEndingHere def lis(arr): # För att tillåta åtkomst av global variabel global maximi # Längd på arr n = len(arr) # Maximal variabel håller resultatet maximum = 1 # Funktionen _lis() lagrar sitt resultat i maximum _lis(arr, n) returnerar maximum # Drivrutinsprogram för att testa ovanstående funktion om __name__ == '__main__': arr = [10, 22, 9, 33 , 21, 50, 41, 60] n = len(arr) # Funktionsanrop print('Längd på lis är', lis(arr)) # Denna kod har bidragit från NIKHIL KUMAR SINGH>
C#
using System; // A Naive C# Program for LIS Implementation class LIS {  // Stores the LIS  static int max_ref;  // 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;  } // Jämför max_ending_here med det totala max // och uppdatera det totala max vid behov om (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;  // Function call  Console.Write('Length of lis is ' + lis(arr, n)  + '
');  } }>
Javascript
>

Produktion
Length of lis is 5>

Tidskomplexitet: O(2n) Tidskomplexiteten för detta rekursiva tillvägagångssätt är exponentiell eftersom det finns ett fall av överlappande delproblem som förklaras i det rekursiva träddiagrammet ovan.
Hjälputrymme: O(1). Inget externt utrymme används för att lagra värden förutom det interna stackutrymmet.

Längst ökande sekvens med hjälp av Memoisering :

Om vi ​​uppmärksammas noggrant kan vi se att ovanstående rekursiva lösning också följer överlappande delproblem egenskap, dvs samma understruktur löses om och om igen i olika rekursionsanropsvägar. Vi kan undvika detta genom att använda memoiseringsmetoden.

Vi kan se att varje tillstånd kan identifieras unikt med två parametrar:

  • Aktuellt index (betecknar det sista indexet i LIS) och
  • Föregående index (betecknar slutindexet för föregående LIS bakom vilket arr[i] håller på att sammanfogas).

Nedan är implementeringen av ovanstående tillvägagångssätt.

inorderpassering
C++
// C++ code of memoization approach for LIS #include  using namespace std; // 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 // 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 f(int idx, int prev_idx, int n, int a[],  vector>& dp) { if (idx == n) { return 0;  } if (dp[idx][prev_idx + 1] != -1) { return dp[idx][prev_idx + 1];  } int notTake = 0 + f(idx + 1, prev_idx, n, a, dp);  int take = INT_MIN;  if (prev_idx == -1 || a[idx]> a[prev_idx]) { take = 1 + f(idx + 1, idx, n, a, dp);  } returnera dp[idx][prev_idx + 1] = max(take, notTake); } // Funktion för att hitta längden på // längst ökande undersekvens int längstaSubsequence(int n, int a[]) { vektor> dp(n + 1, vektor (n + 1, -1));  returnera f(0, -1, n, a, dp); } // Drivrutinsprogram för att testa ovan funktion int main() { int a[] = { 3, 10, 2, 1, 20 };  int n = storlek på(a) / storlek på(a[0]);  // Funktionsanrop cout<< 'Length of lis is ' << longestSubsequence(n, a);  return 0; }>
Java
// A Memoization Java Program for LIS Implementation import java.lang.*; import java.util.Arrays; class 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 f(int idx, int prev_idx, int n, int a[],  int[][] dp)  {  if (idx == n) {  return 0;  }  if (dp[idx][prev_idx + 1] != -1) {  return dp[idx][prev_idx + 1];  }  int notTake = 0 + f(idx + 1, prev_idx, n, a, dp);  int take = Integer.MIN_VALUE;  if (prev_idx == -1 || a[idx]>a[prev_idx]) { take = 1 + f(idx + 1, idx, n, a, dp);  } return dp[idx][prev_idx + 1] = Math.max(take, notTake);  } // Omslagsfunktionen för _lis() statisk int lis(int arr[], int n) { // Funktionen _lis() lagrar sitt resultat i max int dp[][] = new int[n + 1][ n + 1];  for (int row[] : dp) Arrays.fill(row, -1);  returnera f(0, -1, n, arr, dp);  } // Drivrutinsprogram för att testa ovanstående funktioner public static void main(String args[]) { int a[] = { 3, 10, 2, 1, 20 };  int n = a.längd;  // Funktionsanrop System.out.println('Längden på lis är ' + lis(a, n));  } } // Denna kod är bidragit av Sanskar.>
Pytonorm
# A Naive Python recursive implementation # of LIS problem import sys # 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 def f(idx, prev_idx, n, a, dp): if (idx == n): return 0 if (dp[idx][prev_idx + 1] != -1): return dp[idx][prev_idx + 1] notTake = 0 + f(idx + 1, prev_idx, n, a, dp) take = -sys.maxsize - 1 if (prev_idx == -1 or a[idx]>a[prev_idx]): take = 1 + f(idx + 1, idx, n, a, dp) dp[idx][prev_idx + 1] = max(ta, notTake) returnera dp[idx][prev_idx + 1] # Funktion för att hitta längden på längst ökande # efterföljd. def längstaSubsequence(n, a): dp = [[-1 för i i intervallet(n + 1)]för j i intervallet(n + 1)] returnera f(0, -1, n, a, dp) # Drivrutin program för att testa ovanstående funktion om __name__ == '__main__': a = [3, 10, 2, 1, 20] n = len(a) # Funktionsanrop print('Längd på lis är', längstaSubsequence( n, a)) # Denna kod är bidragit av shinjanpatra>
C#
// C# approach to implementation the memoization approach using System; class GFG {  // 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  public static int INT_MIN = -2147483648;  public static int f(int idx, int prev_idx, int n,  int[] a, int[, ] dp)  {  if (idx == n) {  return 0;  }  if (dp[idx, prev_idx + 1] != -1) {  return dp[idx, prev_idx + 1];  }  int notTake = 0 + f(idx + 1, prev_idx, n, a, dp);  int take = INT_MIN;  if (prev_idx == -1 || a[idx]>a[prev_idx]) { take = 1 + f(idx + 1, idx, n, a, dp);  } return dp[idx, prev_idx + 1] = Math.Max(take, notTake);  } // Funktion för att hitta längden på längst ökande // underföljd.  public static int longestSubsequence(int n, int[] a) { int[, ] dp = new int[n + 1, n + 1];  för (int i = 0; i< n + 1; i++) {  for (int j = 0; j < n + 1; j++) {  dp[i, j] = -1;  }  }  return f(0, -1, n, a, dp);  }  // Driver code  static void Main()  {  int[] a = { 3, 10, 2, 1, 20 };  int n = a.Length;  Console.WriteLine('Length of lis is '  + longestSubsequence(n, a));  } } // The code is contributed by Nidhi goel.>
Javascript
/* A Naive Javascript recursive 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  */  function f(idx, prev_idx, n, a, dp) {  if (idx == n) {  return 0;  }  if (dp[idx][prev_idx + 1] != -1) {  return dp[idx][prev_idx + 1];  }  var notTake = 0 + f(idx + 1, prev_idx, n, a, dp);  var take = Number.MIN_VALUE;  if (prev_idx == -1 || a[idx]>a[prev_idx]) { take = 1 + f(idx + 1, idx, n, a, dp);  } return (dp[idx][prev_idx + 1] = Math.max(take, notTake));  } // Funktion för att hitta längden på längst ökande // underföljd.  function longestSubsequence(n, a) { var dp = Array(n + 1) .fill() .map(() => Array(n + 1).fill(-1));  returnera f(0, -1, n, a, dp);  } /* Drivrutinsprogram för att testa ovanstående funktion */ var a = [3, 10, 2, 1, 20];  var n = 5;  console.log('Längd på lis är ' + längstaSubsequence(n, a));    // Den här koden är bidragit av satwiksuman.>

Produktion
Length of lis is 3>

Tidskomplexitet: 2)
Hjälputrymme: 2)

Längst ökande sekvens med hjälp av Dynamisk programmering :

På grund av optimal understruktur och överlappande delproblemegenskap kan vi även använda dynamisk programmering för att lösa problemet. Istället för memoisering kan vi använda den kapslade slingan för att implementera den rekursiva relationen.

Den yttre slingan kommer att löpa från i = 1 till N och den inre slingan kommer att löpa från j = 0 till i och använda återfallsrelationen för att lösa problemet.

Nedan är implementeringen av ovanstående tillvägagångssätt:

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]);  // Function call  printf('Length of lis is %d
', lis(arr, n));  return 0; }>
Java
// Dynamic Programming Java implementation // of LIS problem import java.lang.*; 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;  }  // Driver code  public static void main(String args[])  {  int arr[] = { 10, 22, 9, 33, 21, 50, 41, 60 };  int n = arr.length;  // Function call  System.out.println('Length of lis is '  + lis(arr, n));  } } // This code is contributed by Rajat Mishra>
Pytonorm
# 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] och 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 # Driver program to test above function if __name__ == '__main__': 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;  }  // Driver code  public static void Main()  {  int[] arr = { 10, 22, 9, 33, 21, 50, 41, 60 };  int n = arr.Length;  // Function call  Console.WriteLine('Length of lis is '  + lis(arr, n));  } } // This code is contributed by Ryuga>
Javascript
>

Produktion
Length of lis is 5>

Tidskomplexitet: 2) Som en kapslad loop används.
Hjälputrymme: O(N) Användning av valfri array för att lagra LIS-värden vid varje index.

Notera: Tidskomplexiteten för ovanstående dynamisk programmeringslösning (DP) är O(n^2), men det finns en O(N* logN) lösning för LIS-problemet. Vi har inte diskuterat O(N log N)-lösningen här.
Hänvisa: Längst ökande sekvensstorlek (N * logN) för det nämnda tillvägagångssättet.