public class Solution {
    /**
     * retrun the longest increasing subsequence
     * @param arr int整型一维数组 the array
     * @return int整型一维数组
     */
    public int[] LIS (int[] arr) {
        // write code here
        //特判空情况,方便后面处理
        if (arr.length == 0) return new int[0];
        int n = arr.length;
        
        int[] ls = new int[n];    // ls 数组里面存放递增子序列
        int[] dp = new int[n];    // dp 数组里存放以元素i结尾的最大递增子序列长度
        
        ls[0] = arr[0];
        dp[0] = 1;
        int l = 1;    //ls递增序列的实际长度
        
        for (int i = 1; i < arr.length; i++) {
            if (ls[l - 1] < arr[i]) {
                ls[l++] = arr[i];    //直接加入序列
                dp[i] = l;    // dp[i] 对应的序列是整个ls
            } else {
                int j = 0, k = l - 1;
                int idx = 0;
                //找 ls 中第一个 >= arr[i]的(必定存在,因为保证 ls 的最后一个 >= arr[i])
                while (j <= k) {
                    int m = j + (k - j) / 2;
                    if (ls[m] >= arr[i]) {
                        idx = m;
                        k = m - 1;
                    } else j = m + 1;
                }
                ls[idx] = arr[i];    //找到位置后替换掉,注意是替换不是插入
                // ls 序列的总长不变,但是为了复用原序列一些 < arr[i] 的结果,选择二分把 arr[i] 替换到合适的位置
                //所以 dp[i] 对应的序列其实是 ls 的 [0,idx] 部分(要保证序列的最后是arr[i]),即长度为idx + 1
                dp[i] = idx + 1;
            }
        }
        
        //这里其实可以复用ls来填充的,但是用ans是为了说明求最后的子序列和ls无关
        //ls只是为了上面求dp的复杂度从O(n ^ 2)降低为O(n * logn)
        //这里的求法是倒着遍历,找到满足长度的dp就记录,然后更新。(即同样值的dp,选尽量靠右边的)
        int[] res = new int[l];
        for (int i = n - 1, j = l; i >= 0; i--) {
            if (dp[i] == j) res[--j] = arr[i];
        }
        
        return res;
    }
}