Longest Increasing Subsequence:From n's square to nlogn

Algo demo with Python

Posted by viewsetting on November 2, 2018

Definition

If a sequence is given as $a_1a_2…a_i….a_{n-1}a_n$ , then its Longest Increasing Subsequence is the longest seq whose elements are all in increasing order, every element should be greater (or equal) than its predecessor.

$N^2$ Implement

Thinking about a DAG(Directed Acyclic Graph): if the element $a_i$ is considered now, which one would be the next in an increasing sequence started from $a_i$? Well, that would be $a_j$ for each $j$ larger than $i$ ,so is $aj$ , larger (or equal ) than $a_i$. If we give each $ai$ a node that store $len_i$ for the LIS ended by itself, then each $a_j$ that satisfied the restriction before, node of $a_j$ should be refreshed to a new value: $len_i +1$ . If we iterate that procedure, the LIS problem is solved.

def longest_increasing_subsequence(lst):
    aid=[]
    lst_in=[]
    max_num=1
    max_pos=0
    for i in range (0,len(lst) ):
      aid.append(1)
      lst_in.append([lst[i]])
        
    print("lst_in default:",lst_in)
    for idx in range (0,len(lst)) :
        for nxt in range (idx+1,len(lst)):
           if lst[idx]<=lst[nxt] and aid[idx]+1>aid[nxt] :
                aid[nxt]=aid[idx]+1
                lst_in[nxt]=list(lst_in[idx])   #without list() stuff gone wild!   
                lst_in[nxt].append(lst[nxt])
                if aid[nxt]>max_num : 
                    max_num=aid[nxt]
                    max_pos=nxt
 
    print('array of aid : ',lst_in)
    return max_num,lst_in[max_pos]

lst=[5,1,45,2,5,7,8,1]
print('lst :',lst)
print('len_of_LIS & LIS :',longest_increasing_subsequence(lst))

$N\log N$ Implement

Considering a case that two LIS that share the same length, which should be more potential to be the LIS of the whole seq? U can conclude that it must be the one which ended with the smaller number. Because in this situation,it does not matter if a number with smaller value appears later than a previous larger one. Only value of the node is the key to construct an increasing sequence. Therefore we can use another list or array $dp_i$, which means the end number of the LIS whose size is $i$ . We can simply scan the original seq $a$,then if $a_i$ is larger than the latest largest $dp_k$ ,update $dp_{k+1}$ as $a_i$ . If not, use binary search to find a $dp_J$ that is the smallest one larger than $a_i$, replace it with $a_i$.

import bisect
def bin_LIS(lst):
    dp=[0XFFFFFFFFF]*(len(lst)+1)
    node=[[]*len(lst)]*len(lst)
    leng=0
    dp[0]=lst[0]
    node[0]=[lst[0]]
    print ('iteration: ',0,': ',node)
    for i in range (1,len(lst)):
        if(dp[leng]<lst[i]): 
            dp[leng+1]=lst[i]
            leng=1+leng
            node[leng]=list(node[leng-1])
            node[leng].append(lst[i])
        else:
            pt=bisect.bisect_left(dp,lst[i])
            dp[pt]=lst[i]
            node[pt][len(node[pt])-1]=lst[i]
        print ('iteration: ',i,': ',node)
    return dp,leng+1,node

lst=[5,1,4,2,5,0,7,2,10,7,8,1,11]
bin_LIS(lst)