Lexicographical Numbers - Leetcode 386 - Python

NeetCodeIO · Intermediate ·⚡ Algorithms & Data Structures ·1y ago

Key Takeaways

The video discusses Leetcode problem 386, Lexicographical Numbers, and provides a Python solution using recursive and iterative approaches. The solution involves traversing a tree of lexicographical numbers and using a stack data structure to optimize the recursive algorithm.

Full Transcript

hey everyone welcome back and let's write some more unat code today so today let's solve the problem lexor graphical numbers the idea is that we're given a range one through n which is defined by the only parameter that we're given which is n so the range always starts at one let's consider the example where we have something like 136 the idea is that we want the numbers to be sorted not by like the integer values like that would be 1 2 3 4 5 in increasing order but we want them to be sorted by like the digits the same way we would have some strings let's say a b c and then a c b this string would go after this one because there's a tie in the first position but when you compare the first differing characters B is less than C therefore this string is smaller it's going to go first if you had two strings that were of different lengths like this entire string matches that one the first differing character is this one this one is empty this one has a c so this one would be first AB would go and then we'd get a b c so the same idea is going to be applied to these numbers in other words the first digit is going to have the most precedence so think of it in terms of this we have this range of numbers we want first to get all the numbers that start with one there's not going to be any leading zeros but we want all the numbers that start with one after we get all of them then we want all the numbers that start with two and then all the numbers that start with three Etc Etc from this perspective we've gotten to a sub problem here we're at one now for the remaining digits that's what we're choosing here we're choosing the remaining digits so we could get 10 we could get 11 all the way up until 19 notice that the difference here the first choice we had decisions 1 through n the next choice here we have 10 decisions well I guess you could say 0 through n for the second digit we have those choices now as we go like I said this is technically smaller than all of its descendants because it has the matching prefix with all of them but all of its descendants have a greater lengths so clearly this one is going to go first and then all of its children and all of its children are going to go from left to right but there's a catch what about the children of the children what about 10 it has some children doesn't it we do the same thing add another digit and by doing that we're basically multiplying this by 10 we get 100 then here 101 all the way up until 109 so here the sub problem becomes get all the numbers that start with 10 and we know for sure that this guy and all of its descendants are going to go before its neighbor over here so we do this recursively that's the whole idea behind this problem now how do you know when to stop well eventually we're going to take this and multiply it by 10 let's say we get to a th000 clearly that's too big we know we can stop because it exceeded the max number we don't really have to go through those guys either and same thing like none of these are going to have children either but then we're going to come back up to 11 and then we're going to eventually do 12 and eventually we're going to get to 13 and it's going to have some children 130 all the way up until 139 well not quite we're actually going to end up stopping at 136 cuz that's the max number there's no point in going to 137 or greater so that's another reason for us to stop where we exactly reach the number itself and so basically this is a backtracking solution where we're doing DFS in a preorder fashion because every time we get to a value we're going to immediately add that to the result and then we're going to go here add it to the result then here add it to the result do all of these pop back up do this do all of its descendants etc etc and then we're going to pop back up to here and then pop back up here I guess but two while it will have some descendants it will go up until 20 but we won't get into to the hundred's place we won't have a 200 or anything like that and we'll kind of keep going we'll do the same thing for this node as well and I think we'll actually get it up until 9 and then the max number from there will be 99 I guess in this range the largest number lexor graphically is going to be 99 the problem with this solution obviously it does use some extra memory given that it's recursive but it's not a lot of memory like the height of this tree is going to actually be log base 10 of the max number because each time we go down a level we're multiplying by 10 so how many times can you multiply by 10 until you reach this number or how many times can you divide this number by 10 until you reach one those are both equivalent those can both be determined by the log function so that's the space complexity time complexity is linear as you can see each number is only going to be visited a single time so while we don't meet the constraints I will show you the solution that does in just a minute but first let's code up this one so what I'm going to do is declare the result and that's what we're going to return all the numbers in the form of a list and then I'm going to create our recursive function I'll call it DFS and we'll have a current value and we know that the base case is going to be when the current number exceeds n if it equals n we're not going to return because if it equals n we do still want to append it to the result so we'll do that here in pre-order fashion and then we're going to go to the descendants of the current number and that's not going to be a simple recursive call that's actually going to involve a loop we're going to say for I in range 10 cuz we want to go from 0 to 9 we're going to do this DFS on the current number multiply it by 10 and then add I if any of those exceed n the recursive base case will hit and then will return immediately anyway so while you could have like an if statement here you could also early Stop the Loop it doesn't really make that much of a difference if this is more readable written this way for you feel free to do that current uh multiplied by by 10 and then to the new current value just add I so that way we don't have to keep doing the multiplication by 10 each time the problem here is that we can't just easily call DFS starting from one because if we do that this recursive call is only going to give us all the numbers that start with a one but some numbers might start with a two or three or four how do we get those well you can't also start at zero either because the way this is written then we would have a leading zero in our result and actually I think we'd even get infinite recursion cuz we would try multiplying that by 10 it'll still be zero and then we'd add zero to it and then we'd probably get infinite recursion so the better thing to do here is a loop so for I in range the starting value is going to be from 1 through 9 so we write 1 through 10 in Python this is non-inclusive and then we do DFS starting from I there you go I believe this is the entire code let's just run it and it works and it's pretty efficient in terms of time complexity but the runtime suffers mainly because of the recursion like the overhead introduced by all these recursive calls and the space complexity definitely suffers as well so how exactly can we optimize this well the obvious way to take a recursive algorithm and make it iterative is to use a stack data structure well yeah that'll technically work but it's not going to reduce the memory because from 100 down here we'd still have to remember to pop back up to 10 and then from 10 we'd still have to remember to pop back up to one but do we really need to remember that is there a pattern that you notice from all of these to pop back up to the previous level and even if we were to have drawn it from here what's the pattern it looks like all we do is remove the last digit because from 10 we added the digit zero here we added the digit one here we added the digit there to reverse that we can just divide it by 10 integer division so that we round down but it's really that simple so conceptually now we know how we can get rid of the need for a stack and we can optimize the space to constant space but it's not quite that simple cuz with recursion sometimes you don't just pop back up once you continuously pop back up when would that occur well it definitely wouldn't occur from over here because when we pop back up to 10 then we're going to go to 11 and then 12 etc etc but on the last value in that level then we would want to pop back up multiple times let's consider the example where the n value is actually something like 100 or1 19.99 from here eventually we'd get down to$ 19.99 that's the last value so we know we can stop we divide by 10 to pop back up here just to make it more simple consider it from this side when we pop back up here on the last level let's say from 109 we pop back up to 10 we don't want to revisit the same value we want to go to the next value so what we would do after popping up is we'd add one to it cuz we're doing pre-order traversal we don't want to add this guy twice so we add one immediately after popping back up but that is sometimes going to cause an issue cuz right now from here if we pop back up and we add one to this we're going to get to 200 that's not quite what we wanted cuz we haven't even visited two yet and we haven't even visited 20 yet so why would we want to visit 200 first so that's the issue here we don't want to add one to this we want to pop it again and from 19 we don't want to add one to that that would give us 20 we haven't even visited two yet so we don't want to do that we want to pop back up again what's the pattern why are we popping up multiple times because we reached the last number in the level here and here how do we know we reach the last number in the level because the last digit is a nine how do we get that last digit that's pretty easy you just mod it by 10 so if that's the case we would keep popping back up so we don't need an if statement to pop back up we actually need a loop to continuously do that I think that's probably the hardest part about the iterative solution and the other thing is um imagine that we reached this number like down here we reached 136 we know that we pop back up not because the last digit is nine but because this number is actually equal to this one that's another reason to pop back up so that's more or less how you do the iterative solution so let's code it up now I'm going to leave this code here we are still going to have a result we are still going to return it and we're going to do something slightly differently we're going to do this iteratively so I'm going to use a loop I'm going to say while the length of my output is less than n I'm going to keep going we're going to start at one so we can put that here current is one the easiest thing to do is just to take the number every time we expect the current number to be distinct and to continuously follow that iterative sequence so every time we have that number we're just going to add it to the result like this now just like in the recursive solution where we multiply by 10 make the recursive call and then from that recursive call we do the same thing check the base case multiply by 10 make the recursive call we're basically multiplying it by 10 as much as we possibly can that's the first thing that takes the highest precedence so if current times 10 we don't have the luxury of a base case so before we actually multiply it by 10 let's check that it's in bounds let's check that this multiplied by 10 is less than or equal to n if so then do that perform that multiplication and then on the next iteration of the loop we're going to add it to the result and we're going to keep doing this as much as we can now if that's not the case we have to do something different okay we can't multiply it by 10 so there's two things either we pop back up or we add one to it well remember after popping back up we're going to be adding one to it anyway so we might as well put that here we might as well add one to it now this is the optional part we don't know if we're popping back up how do we know if we would be popping well if current is equal to n it's already as big as we can possibly make it so we better pop back up before we add one to it because we don't want a number larger than n to be appended to the result so that's one case or obviously if the number moded by 10 is equal to 9 if the last digit is a nine if we add one to it that's not going to give us exactly what we want so that's another reason to pop back up when I say pop back up I basically mean take the number and integer divide it by 10 and so this is not going to be an if statement remember it's going to be a loop but that's pretty much the entire solution I'll go ahead and run it and as you can see it works and is more efficient thanks for watching if you're preparing for coding interviews check out NE code. I'll see you soon

