Python N Log N solution
\nclass Solution:\n    # Dynamic Programming with Binary Search\n    # Time complexity : O(nlogn). Sorting and binary search both take nlogn time.\n    # Space 
```\nclass Solution:\n    # Dynamic Programming with Binary Search\n    # Time complexity : O(nlogn). Sorting and binary search both take nlogn time.\n    # Space complexity : O(n). dp array of size n is used.\n    def maxEnvelopes(self, envelopes: List[List[int]]) -> int:\n        # For each envelope, sorted by envelope[0] first, so envelope[1] is the the longest\n        # increasing sequence(LIS) problem. When envelope[0] tie, we reverse sort by envelope[1]\n        # because bigger envelope[1] can\'t contain the previous one.\n        envelopes.sort(key=lambda e: (e[0], -e[1]))\n        # dp keeps some of the visited element in a sorted list, and its size is length Of LIS\n        # sofar. It always keeps the our best chance to build a LIS in the future.\n        dp = []\n        for envelope in envelopes:\n            i = bisect.bisect_left(dp, envelope[1])\n            if i == len(dp):\n                # If envelope[1] is the biggest, we should add it into the end of dp.\n                dp.append(envelope[1])\n            else:\n                # If envelope[1] is not the biggest, we should keep it in dp and replace the\n                # previous envelope[1] in this position. Because even if envelope[1] can\'t build\n                # longer LIS directly, it can help build a smaller dp, and we will have the best\n                # chance to build a LIS in the future. All elements before this position will be\n                # the best(smallest) LIS sor far. \n                dp[i] = envelope[1]\n        # dp doesn\'t keep LIS, and only keep the length Of LIS.\n        return len(dp)\n```
['Dynamic Programming', 'Binary Tree', 'Python']