Longest Increasing Subsequence (LIS) 的java实现

时间:2022-04-04 19:28:48

最近用到最长递增子序列 Longest Increasing Subsequence (LIS)。这其实是个比较基础的题目,奈何本人才疏学浅,看了半天也理解不了,尤其是那个O(n lg n)的算法。这里附上wiki上的算法,如果能看懂,就不必再往下看了。

 

----------copy from wiki----------

The algorithm outlined below solves the longest increasing subsequence problem efficiently, using only arrays and binary searching. It processes the sequence elements in order, maintaining the longest increasing subsequence found so far. Denote the sequence values as X[1], X[2], etc. Then, after processing X[i], the algorithm will have stored values in two arrays:

M[j] — stores the position k of the smallest value X[k] such that there is an increasing subsequence of length j ending at X[k] on the range ki (note we have jki here).

P[k] — stores the position of the predecessor of X[k] in the longest increasing subsequence ending at X[k].

In addition the algorithm stores a variable L representing the length of the longest increasing subsequence found so far.

Note that, at any point in the algorithm, the sequence

X[M[1]], X[M[2]], ..., X[M[L]]

is nondecreasing. For, if there is an increasing subsequence of length i ending at X[M[i]], then there is also a subsequence of length i-1 ending at a smaller value: namely the one ending at P[M[i]]. Thus, we may do binary searches in this sequence in logarithmic time.

The algorithm, then, proceeds as follows.

 L = 0
 for i = 1, 2, ... n:
    binary search for the largest positive j ≤ L such that X[M[j]] < X[i] (or set j = 0 if no such value exists)
    P[i] = M[j]
    if j == L or X[i] < X[M[j+1]]:
       M[j+1] = i
       L = max(L, j+1)

The result of this is the length of the longest sequence in L. The actual longest sequence can be found by backtracking through the P array: the last item of the longest sequence is in X[M[L]], the second-to-last item is in X[P[M[L]]], etc. Thus, the sequence has the form

..., X[P[P[M[L]]]], X[P[M[L]]], X[M[L]].

----------end copy from wiki----------

 

人家写的算法看不懂,搜到的code都是c或者c++的,那叫一个郁闷。

后来终于想通了,关键是,假设已经找到几个长度为l的LIS,那么只需要记下尾数最小的LIS,因为这样的LIS才最有可能被后面的数接上创造新的LIS。例如,已经找到两个长度为2的LIS,一个是1 2,另一个是7 8。假设下一个数是3,那么就能接到1 2后面制造一个新的LIS,而不能接到7 8后面。所以7 8根本就不需要记下来了。

下面附上自己写的code,思想和上面的算法是一样的,上面的数组M就是我的EndingIndex,上面的数组P就是我的PreviousIndex,但是我的写法比上面容易理解的多,而且每处理一个数就把结果打印出来,可以看到每个数组的变化。代码copy下来直接能run,希望能抛砖引玉,还请大家指正。

 

----------myLIS----------

public class LIS {
 public static void main(String[] args){
  int[] input = new int[9];
  // input[0] is not used
  input[1] = 5;
  input[2] = 4;
  input[3] = 8;
  input[4] = 1;
  input[5] = 2;
  input[6] = 6;
  input[7] = 7;
  input[8] = 3;
  // array: 5 4 8 1 2 6 7 3. LIS is 1 2 6 7 with length 4
  System.out.println("LIS length: "+lis(input));
 }
 
 // Longest Increasing Sequence
 private static int lis(int[] x){
  int n = x.length-1;                             // number of elements
  int length;                                         // the length of the LIS found so far
  int[] EndingIndex = new int[n+1];    // the ending index of the LIS of length i
  int[] PreviousIndex = new int[n+1]; // the previous index of the LIS ending at X[i]

 

  // initialization: the first element is the first LIS of length 1, with no previous index
  length = 1;
  EndingIndex[1] = 1;
  PreviousIndex[1] = 0;
  System.out.println("Initialized");
  printInfo(length, EndingIndex, PreviousIndex);
  
  // iteration
  for(int i = 2; i <= n; i++){
   System.out.println("Processing element "+i+":"+x[i]);
        
   int l = length;
   // search in EndingIndex for the largest index l (longest LIS) that X[i] > X[EndingIndex[l]]
   // so X[i] could be added to the end of this LIS to make a new LIS ending with X[i]
   for(; l > 0; l--){
    if(x[i] > x[EndingIndex[l]]){
     PreviousIndex[i] = EndingIndex[l];
     break;
    }
   }
   if(l == 0)
    PreviousIndex[i] = 0; // this element will be a new LIS of length 1    
   // System.out.println("The l value found is "+l);

   int currentLength = l+1; // the length of LIS ending with X[i]
         if(currentLength > length){ // a new LIS found!!!
          length = currentLength;
             EndingIndex[currentLength] = i;
         } else if(x[i] < x[EndingIndex[currentLength]])
             EndingIndex[currentLength] = i;

         printInfo(length, EndingIndex, PreviousIndex);
  }
  
  // tracing back the result
  int[] result = new int[length+1];
  int r = length;
  int index = EndingIndex[length];
  do{
         result[r] = x[index];
         r--;
         index = PreviousIndex[index];
  }while(r>0);
  System.out.println("The final LIS: ");
  for(int i = 1; i<=length; i++)
   System.out.print(result[i]+" ");
  System.out.print("/n");

  return length;
 }
 private static void printInfo(int length, int[] EndingIndex, int[] PreviousIndex) {
        System.out.println("Current LIS length: "+length);
        System.out.println("Current EndingIndex: ");
        for(int j = 1; j<EndingIndex.length; j++) {
         if(EndingIndex[j]==0) break;
         System.out.print(EndingIndex[j]+" ");
        }
        System.out.println("/nCurrent PreviousIndex: ");
        for(int j = 1; j<PreviousIndex.length; j++)
         System.out.print(PreviousIndex[j]+" ");
        System.out.println("/n");
 }
}
----------end myLIS----------