Original Description

🚀 https://neetcode.io/ - A better way to prepare for Coding Interviews 🧑‍💼 LinkedIn: https://www.linkedin.com/in/navdeep-singh-3aaa14161/ 🐦 Twitter: https://twitter.com/neetcode1 ⭐ BLIND-75 PLAYLIST: https://www.youtube.com/watch?v=KLlXCFG5TnA&list=PLot-Xpze53ldVwtstag2TL4HQhAnC8ATf Problem Link: https://leetcode.com/problems/lexicographical-numbers/description/ 0:00 - Read the problem 0:30 - Drawing Explanation 4:56 - Coding Explanation 7:08 - Optimized Explanation 10:23 - Optimized Solution leetcode 386 #neetcode #leetcode #python
Watch on YouTube ↗ (saves to browser)
Sign in to unlock AI tutor explanation · ⚡30

Playlist

Uploads from NeetCodeIO · NeetCodeIO · 0 of 60

← Previous Next →
1 Leetcode 149 - Maximum Points on a Line - Python
Leetcode 149 - Maximum Points on a Line - Python
NeetCodeIO
2 Design Linked List - Leetcode 707 - Python
Design Linked List - Leetcode 707 - Python
NeetCodeIO
3 Minimum Time to Collect All Apples in a Tree - Leetcode 1443 - Python
Minimum Time to Collect All Apples in a Tree - Leetcode 1443 - Python
NeetCodeIO
4 Design Browser History - Leetcode 1472 - Python
Design Browser History - Leetcode 1472 - Python
NeetCodeIO
5 Number of Good Paths - Leetcode 2421 - Python
Number of Good Paths - Leetcode 2421 - Python
NeetCodeIO
6 Flip String to Monotone Increasing - Leetcode 926 - Python
Flip String to Monotone Increasing - Leetcode 926 - Python
NeetCodeIO
7 Maximum Sum Circular Subarray - Leetcode 918 - Python
Maximum Sum Circular Subarray - Leetcode 918 - Python
NeetCodeIO
8 Find Closest Node to Given Two Nodes - Leetcode 2359 - Python
Find Closest Node to Given Two Nodes - Leetcode 2359 - Python
NeetCodeIO
9 Concatenated Words - Leetcode 472 - Python
Concatenated Words - Leetcode 472 - Python
NeetCodeIO
10 Data Stream as Disjoint Intervals - Leetcode 352 - Python
Data Stream as Disjoint Intervals - Leetcode 352 - Python
NeetCodeIO
11 LFU Cache - Leetcode 460 - Python
LFU Cache - Leetcode 460 - Python
NeetCodeIO
12 N-th Tribonacci Number - Leetcode 1137
N-th Tribonacci Number - Leetcode 1137
NeetCodeIO
13 Best Team with no Conflicts - Leetcode 1626 - Python
Best Team with no Conflicts - Leetcode 1626 - Python
NeetCodeIO
14 Greatest Common Divisor of Strings - Leetcode 1071 - Python
Greatest Common Divisor of Strings - Leetcode 1071 - Python
NeetCodeIO
15 Shortest Path in a Binary Matrix - Leetcode 1091 - Python
Shortest Path in a Binary Matrix - Leetcode 1091 - Python
NeetCodeIO
16 Insert into a Binary Search Tree - Leetcode 701 - Python
Insert into a Binary Search Tree - Leetcode 701 - Python
NeetCodeIO
17 Delete Node in a BST - Leetcode 450 - Python
Delete Node in a BST - Leetcode 450 - Python
NeetCodeIO
18 Shuffle the Array (Constant Space) - Leetcode 1470 - Python
Shuffle the Array (Constant Space) - Leetcode 1470 - Python
NeetCodeIO
19 Fruits into Basket - Leetcode 904 - Python
Fruits into Basket - Leetcode 904 - Python
NeetCodeIO
20 Number of Subarrays of size K and Average Greater than or Equal to Threshold - Leetcode 1343 Python
Number of Subarrays of size K and Average Greater than or Equal to Threshold - Leetcode 1343 Python
NeetCodeIO
21 Naming a Company - Leetcode 2306 - Python
Naming a Company - Leetcode 2306 - Python
NeetCodeIO
22 As Far from Land as Possible - Leetcode 1162 - Python
As Far from Land as Possible - Leetcode 1162 - Python
NeetCodeIO
23 Shortest Path with Alternating Colors - Leetcode 1129 - Python
Shortest Path with Alternating Colors - Leetcode 1129 - Python
NeetCodeIO
24 Minimum Fuel Cost to Report to the Capital - Leetcode 2477 - Python
Minimum Fuel Cost to Report to the Capital - Leetcode 2477 - Python
NeetCodeIO
25 Count Odd Numbers in an Interval Range - Leetcode 1523 - Python
Count Odd Numbers in an Interval Range - Leetcode 1523 - Python
NeetCodeIO
26 Contains Duplicate II - Leetcode 219 - Python
Contains Duplicate II - Leetcode 219 - Python
NeetCodeIO
27 Path with Maximum Probability - Leetcode 1514 - Python
Path with Maximum Probability - Leetcode 1514 - Python
NeetCodeIO
28 Add to Array-Form of Integer - Leetcode 989 - Python
Add to Array-Form of Integer - Leetcode 989 - Python
NeetCodeIO
29 Unique Paths II - Leetcode 63 - Python
Unique Paths II - Leetcode 63 - Python
NeetCodeIO
30 Minimum Distance between BST Nodes - Leetcode 783 - Python
Minimum Distance between BST Nodes - Leetcode 783 - Python
NeetCodeIO
31 Design Hashmap - Leetcode 706 - Python
Design Hashmap - Leetcode 706 - Python
NeetCodeIO
32 Range Sum Query Immutable - Leetcode 303 - Python
Range Sum Query Immutable - Leetcode 303 - Python
NeetCodeIO
33 Binary Tree Zigzag Level Order Traversal - Leetcode 103 - Python
Binary Tree Zigzag Level Order Traversal - Leetcode 103 - Python
NeetCodeIO
34 Middle of the Linked List - Leetcode 876 - Python
Middle of the Linked List - Leetcode 876 - Python
NeetCodeIO
35 Course Schedule IV - Leetcode 1462 - Python
Course Schedule IV - Leetcode 1462 - Python
NeetCodeIO
36 Single Element in a Sorted Array - Leetcode 540 - Python
Single Element in a Sorted Array - Leetcode 540 - Python
NeetCodeIO
37 Capacity to Ship Packages - Leetcode 1011 - Python
Capacity to Ship Packages - Leetcode 1011 - Python
NeetCodeIO
38 IPO - Leetcode 502 - Python
IPO - Leetcode 502 - Python
NeetCodeIO
39 Minimize Deviation in Array - Leetcode 1675 - Python
Minimize Deviation in Array - Leetcode 1675 - Python
NeetCodeIO
40 Longest Turbulent Array - Leetcode 978 - Python
Longest Turbulent Array - Leetcode 978 - Python
NeetCodeIO
41 Last Stone Weight II - Leetcode 1049 - Python
Last Stone Weight II - Leetcode 1049 - Python
NeetCodeIO
42 Construct Quad Tree - Leetcode 427 - Python
Construct Quad Tree - Leetcode 427 - Python
NeetCodeIO
43 Find Duplicate Subtrees - Leetcode 652 - Python
Find Duplicate Subtrees - Leetcode 652 - Python
NeetCodeIO
44 Sort an Array - Leetcode 912 - Python
Sort an Array - Leetcode 912 - Python
NeetCodeIO
45 Ones and Zeroes - Leetcode 474 - Python
Ones and Zeroes - Leetcode 474 - Python
NeetCodeIO
46 Remove Duplicates from Sorted Array II - Leetcode 80 - Python
Remove Duplicates from Sorted Array II - Leetcode 80 - Python
NeetCodeIO
47 Maximum Twin Sum of a Linked List - Leetcode 2130 - Python
Maximum Twin Sum of a Linked List - Leetcode 2130 - Python
NeetCodeIO
48 Concatenation of Array - Leetcode 1929 - Python
Concatenation of Array - Leetcode 1929 - Python
NeetCodeIO
49 Symmetric Tree - Leetcode 101 - Python
Symmetric Tree - Leetcode 101 - Python
NeetCodeIO
50 Check Completeness of a Binary Tree - Leetcode 958 - Python
Check Completeness of a Binary Tree - Leetcode 958 - Python
NeetCodeIO
51 Construct Binary Tree from Inorder and Postorder Traversal - Leetcode 106 - Python
Construct Binary Tree from Inorder and Postorder Traversal - Leetcode 106 - Python
NeetCodeIO
52 Find Peak Element - Leetcode 162 - Python
Find Peak Element - Leetcode 162 - Python
NeetCodeIO
53 Accounts Merge - Leetcode 721 - Python
Accounts Merge - Leetcode 721 - Python
NeetCodeIO
54 Binary Tree Preorder Traversal (Iterative) - Leetcode 144 - Python
Binary Tree Preorder Traversal (Iterative) - Leetcode 144 - Python
NeetCodeIO
55 Binary Tree Postorder Traversal (Iterative) - Leetcode 145 - Python
Binary Tree Postorder Traversal (Iterative) - Leetcode 145 - Python
NeetCodeIO
56 Number of Zero-Filled Subarrays - Leetcode 2348 - Python
Number of Zero-Filled Subarrays - Leetcode 2348 - Python
NeetCodeIO
57 Minimum Score of a Path Between Two Cities - Leetcode 2492 - Python
Minimum Score of a Path Between Two Cities - Leetcode 2492 - Python
NeetCodeIO
58 Sqrt(x) - Leetcode 69 - Python
Sqrt(x) - Leetcode 69 - Python
NeetCodeIO
59 Successful Pairs of Spells and Potions - Leetcode 2300 - Python
Successful Pairs of Spells and Potions - Leetcode 2300 - Python
NeetCodeIO
60 Optimal Partition of String - Leetcode 2405 - Python
Optimal Partition of String - Leetcode 2405 - Python
NeetCodeIO

