Will never forget my first day on the job where a customer requested a feature to find the longest increasing sub-sequence. A totally valid way to test someones competency as a developer
It is important to understand the idea of subsequences, use memory efficiently and understand complexities of exponentially increasing subroutines. A good Programmer is not the one who solves it the first time seeing it. A good Programmer is the one who understands all the dimensions of the problem and Learn as much as he can about underlying intuitions. and remember when Tony Hoare first made Quick Sort He didn't say I am not going to face a situation where I sort a customer's Needs. His attitude as any computer scientist when making a helpful research or an idea was If I am able to think How to sort and even invent a sorting algorithm then I am going to be able to satisfy my Customer needs who were Future Generations that used Quick sort in every Customer related Situation . Good bye.
Please keep that tone and speed of voice. It really helps to "understand" the solution. All of us are here to "understand" the solution not just for a solution. You will do great my dude.
You can change the playback speed on RU-vid to make it go faster or slower. In case you find a problem that some other youtuber has solved, that Neetcode hasnt solved yet, use that feature.
"I really doubt your interviewer is going to expect you to get this without a hint; if they do I'd just walk out of the room" Probably worked great up until the layoffs 😥
You're the best at actually explaining the reasoning behind these problems and all your views are so well deserved. I'm absolutely dreadful at these despite being a senior dev but you don't know how much your channel has helped, thank you!!!
This explanation went down so smooth -your voice was very easy to follow and your diagrams weren't sloppy and not complex to understand -this might be the cleanest solution I have seen for this problem for beginners to study!
Thank you for the clear explanation! For those wondering why going backwards in dynamic programming, you can actually solve this in forward dynamic programming, start from the beginning, too.
Yeah, I agree that being expected to find the O(nlogn) solution is walkout tier. I came damn close to figuring it out: use an ordered set to keep track of the elements you've inserted so far so that you can easily find the greatest value that's smaller than or equal to your current one. From here, assuming nums[i] is not your maximum thus far, there are two ways, and figuring out either of them is easily upper Hard level: either you actually delete the value you've found (the fact that this works because it means you can just return the size of your set at the end is incredibly unintuitive), or you mess with the way you store everything in the set so that you can still retrieve the index of the value corresponding to your found value (which is awful to implement).
Why would deleting the value you found work? If you have input array [2,3,1,5,6] you cannot delete the value found at 5 when you see the 1, because then you cannot use it for the actual longest subsequence of 2,3,5,6 Tbh the approach of using a heap to store the subsequence length cache is quite reasonable imo… it’s annoying to implement but quite straightforward as an obvious improvement. If you know how to solve heap problems, which are based on the premise that a heap is a priority queue, why not just apply that here when you are searching for the largest element?
That was so simple and an epic explanation of how you can start thinking about approaching this problem. Being a beginner at dp, your videos help me understand how to start approaching a problem :) Thankyou!
Not sure why, but this one felt much easier than the prior 3-4 problems in the Neetcode Dynamic Programming learning path. Got it on my first try, and solved it exactly the way Neet did. Just goes to show the value of the Neetcode Roadmap, and how the patterns start to solidify in your mind over time.
Optimal Approach O(nlogn) bisect_left is a python function which gives the lower bound of the element in O(logn) time. bisect_left(array, element, start, end) class Solution: def lengthOfLIS(self, arr: List[int]) -> int: subs = [arr[0]] for i in range(1,len(arr)): if arr[i] > subs[-1]: subs.append(arr[i]) else: subs[bisect_left(subs, arr[i], 0, len(subs))] = arr[i] return len(subs)
@@davidespinosa1910 Yeah, the problem asks for the longest increasing subsequence. So, this will still give you the correct length just not the subsequence itself
noticing the subproblem 'is there an increasing subsequence with length m' is O(n), and m is between 1 and n, we can use binary search and get overall complexity O(nlogn). But it is way neater with DP
Great demostration starting from brute force, work way up to memoization and then leads naturally to dp!!! So Nice and easy it becomes with your approach! 1 question though: Why work from end backwords? How did you get the instinct?Could you please share your thougghts?
I'm used to working from the end backwards because it's similar to the recursive approach. But it's possible and maybe more intuitive to start at the beginning. Whatever makes sense for you is the best approach I think.
Example: [1,4,2,3], While computing longest common subsequence starting from index 0, the number at index 2, will be used. While computing longest common subsequence starting from index 1, the number at index 2 will be used. What i mean by "Will be used" is i am asking a question: What is the longest common subsequence starting from index 2. So if we had started computing longest common subsquence from backwards, then when we compute longest common subsquence for index 0 and 1, we already have the answer for longest common subsequence starting from index 2 stored.
Superb Explanation.Anyone having doubt in leetcode can refer this channel.Excellent video bro.I was struggling for this problem you made it clear.Thank you.
Thanks for the explanation @neetcode , code in java : class Solution { public int lengthOfLIS(int[] nums) { int dp[] = new int[nums.length]; Arrays.fill(dp, 1); for (int i = nums.length - 1; i >= 0; i--) { for (int j = i-1; j >=0; j--) { if (nums[i] > nums[j]) { dp[j] = Math.max(dp[j], 1 + dp[i]); } } } int maxLIS = 0; for (int i = 0; i < nums.length; i++) { maxLIS = Math.max(maxLIS, dp[i]); } return maxLIS; } }
Thanks for such a great explanation, I searched for it so many places, but I didn't find anything more than the formula. This video should have more likes.
Why do you not cover the best solution: dynamic programming with binary search? That's the one I'm looking for because you need it to solve later problems like the Russian Doll Envelopes.
since we have already assigned LIS with value 1 for the length of nums, in the first for loop, we can start from len(nums) - 2 instead of len(nums) -1.
No need to complicate it further by doing reverse looping, from 0 to n works just fine with the same function of max(lis[i], 1+lis[j]) for i=0 to n, j=0 to i if nums[j]
"I really doubt your interviewer is gonna expect you to get the O(n logn) solution without a hint. If they do, I would personally just walk out of the room." XDDDDD
11:34 I think using the name LIS[ ] is not a good choice, as you may think the final solution is LIS[0] this way. The strict definition of this lookup table, let's call it lookup[ ] is this: IF YOU TAKE THAT NUMBER nums[i] into the sequence, then what is the longest you can get. So lookup[i] IS IF YOU MUST INCLUDE nums[i] into that sequence. If you write LIS[i], it sounds like it is the max NO MATTER you include nums[i] or not, which is not the case. So that's why in the code that follows, the final result is not LIS[0], but max(LIS)
def lengthOfLIS2(self, nums: List[int]) -> int: L = len(nums) cache = {L: 0} def dfs(i): if i in cache: return cache[i] n = nums[i] result = 1 for j in range(i + 1, L): if n < nums[j]: result = max(dfs(j) + 1, result) cache[i] = result return result return dfs(0)
Great explanation as usual!! A repost(?) of the O(nlong(n)) solution, not that hard and really comes in handy for other problems :) class Solution: def lengthOfLIS(self, nums: List[int]) -> int: indices = [None for _ in range(len(nums)+1)] # Mapping of size to the last indice of the subsequence size = 0 def binary(elem, l, r): # a binary search is possible since the sizes are sorted by definition, even if the values in nums are not while l
It feels overly complicated to start from the end of `nums` when we can start from the beginning. The implementation below was *easier* to understand conceptually and ran faster. ``` class Solution: def lengthOfLIS(self, nums: List[int]) -> int: LIS = [1] * len(nums) for i in range(1, len(nums)): sub_problems = [LIS[k] for k in range(i) if nums[k] < nums[i]] LIS[i] = 1 + max(sub_problems, default=0) return max(LIS) ```
leetcode editorial suggests improving time complexity with binary search class Solution: def lengthOfLIS(self, nums: List[int]) -> int: sub = [] for num in nums: i = bisect_left(sub, num) # If num is greater than any element in sub if i == len(sub): sub.append(num) # Otherwise, replace the first element in sub greater than or equal to num else: sub[i] = num return len(sub)
Great video, much appreciated. However, I didn't understand the logical jump at @10:40 that suggested we were "starting at 3". I would have preferred to see a solution that proceeded front-to-back, because it seemed to me that is what you were doing in the recursive solution.
When i feel its so hard to learn DSA problem and crack FAANG like companies my mind tells me "neetcode" and after seeing the video explanation i become calm and motivated to proceed further.
You are solving problems like God would solve, I attempted it and couldn't solve it in my first attempt though I knew what Dynamic programming is. Also, the way you explain choices and recursion is far the best way to start attacking problems like this.
O(nlogn) solution class Solution: def lis(self, A): res = [A[0]] n = len(A) for num in A[1:]: if num>res[-1]: res.append(num) else: res[bisect_left(res,num)] = num return len(res)
@Neetcode the O(N^2) solution now gives a TLE on Leetcode. Given the popularity of this problem, could you please make a follow-up video or respond to this comment on how to get to the O(N.logN) solution?
It will not in c++, but I guess you can reference to this video ru-vid.com/video/%D0%B2%D0%B8%D0%B4%D0%B5%D0%BE-22s1xxRvy28.html you can see the problem is the O(nlogn) solution is not sth you can figure out but more like applying an algorithm.
There is an alternative O(nlogn) (?) solution using the bisect module of python. I found this solution more intuitive and faster than the solution in this video, that's why I wanted to share here. For anyone who wants to debug/understand the algorithm, just uncomment the print line and check the output. sub = [] for num in nums: i = bisect_left(sub, num) # print(f'i: {i}, num: {num}, sub: {sub}') # If num is greater than any element in sub if i == len(sub): sub.append(num) # Otherwise, replace the first element in sub greater than or equal to num else: sub[i] = num return len(sub)