The video teaches how to solve Leetcode problem 386, Lexicographical Numbers, using recursive and iterative approaches in Python. The solution involves traversing a tree of lexicographical numbers and using a stack data structure to optimize the recursive algorithm. This problem is relevant to systems design and algorithm basics.

Key Takeaways
  1. Declare the result variable and return all numbers in the form of a list
  2. Create a recursive function DFS with a current value and base case
  3. Use a loop to traverse the descendants of the current number
  4. Call DFS starting from 1 through 9 to get all numbers
  5. Use a stack data structure to optimize recursive algorithm
  6. Divide by 10 to pop back up in iterative solution
  7. Use a loop to continuously pop back up when last digit is 9
💡 The key insight is to use a stack data structure to optimize the recursive algorithm and to divide by 10 to pop back up in the iterative solution.

Related Reads

📰
75 Days of Leetcode — Day 4: #238 — Product of Array Except Self
Solve the Product of Array Except Self problem on LeetCode to improve coding skills and learn array manipulation techniques
Medium · AI
📰
I implemented the algorithm that broke the sorting barrier. Dijkstra still wins.
Learn how the author implemented an algorithm that broke the sorting barrier and compare its performance with Dijkstra's algorithm
Medium · Programming
📰
I implemented the algorithm that broke the sorting barrier. Dijkstra still wins.
Implementing an algorithm that breaks the sorting barrier still can't beat Dijkstra's algorithm in practice, highlighting the importance of real-world testing and optimization.
Medium · Python
📰
Practice Algorithms and DS the Structured Way
Learn to practice algorithms and data structures in a structured way to build a mental map of techniques
Medium · Programming

Chapters (5)

Read the problem
0:30 Drawing Explanation
4:56 Coding Explanation
7:08 Optimized Explanation
10:23 Optimized Solution
Up next
Stump Grinder Carbide Wheel Grinds Hardwood To Chips
Innoforge Studio
Watch →