Learniverse

Improving Problem Solving Skills with Elite Code Problems

00:00

Hey, what's up everyone and welcome back to another video.

00:04

So if you want to become a great data scientist, software engineer, Uber, hacker, dude,

00:11

dude at you first want to become a good problem solver.

00:16

So in this video we're going to walk through a bunch of elite code problems and the reasoning

00:20

for this is twofold.

00:22

One, these elite code problems kind of can help you improve your general coding skills.

00:26

And then two, these are algorithms type problems.

00:30

So they really make you think and then really kind of train that problem solving muscle.

00:35

In this video, we're going to go from easy problems or relatively easy problems to more

00:40

difficult problems.

00:41

So feel free to do a little binary search to figure out like the right difficulty for

00:45

you.

00:46

Also feel free to kind of like watch one problem, then come back to the video another

00:49

day, et cetera.

00:51

And also the general way that you'll want to approach each problem is I'll link every problem

00:56

problem that we do in the description.

00:58

So what you should do is try out the problem on your own.

01:01

And then if you can't figure out the solution or just want to see how I would approach the

01:05

problem, play through my solution of the, the problem in the video.

01:10

Think this should be a fun one without further ado, let's get into it.

01:14

How I'm going to get to these problems is I'm just going to go to leekode.com.

01:17

I'm going to click on problems.

01:21

I'm going to use filter by algorithm, it doesn't really matter here.

01:28

And then just to kind of figure out what's harder, what's easier.

01:31

This isn't a perfect filter, but what I ended up doing is I filtered or I sorted by acceptance.

01:37

So the higher the acceptance rate, you think that that would be kind of the easier problems.

01:41

More people got them right.

01:43

So to start off, we'll kind of do some higher acceptance, easy problems, then we'll

01:48

move into kind of some higher acceptance, medium problems.

01:52

And then maybe at the end of the video, we might do one or two hard problems, not positive

01:56

yet.

01:57

All right, to start off, let's do this problem, the goal parser interpretation.

02:04

This is problem 1678 and I've linked it in the description.

02:08

And so you can read through the problem statement, but the basics is that we have this

02:12

parser that takes in strings of the format like this and translates it into more readable

02:21

strings like this.

02:23

And so there's three characters, there's the G, the parentheses and the parentheses with

02:27

the AL, and we need to just do these replace bins.

02:31

So it's pretty straightforward problem, but let's give it a shot.

02:34

Feel free to pause the video and then resume when you want to see me solve it.

02:38

All right, so when I look at this problem, I think about replacements in Python.

02:42

We're taking these inputs over here on the left and we're making these outputs.

02:47

So when we're thinking of replacements in Python, I think of the string replacement method.

02:52

And if you're not familiar with that, as always, kind of allude to, do a quick Google

02:56

search like replace strings in Python.

03:01

And you'll find something useful that can maybe help you out.

03:05

So it's as easy as doing something like this.

03:11

Well, now that we have that, we can just simply do the replacements that are necessary.

03:17

So we have a command, right?

03:19

And if we do dot replace, or we can replace the G with a G, but we don't really need to

03:26

do that because that's not actually replacing anything.

03:29

So we can start with the parentheses.

03:31

Anytime we see parentheses, let's replace it with just an O.

03:37

We can chain these commands together.

03:40

So this is going to return us a string.

03:43

So we can actually just simply do dot replace again with AL and making that just AL without

03:55

parentheses.

03:56

And that should be all there is to changing it to the format that we expect.

04:02

So I want to just go ahead and return this and see what happens.

04:06

So I'm going to run code.

04:15

Nice.

04:16

It looks like it worked.

04:17

And the next thing we want to think about when we're doing problems like this, are there

04:21

edge cases that we're not considering?

04:23

Well, I think to do that, we're going to have to look at the different examples and

04:29

kind of the constraints.

04:31

So we know that it's going to be between 1 and 100.

04:36

So that's pretty straight forward.

04:37

That's pretty small.

04:38

We don't have to really worry about like, is the replace method like efficient or not.

04:43

And we also know that we're constrained to only these three characters.

04:47

So there's no like weird, maybe like situation where there was like something like, you

04:54

know, a parentheses that's not fully closed or something and or like nested parentheses

05:00

like that.

05:01

So I think we're good here.

05:04

You can feel free to type in these other examples as test cases, but I'm going to go ahead

05:10

and submit this.

05:18

Accepted.

05:19

Look at that.

05:20

Cool.

05:21

And there's many different ways to do this.

05:23

My other recommendation when you're doing these problems is you can go to the discussion

05:28

always and look at other people's solution.

05:32

So we see like Python online, I'm guessing this is going to probably be the same as what

05:36

we have and look at that it is, but maybe some of these other solutions are different.

05:41

So Python, C++, simple solution using dictionaries.

05:49

And we see this person has done something else in their solution.

05:55

So it's good to maybe look through these discussion solutions because that gives you other

06:00

ways, other perspectives on how you can solve these different things.

06:04

All right.

06:05

Next, let's do problem 1436 destination city.

06:09

So in this problem, we are given an array paths and each path in that array is a connection

06:16

between city A and city B where there is a, which means there is a direct path going from

06:21

city A to city B.

06:24

And our goal is to return the destination city, which is the city that doesn't have any

06:30

outgoing paths from it.

06:33

So as an example, we see in this case, we have the path London to New York.

06:38

We have New York to Lima, Lima to Sao Paulo.

06:42

And because Sao Paulo has nothing coming out of it, no, no paths are leaving from Sao

06:47

Paulo, we know that Sao Paulo is the destination city.

06:53

There's also you can have more complex examples, like example two, where you can go from

06:58

B to C, you can go from D to B and you can go from C to A. So there's a few different

07:03

path options as we can see here.

07:06

But every one of those paths leads to A. So we know that A is the destination city.

07:12

So how could we program out a solution that could kind of find that destination city?

07:18

So what I think about this problem, I think about counting.

07:21

We're ultimately trying to count the city that has no outgoing nodes from it.

07:28

So if we count how many outgoing nodes, each one of the cities that we see has and just

07:34

return the one that has zero outgoing, that will be our solution.

07:38

So we can probably use a lot as a dictionary to help us do this.

07:41

So let's write a solution that does that.

07:44

So for path in paths, we want to iterate over each path.

07:50

And I do want to quickly add some sort of dictionary that we can use to count.

07:53

So let's say that this is called outgoing count and we'll make that an empty dictionary.

08:01

So for path in paths, well, that each path is going to be a pair of two cities city A and

08:10

city B. So what we could do is we could say that city A comma city B equals path.

08:19

This is just unpacking a path like London, New York.

08:24

So city A would be equal to London and city B would equal to New York.

08:29

That's one way we can unpack a list of two items or a tuple of two items.

08:35

So we have city A and city B. And really what we want to count is we want to increase

08:41

this account every time a city is listed as city A.

08:48

But we do want to count city B because ultimately if we only counted city A is we'd

08:52

miss out on the destination city.

08:55

So let's add both of these to the dictionary.

08:58

So what we can do is something like outgoing count and

09:10

we can use the city that we want to input into that.

09:15

And then what we want to do is kind of like increase that count by one.

09:21

But it might not be set to begin with.

09:23

So we'll have to keep that in mind.

09:25

So we'll say something maybe like outgoing count dot get of the city A.

09:35

And if we don't have city A already there, then let's go ahead and set that to one.

09:43

Do we get in with?

09:46

And we can do something similar for city B.

09:48

So we can say outgoing count city B equals outgoing count.

09:56

Dot get city B.

09:59

But this time because city B is the destination node,

10:02

we don't want to increase that outgoing count for city B.

10:06

So if we haven't seen city B before, let's just set it to zero.

10:12

And I guess in this case too, we don't want to just get the number.

10:19

We do need to increase it.

10:21

So in this case, we should do, we don't even need it.

10:26

We can say keep this at zero, but we should add one at the end.

10:29

So if we haven't seen it before, we set it to zero, but then we add one.

10:34

If we have seen it already, we've seen that there's outgoing node.

10:37

Then we get that count and we just add another one to it.

10:43

And really it doesn't even matter.

10:44

All we care about is ultimately what's left as zero.

10:48

Okay, so that creates our dictionary.

10:51

So once we have a dictionary that has all those counts,

10:54

then we actually need to go ahead and count them and see what value is the lowest.

11:00

So what we could do is just iterate over the dictionary.

11:04

So let's just say for city and outgoing count.

11:11

And then if we ever find a city that has zero outgoing counts, that's our solution.

11:17

So that's what we can return.

11:20

So outgoing count of city, if outgoing count of city equals equals zero,

11:31

then we want to return that city.

11:36

And we always are supposed to have a destination city.

11:38

So there should never be an error case here.

11:42

So I think that this is probably a good solution.

11:48

Look at that, it seems to work.

11:52

And so what would be the complexity of this solution?

11:54

Well, it's bounded by O of n to iterate over each path in the paths.

12:03

And really we can't get any better than O of n because we have to look at

12:08

every path to ultimately figure out what's the destination city.

12:12

But I guess the question is, does this last second iteration over the dictionary

12:19

increase that runtime complexity?

12:22

And ultimately it does not because it's just another,

12:26

instead of just going O of n or like just one iteration of n times,

12:32

we're doing a second iteration of n times.

12:34

So it's really a total of two n iterations which is still bounded by that O of n

12:42

runtime complexity.

12:43

So that seems like a pretty good solution to me.

12:46

Let's go ahead and submit that.

12:51

And it is submitted or it is accepted.

12:54

Cool.

12:56

And it looks like it's running pretty well.

12:58

It's less than 97.31% of the Python 3 online submissions.

13:04

And it's faster than 74.51% of them.

13:08

So in this case, our first solution worked out well and did a good job.

13:13

Sometimes that's not the case and we have to continue and prove things.

13:16

But I would say that I would be pretty happy with this.

13:21

If I saw this as the solution to like a coding interview question,

13:25

one thing that's cool to do though is kind of look at the discussion,

13:27

see if there's anything else really clever that people did.

13:31

And look at that.

13:33

This person used a set and basically said, OK.

13:39

I mean, this is a little hard to read, but I really like this idea of a set.

13:45

Basically, they counted what's in city A and what is a city B.

13:53

And then they subtracted the set of B elements that were in B from elements

13:59

that were in city A. And ultimately, the one that's left in B

14:02

that wasn't in A will be that element that is returned.

14:08

Next, we're going to do problem 1365.

14:11

How many numbers are smaller than the current number?

14:14

Again, linked in the description.

14:17

And so the problem statement here is, given array numbs, for each numbs I,

14:22

find out how many numbers in the array are smaller than it.

14:24

That is, for each numbs index I, you have to account the number of valid j's

14:30

such a j is not equal to i.

14:32

So that's talking about indexes.

14:34

And numbs j is less than numbs i.

14:37

So sometimes these problem statements can be kind of confusing.

14:40

So my recommendation is always, you know, really look at the examples.

14:44

That will be what really solidifies the problem.

14:47

So we see here in this example, we have this input, which is an array of numbs.

14:52

8, 1, 2, 2, 3.

14:55

And we see the output is 4, 0, 1, 1, 3.

14:58

And what they're doing here is that we look at this number 8, and we see that we have

15:03

4 numbers that are less than it.

15:06

That's where we get the 4 here, the 1.

15:08

There are no numbers in this array that are less than it, so we get the 0, et cetera.

15:14

So when we're solving problems like this, what I recommend is don't try to be creative.

15:20

Don't try to be clever to start.

15:21

What is a simple solution that you can think of that would work?

15:26

And so when I look at this, I see what's easy to me is just let's do some kind of a straightforward

15:32

counting.

15:33

Let's look at each number in this.

15:35

And as we're looking at that number, let's just iterate over the entire list and just count

15:40

how many numbers are less than that number and append it to our output.

15:47

So for example, this 8, we would be looking at it and then iterate over the rest of

15:52

the list.

15:53

1, that's less than it, plus 1, 2, plus 1, 2, plus 1, 3, plus 1.

16:00

That's a total of 4, so we'd append 4 to our list, and we just kind of continue down

16:07

our list.

16:08

So let's write that in code.

16:09

And this solution I just described would be a complexity of n squared.

16:16

So I think we can probably do better, but I think it's good to just kind of write out a

16:19

simple solution first.

16:21

But if you were doing this in an interview setting, just be like, hey, this is what I'm

16:25

thinking of right now.

16:26

But ideally, we could figure out something more efficient and kind of start there.

16:32

See that you're thinking, see that you are kind of always seeing how you can improve things.

16:38

All right, let's implement this.

16:40

So for I in Nums, we'll look at each number here, I'm going to keep track of our output

16:47

in a row.

16:48

So we're going to say output equals an empty list.

16:52

So for I in Nums, we want to then iterate over 4j in Nums.

17:01

And basically, as we are looking at this problem, we don't want j to be equal to i.

17:06

So we're going to say if j is not equal to i, then we want to, maybe let's say we have

17:16

a counter, count equals 0, and we're going to increase that counter.

17:22

Count plus equals 1.

17:25

And then when we've exited out of this loop, so when we've iterated over the entire array

17:30

of Nums for this specific i, we can do output is the append of the current count.

17:41

And then we will get our next i, and our count will be reset to 0.

17:47

So we do that for everything, and then we can simply at the end return the output.

17:53

So hopefully that made sense, and let's run it and try it.

18:02

Wrong answer.

18:03

Okay, I guess something got screwed up there.

18:06

So I'm pending every time.

18:12

We want it only append if j is not equal to i, and Nums j is less than Nums i, oops

18:25

about.

18:26

That was a silly mistake.

18:31

Index our list index out of range.

18:34

What do we do here?

18:38

Oh, oops, now what are we doing?

18:43

Now we're not looking at the indexes.

18:45

So see, you make mistakes if you're not careful, but what's happening here is we're not

18:53

iterating over the actual index value.

18:55

So what we could do is we could say, in length of and range, length, Nums, and again,

19:06

we want to iterate over the index value, so we're going to do range, length, Nums.

19:16

And what I would recommend is if you have premium, you can get the debugger, so you can

19:19

actually print out values and see what's going on.

19:22

But you also could just like open up a sublime text window, whatever your code editor

19:26

of choice is, and try out some of these test code cases with a function that you define

19:32

there.

19:33

But let's run this code again.

19:38

Okay, look, that looks like it works.

19:44

We could try more test cases, but I'm feeling confident that let's just go ahead and

19:49

submit our code.

19:55

Look at that.

19:56

It is accepted, but we see that our runtime is only faster than 8.83% of Python 3 submissions.

20:04

So there's something we can probably can do that is smarter than this.

20:08

So we're right now at a complexity of N squared.

20:11

I'm going to just scrap all this and we're going to start fresh.

20:15

Let's try to get to a complexity that is better than N squared.

20:21

And one thing to note is you can always find the code.

20:24

If I click on accepted here, I could find that last submission.

20:26

So I'm not losing anything by deleting all of my code.

20:29

Let's go back.

20:30

All right, we just did an N squared solution.

20:33

What I think is better, this is the next thing that comes to mind.

20:37

It might not be the optimal solution, but I'm thinking if we sort this list and know the

20:43

length of this list, then it's pretty easy for us to tell how many numbers are less than

20:53

the current number we're looking at.

20:55

So if we sorted this, let's say, like 8, 3, 2, 2, 1, so I'm going to type that out real

21:00

quick.

21:01

So let's say we sorted this to 8, 3, 2, 2, 1, well, we see that 8 would get 4 numbers

21:12

less than it, 3 numbers less than it, 2, well, we see we have 2, and we see the next

21:20

number is 2.

21:22

So really, we need to start counting only when we get to the new number.

21:27

So we'd have to count how many times we see that the same number and kind of append that

21:31

many times.

21:33

So we'd 2 would be 1 number, 2 would be 1 number, and then 1 is the last item, so that'd

21:39

be 0 numbers.

21:40

So couldn't we implement this solution?

21:42

All right, I'm going to kind of just start fleshing things out, and I might have to like

21:47

throw some print statements in this and whatnot to get it to fully be working.

21:52

But let's start out by defining sorted num's equals sorted, I think we do that, sorted.

22:00

I think that's a built-in Python function of num's.

22:03

And honestly, I could real quick do print sorted num's and just check.

22:08

So we're going to run code real quick, and it does give us our standard output, okay.

22:15

So we see that it's sorting it lower to higher.

22:18

So I think if I pass in the keyword reversed equals true, I'm like, really, I would normally

22:25

be Google searching this, I'm like crossing my fingers at those works right now.

22:32

this is, maybe it's reverse, oh, look at that, it worked.

22:41

Normally I would have to look this up, I kind of vaguely remember that.

22:46

Okay, so we have 8, 3, 2, 2, 1, that's what we're looking for.

22:50

And now what we're going to do is, let's go for num, or let's do, how about this?

23:00

Let's do, for I comma num, so we're going to have an index and the number, and a numerate.

23:08

A numerate's a very useful keyword, a numerate sorted num's.

23:14

And we also need to think about the original index here.

23:20

So there's a couple of things that now that we're working through this, that I'm trying

23:26

to keep in mind, because now that we sorted it, things are out of order, so this output

23:33

would be out of order as well.

23:36

So how could we keep that in mind?

23:41

So one idea that comes to mind, and I'm not positive if it's the optimal solution, but

23:47

if we have a list like 8, 3, 2, 2, 1, let's create a dictionary that looks of the format,

23:59

you know, value, so the key of the dictionary is the value, and it maps that value to the

24:06

number of values that are less than that value.

24:09

So like 8 would map to 4, 3 would map to 3, etc.

24:17

And then we'll do a additional pass on our input just doing lookups in this dictionary

24:25

to create our final output.

24:28

So this will make sense in one second.

24:30

So what we could do is something like, as we look at a number, so let's say we're looking

24:36

at 8, we check the next number.

24:38

If it's less than 8, then we check the length of this array, and we subtract this current

24:43

index to find the number of values that are remaining in the array.

24:47

And then we append that to our dictionary.

24:50

So I'm going to call this value dict, or maybe I'll call it, if I want to be more descriptive,

24:58

like smaller, smaller count, I'm struggling to just figure out a good name.

25:05

It's good to use descriptive variables, but you don't have to worry too, too much.

25:10

I think smaller count makes sense, they're probably something better I could use.

25:13

But what we're going to do is, so we're looking at a current num, so basically if sorted

25:22

num's i plus 1 is less than sorted is less than num, then we want to take these

25:35

the index of sorted num's i plus 1, and basically do remaining values equals sorted num

25:48

or equals length of sorted num's minus i plus 1, because this is the index that we're

26:00

looking at, and this is the total length.

26:03

So this subtraction would give us the number of values that meet that criteria.

26:10

So we see if we had 8, this would be length 5, the 3 would be index 1, length 5 minus

26:19

index 1 would give us 4, so we'd see that 8 has 4 values.

26:24

So what we would do is then do num or smaller count of that num that we're currently

26:34

looking at equals remaining values.

26:39

And one thing that I see right now is that we're going to get an error when we get to the

26:43

last index, because i plus 1 will not be defined, so I'm actually going to switch this up again

26:48

and actually do for i in range length of sorted num's just so that I can actually go

27:01

until 1 less than the total length, so that i plus 1 is always defined.

27:08

We always know that the smallest number will be 0 less than, so we can add that at the

27:14

the end, so a smaller count of the last index, so we could just do sorted num's negative

27:35

1 equals 0, so this is kind of the last case, okay, so for i now we need to define num,

27:47

so num equals sorted num's i, and if we wanted to be very kind of neat about this we could

27:56

say current num equals sorted num's i, next num equals sorted num's i plus 1, then we could

28:09

change this to if next num is less than the current num, we want to do this.

28:19

Now this, the one case that we want to think about is what happens when we're iterating

28:23

through our list, we go 8, 3, 2, 2, 1 and we get to a situation like this where 2 and 2 are

28:31

the same number. Well in this case what our code is already actually nicely factoring in but it

28:36

might not be intuitive that it's doing this is if the next num is not less than the current

28:45

num, then we're basically going to skip over it because we really only start counting when

28:51

the next number is in fact less than the current number, so every 2 here will have the same value

29:01

1 number less than it, and ultimately that will be added to our dictionary right at this last 2,

29:07

so we could have 100 2's but that last 2 is going to define how many numbers are after it that

29:12

are less than it. So we've now created our smaller count dict and then the final thing is to

29:20

just iterate over our smaller count dict, so for value in smaller, I guess we want to iterate

29:31

over our input, so for number in num, we want to create an output list, oh and so for num

29:49

num and num, we want to replace every num, we want to do output, dot append, the lookup of the

29:56

smaller count of the number we're currently looking at, run that code, I didn't return anything

30:06

Oops, and we also have an error, oh this is current num, gotta make sure that we don't, we change

30:14

if we change a variable name we gotta change it everywhere, and now we're getting nothing

30:22

because we're not returning anything, return output, accepted, look at that and I'm feeling confident

30:32

it again and so I'm gonna go ahead and submit, and look at that, that's crazy, so that this new

30:48

solution we just implemented, we're not doing n squared time complexity anymore, we're gonna be

30:55

ultimately bottlenecked by how long this sort takes, and if you know anything about sorts,

31:00

typically sorts are upper bounded by time constraint of n log n, so o of big o of n log n is

31:09

kind of time constraint with this sort, I don't know exactly what sort this sorted does by default,

31:16

but you can expect o of n log n for time complexity there, so hopefully this answers a bit more

31:22

complex, but as we can see it 10x speed up, upgrades are our solution, so this is in an interview

31:33

setting, this is the type of thing that you kind of work to get better and better, and there might

31:37

be even more optimal solutions, but I'd be pretty happy with this n log n solution, so retroactively

31:45

I wanted to check to see if this was an optimal type solution, I went to the discussion, and I

31:52

ultimately saw this Java solution beats 100% o of n, so they somehow found a way to do o of n,

32:01

and ultimately I'm not gonna go through this, you can look at it if you want, it's pretty

32:04

easy to find from the problem statement, but what we missed when we were looking at this problem

32:10

is the constraints are very important, if you're in an interview setting, always ask about the

32:15

constraints, you know how big can the numbers in this number list be, how many numbers can there be,

32:21

because that can influence how you design the solution, and ultimately what that very quick solution

32:26

did that was very highly upvoted in the discussion is they noticed that the numbers are always

32:33

gonna be between 0 and 100, so ultimately as a result of that, you can kind of do smarter things

32:42

with counting, because the numbers are, there's not infinite number like infinite possibilities

32:48

of what numbers you can choose, so you can ultimately like store everything in a bucketing

32:55

solution, and that discussion problem has that, and speed it up further, so constraints can be very

33:00

important here, always try to, and I'm guilty here, but it's always good practice to get more

33:11

information about the constraints. All right, next let's do a problem 704 binary search, so if

33:17

you're familiar with binary search, it's probably pretty straightforward what this question is

33:21

asking you to do, if you're not familiar with binary search, then this is a great example to kind of

33:26

help you learn what binary search is, and ultimately binary search is used in a lot of these coding

33:32

interview questions, so it's always good to be kind of thinking of this whenever you're trying to

33:38

do a problem that you need to find something inside of like a bigger array, binary search pops up a

33:44

so ultimately what are we trying to do? Well, we're given an array of numbers, and we're given a target,

33:49

and ultimately our goal is to figure out what index, or if there is even an index that that target

33:56

occurs at, so in this case we see that there is a target, and because it occurs at index 4, we return

34:04

4, in this second case, there's no 2 inside of this array, so whenever we don't see a number,

34:12

we output negative 1, so we could implement this in a very, very straightforward manner and just

34:19

kind of iterate over each element 1 by 1, and then if we see the number, then we return the index,

34:27

if we don't, then we return negative 1, and that is a valid solution, and let's just type that

34:32

up real quick, the issue with this solution is it's you'll find out it's not optimal, so let's do

34:41

for i and range length numbs, or we could do something like for i common num in enumerate numbs,

34:54

let's say if num equals equals target, then ultimately we want to return the index that that happened

35:06

else, if we get through every one of these items in the list, and we don't see a number that

35:13

equals target, then we can go ahead and return negative 1, and I always forget, I think it's index

35:21

common num when we use this enumerate keyword, this is supposed to be the index, this is supposed

35:26

to be the actual item in the list, I always forget the order, let's run code and see if this works,

35:33

it looks like it works, I'm actually very curious if this will be an accepted answer, it might

35:40

time out on us, so it was accepted, but it is not optimal because ultimately this problem

35:55

is the key to this problem is that this is sorted, so is there a way instead of iterating over

36:02

every number and figuring out if our targets there, is there a way that we can do this in a more

36:08

efficient manner, and this is kind of the crux of binary search, so if this isn't immediately

36:15

come to you, don't worry, like this can be tricky, but try to think, if the target is nine,

36:22

do we have to look through all of these numbers, so maybe take a second pause the video,

36:26

think about that, and resume it when you're ready to here, okay, so yeah, the key is that this is

36:33

sorted, so because it is sorted, well we can do in what kind of the root of binary search is,

36:40

instead of iterating over every number, let's just start in the middle, let's start with this five

36:45

or this three, our target is nine, so let's say we started with the three, is three bigger or

36:54

smaller than our target, well three is smaller, so ultimately we can ignore everything that comes to

37:00

the left of that three, because we know that it's also going to be smaller, so let's just focus on

37:06

these last three numbers, now we'll repeat the process, we'll hit the middle of the remaining

37:11

elements, and that is nine, and that's exactly our target, so the index of that position is what

37:16

we'll ultimately return, and so this is going to be kind of tricky to write and code, but let's

37:19

give it a shot, I think the best way to think about this as you start writing code is to think

37:24

of examples of how this might work, so let's say that we have the array of num that is equal to

37:31

negative one, three, four, six, eight, nine, twelve, fifteen, eighteen, and we have the target,

37:44

which is equal to nine, well how could we go about solving this, well we see that this has a length

37:54

of one, two, three, four, five, six, seven, eight, nine numbers, so that would mean that the first index

38:05

is always going to be, so our start index let's say is equal to zero, and our end index, this has nine

38:14

numbers, so because we start index counting at zero, that would be the end index would be eight,

38:21

and so our middle index would be equal to start plus end divided by two, which in this case,

38:31

plus end divided by two equals four, so that would give us zero, one, two, three, four, this is our

38:45

new kind of like our middle point, and we know that nine is bigger than eight, so what that tells

38:52

us is we can ignore all this stuff to the left, and so what we can do with binary search is basically

38:58

say okay our new start is going to be four, our new end is eight, still eight, and now our new middle

39:06

is four plus eight, which is twelve divided by two, which are going to be equal to six,

39:17

and that would give us zero, one, two, three, four, five, six, twelve is our new number,

39:25

well as nine, our target less than or greater than twelve, well it's less than in this case, so

39:31

we'll iterate again, our start equals four, our end now equals six because we're looking to the

39:39

left, and so now our midpoint equals four plus six divided by two, which gives us index five,

39:49

which also we see is the nine, so in this case we would return the five, if instead our target was,

39:57

so I'll say our mid equals five, that number equals the target, so return five,

40:05

if instead let's say our target was ten, well we get to the same point be between eight and 12,

40:12

but we'd see that our iteration of nine, our midpoint was less than ten, so we'd kind of like

40:21

repeat the process, we'd say start equals four, end equals, or I guess our start equals five now,

40:30

end equals six, and we see that if we try to like add those, and divide by two, our midpoint

40:40

point is the same as it was in the prior iteration, if we floor divide, so it doesn't quite work,

40:48

so let's get this out in code,

40:54

so I'm going to say start equals zero, end equals length of num's minus one, because if our,

41:03

we have nine numbers, the last index is one less than that, and so our first midpoint is going to

41:09

be start plus end divided by two, to be safe, because we might have an odd number here, let's

41:16

do floor divide by two, basically what we're going to want to do is, while our start

41:26

is, I'm writing in English, even though I'm talking code, while start is less than or equal to

41:33

end, let's continue this process, and I guess let's define our mid inside this for loop, so while

41:40

start is less than or equal to end, our midpoint is this, our number then is going to be

41:48

num's of midpoint, if target is equal to our number that we're looking at, then we want to return

41:58

that midpoint, that index that gave us that number, but if it's not, then we want to reset

42:04

our start or end to be kind of the new value, so if target is greater than num, then let's say like

42:15

we were here, well we would have to go look to the right, so we should shift up our start, so we'll

42:21

say that now start equals mid, else, and we can write this more elegantly, but we can do that after,

42:31

if target is greater than num, this is less than num, then our end is going to be equal to mid,

42:40

and then this for loop, we'll just go or this while loop, we'll go again until we hit this kind

42:46

of base condition, and if we exit the loop without ever finding a match, then we can return

42:54

negative one, let's see if this works, look at that works, and so if we wanted to be a little

43:06

bit more elegant here, we could have said that this was like lf, and this was even an else

43:13

conditioner, we could do another lf, so basically it doesn't try to run these statements if you

43:21

enter this statement, hopefully that kind of makes sense, binary search is very well documented,

43:25

so if this kind of quick code solution didn't make sense, then you definitely can find resources

43:33

online that can provide more I guess, algorithms type high level intuition about binary search,

43:41

let's go ahead and try submitting this, and I hope we didn't miss like an edge case or something,

43:50

we should be going kind of slow, time limit exceeded what did we do something silly,

44:01

as there any case that this, I feel like we might get into a never ending loop.

44:22

All right, so what I'm thinking about is it doesn't really make sense for us to just start the new

44:30

start at the exact midpoint, or the end at the exact midpoint, because we've already looked at

44:35

that midpoint, so we don't need to include it in our range. So let's just go ahead and make that

44:40

the start is whatever the midpoint one was plus one, and the end, if we have to change that,

44:47

it's whatever the middle point was minus one, so that you kind of are shifting in the right

44:52

direction. So like if we found the midpoint was eight, then we do eight, the index of eight plus one

45:01

to get just this instead of looking at eight again in our range. So I think that that hopefully

45:07

we'll solve the time limit exceeded, because basically I think we're getting into an infinite

45:11

loop, and that's why it wasn't working. So I'm going to try running the code again.

45:16

Accepted, submit it, cross our fingers. Accepted, look at that. And honestly, it didn't run that

45:28

that much faster than our first solution, but honestly, I believe that that's probably because

45:34

of the test cases that they used for this problem, but feel free always to look at the solution.

45:42

So let's see what the solution actually does. It seems like pretty similar.

45:51

There's even a video solution here.

45:57

Yeah, it looks pretty similar if just kind of some slight nuance.

46:06

Yeah, and instead of using a time complexity of O of N that we did originally, now it's O of log N,

46:12

cool. All right, we're going to do 409 longest palindrome.

46:18

So the question here is, and if you don't remember what a palindrome is, it's something that's the

46:23

same forward as backwards. And basically what we're trying to do is given a string, we want to know

46:30

how many digits would be the longest palindrome in that. So we have A, B, C, C, D, D, and we see that

46:41

the output is seven because the longest palindrome would be D, C, C, C, C, D. So we need to write

46:49

some code that figures this number out. Okay, so how should we think about this?

46:54

Well, if we think about palindroms, they have to be the same in, you know, going left to right

47:00

as right to left. They have to be the same forward and in reverse. So really, they need to

47:06

be flippable. And what that tells me is that really the digits that we use in it

47:13

need to all be even except for whatever that middle one is, if there is a middle.

47:20

So really, if we just count the number of characters in the string that have even number of

47:25

occurrences, that's going to be really the longest palindrome. Plus, if there's any odd numbers,

47:31

we would just do a plus one on that. So it should be a pretty straightforward counting problem.

47:37

Let's use a dictionary to do this counting. So we'll say character count is an empty dictionary

47:44

and what we can do is for character and string. We want to do character count. Well, if we haven't

47:53

seen this character yet before, we'll want to do equals character count dot get of the character.

48:03

But if we haven't seen it before, we'll want to set it to zero and then we can do plus one.

48:09

This lets us feel gracefully in case character is not already in the dictionary. Whereas if we did

48:16

something like this, it would error out because characters not in the dictionary and we're trying

48:26

to reference it, but we're able to set it like this. So that's why we're using the dot get because

48:32

if it doesn't find character, then it defaults to zero and then we can plus one to that. Okay.

48:39

So that would count all of the characters in the string. And so what basically we're going to want

48:44

to do is our final count will say is equal to zero for character in character count. Well, if the value,

49:01

so if the value is that's going to be character count of that character, if it's even so mod

49:08

two equals equals zero, so if it's evenly divisible by two, then we want to go ahead and add to

49:17

our final count all of those characters because they all could be included in a palindrome because

49:25

you could use half of them in the front and half of them in the back. So the equals character

49:31

count the character. All right. So that would add all of the even number characters. And now we

49:48

just need to count the odd number of characters. Well, we only can count a single one of them because

49:52

only can it be in the middle to create a valid palindrome. We couldn't have them on both sides.

49:59

So here we just really need to have some sort of Boolean that tracks whether or not we find a

50:06

character count that is odd. So what I'm going to say here is just an else. So this would mean it's

50:13

not evenly divisible by two. Then basically we just want to say that odd is present, I'll say,

50:26

equals true. And we can set out value odd is present, equaling false initially. And all we're going

50:36

to do with this variable is at the end once we've iterated over thing. If we did find an odd number

50:42

of values, we'll just add one more to our final count. And then we'll return that final count.

50:49

Let's give this a shot on code. That seemed to have worked. We can try another test case. It's always

51:03

good to use these short strings as test cases because you always can get errors on the short ones.

51:12

And also do we know that the string length is always between, yeah, it always has to at least be

51:18

one. It can't be an empty string. That's another good edge case to think about. So now we have the

51:22

three examples that gave us. Oh, wrong answer. No. So BB was wrong. What was BB wrong? It should have been

51:36

count two. Oh, oops. If why did I not do this? If odd is present, then we want to increase the

51:52

final count by plus one. So the last one odd wasn't present. So we shouldn't count that. Let's

51:58

run it again. Cool. Let's submit. Come on. Wrong answer. CCC. Oh, interesting. This is an

52:18

interesting edge case. It could be all of the same characters. So this is what happens. If you're

52:22

not careful, you should be thinking about the different edge cases. And I was running, you know,

52:28

I was jumping the gun there. So let's think. How do we incorporate this one?

52:35

Okay. So we need to add one more if statement to our condition. Basically what we want to do is

52:41

if it is divisible by two, then we add the exact character count. L if it is, or I guess else,

52:49

if it's not divisible by two, then we would want to do final count plus equals character count

53:04

the character minus one. So how does this work? Well, if there's three Cs, right, then that is not

53:16

divisible by two. So we wouldn't, we'd skip this if statement and we'd enter this one. Well, we can count

53:23

the two Cs because that is a palindrome. So we could add two there instead of three. That's basically

53:35

what we're doing right here. And then the last thing is, is there is an odd value present in this case.

53:42

It is three in general. So that remaining C, we can add it back in using the same if statement we had

53:48

originally. And then return the final count. So this should work. Look at that accepted and submit.

53:57

I think that will be good this time. Accept it. Yay. Cool. And it runs faster than 88.20% of Python

54:10

three submissions. Memory usage is not great though. Let's think. What is the runtime here?

54:18

Well, we count everything, every number in the string. So that would be O of n. And then

54:29

we iterate. Oh, so yeah, we count all that. That's O of n. And then we just iterate over our dictionary.

54:40

So that's also O of n because we could have n different unique characters. So really our runtime is

54:46

bounded by O of n. And that's really the best you're going to get because you do have to kind of

54:51

look at every character to figure this solution out. But we could do better probably in the memory

54:56

usage department. But I'm happy with this solution. Next, let's do 1561. Maximum number of

55:06

coins you can get. And as always, it's linked in the description. So in this problem, there are

55:12

three n piles of coins with varying size. And you and your friends will take turns taking one of

55:19

the piles of coins. And so in each step, you'll choose three piles of coins. And it's kind of,

55:27

we don't know which piles of coins we'll select. Of those three piles that we choose, Alice,

55:35

we'll always pick the maximum number of coins. Then we will pick the next maximum number of coins.

55:42

And then our last friend Bob will pick the remaining pile. And then we repeat until there are no

55:48

more piles of coins. So fairly straightforward problem. It seems like let's look at an example.

55:55

So we have the piles two, four, one, two, seven, eight. And we kind of are just randomly choosing

56:01

triplets. So like you can imagine any pair of three we start with. So if we picked two, four,

56:06

one is our first triplet. Alice would pick the four. I would pick the two. And then Bob would pick

56:12

the remaining, the one. So given that we could pick any of these three at any time, what is the

56:19

maximum number of coins that we could ultimately get? And so as we see in this example, like we

56:25

chose two, seven, eight first, we'd get the seven. And then the next triplet, one, two, four, we'd

56:31

get the two. And our maximum would be nine. There's no way for us to get any better than that

56:37

based on how this is structured. All right. So when we're thinking about solving this problem,

56:41

let's start with kind of a very, you know, simple solution. What would work? What is the easiest

56:48

thing we can think of that would work? Well, I'm thinking about that. I'm thinking about, okay,

56:52

if we just looked at every single pair of three items, like all the different combinations of

56:57

triplets and then just took the max sum based on all those different combinations, then we get

57:05

the solution. But we got to think about if that's practical. There are so many different ways we

57:12

could break up even just like six items into triplets of three. I'd have to do some kind of

57:20

combinatorics of the word to figure out the exact count of that. And I think in this case,

57:27

it'd be six choose three, which would give us our count. But in like other cases, longer cases

57:35

would be like nine choose three times six choose three all the way down until you, you know,

57:46

you have no triplets to go to. So that would add up really quickly. And it scares me,

57:52

especially given that the length could be 10 to the fifth in total. So doesn't seem like counting

57:58

all the different triplets in like summing that way makes sense. So what else can we do?

58:03

Well, I think the key thing to think about is that we just really care about the maximum number

58:08

of coins which we can have. So all we care about is the most optimal case. We don't have to care

58:15

if that's probabilistically likely that this happens. We just need to know in the best case scenario

58:21

what happens. And so if we're thinking about the best case scenario, we would pick the, we'd get

58:30

triplets that always have like the max number and the second max number and then the lowest number

58:36

and always get that like any triplet always would get that second max number of the remaining piles.

58:42

So as an example, what we could show is that let's take this list piles. So we have two, four,

58:53

one, two, seven, eight. Well, if we wanted to figure out what's optimal and it might actually work

59:01

better to take like this longer example, just to really demonstrate this property, take this,

59:12

well, I think we should sort it. So if we sorted this, we'd get one, two, three, four, five, six, seven,

59:21

eight, nine. And so what would be the most optimal thing to happen? Well, the most optimal thing

59:28

would be, okay, maybe our first pairing is has the eight and nine because those are the two

59:36

largest numbers. And then it has the smallest number for Bob to pick. Next iteration, we'd keep

59:48

going. So hopefully we'd have the next two biggest numbers, the six and the seven, and then maybe

59:55

like the smallest number for Bob to pick. So that would leave us with the biggest numbers remaining.

01:00:01

And then finally on the last iteration, our last triplet would be three, four, five,

01:00:10

it would be these three numbers. So why would this be optimal? Well, if we ever change one of

01:00:16

these numbers, so let's say like we put the seven in this spot instead of how we structured it,

01:00:23

well, that would mean that Bob would get the seven and ultimately that would reduce what we

01:00:26

could ultimately acquire. So we always want Bob to get the smallest and we always want to have

01:00:31

the opportunity to get the second biggest thing remaining. So that's why we'd like these triplets.

01:00:37

So really what we have to do is just sort this list and then iterate two from the right and one

01:00:45

from the left and keep going until we have no more items remaining. And then just add up

01:00:52

all of the second most from each triplet, like as we're doing that, just keep adding the second

01:00:59

biggest item in each triplet. So let's implement that in code. And let's think about also just

01:01:06

real quick runtime. Well, this would be bottlenecked by how much time it takes to sort this sorting

01:01:12

takes O of n log n. So n in this case would be the piles length. So you take that to sort it and

01:01:20

iterate over this. We could do that in just O of n. So our total runtime would be O of n log n.

01:01:28

All right, so what does that look like? Well, we could say sorted piles equals sorted of piles.

01:01:40

That's a good way to just do this in O of n log n. It's built in Python keyword.

01:01:44

Then so what we want to do is let's keep track of our index. So for i in range, the number of

01:01:55

iterations we're going to do in total because piles is 3n. We see 3n piles of coins. The total number

01:02:04

of iterations we want to do for grabbing three to time is just n. So let's do a length and also

01:02:12

given our constraints, we see that we know it's always going to be divisible by 3. So we can go

01:02:16

ahead and do length of piles or we can do sorted piles. It doesn't really matter,

01:02:23

divided by 3 to give us how many times we'll have to iterate. And so for each iteration,

01:02:30

our triplet is going to be sorted piles of i sorted piles of length of piles

01:02:49

because our last index, let's think about our last index. If we have length piles,

01:02:53

then our last index is length piles minus one. So I'm going to define this as a variable.

01:03:00

We'll say end index equals length piles minus one so that in this case we'd want to go

01:03:13

our next part of our triplet would be that end index. And then we also need a factor in

01:03:23

our index. So we apply something like minus i times two because we're looking at two items

01:03:35

each time on the right side when it is sorted. And then finally our last item would be sorted

01:03:41

piles end index minus i times two. That same thing is before minus an additional one,

01:03:52

just to get that offset one. So that would be what's consisted in our triplet.

01:03:58

I can make this a little smaller. You see it looks like something like that. But let's think about,

01:04:03

do we need every item in this triplet? I don't think that we do because we only care about our own

01:04:08

individual pick. So really all we care about is this last pick right here because that's what

01:04:15

we're going to take every time. That's what we're going to get based on these rules in the problem.

01:04:20

And I think that these last two things are right. We'll probably have to play around and print

01:04:25

some stuff out to be sure. But we're going to go ahead and say our value equals instead of doing

01:04:33

this whole triplet, we'll just do this last part. So I'm going to just copy this and paste that in.

01:04:44

And so let's think. So on the first iteration, our index index is the last item in the array.

01:04:51

So if we had six items, our end index would be five. Five minus i times two. i is zero in the

01:04:58

first case. So that's minus zero. So we're still at five minus one would give us four. And that

01:05:05

sounds correct. Next iteration would be, I would be one, that would give us five minus

01:05:18

two, which would be three for this index minus one, which would give us this one right here.

01:05:25

Assuming that this is sorted, I'm kind of just using this array down here in the bottom left,

01:05:29

just to show index and think about my head, the index values here. So our value,

01:05:35

equals this. And really what we could do is just keep track of account. So our count equals zero.

01:05:43

And so, or maybe I could even say our value equals zero. And so every time we get a new item in the

01:05:53

triplets, we do plus equals our value. And so once we get through this iteration, we can just go ahead

01:06:00

and return our value, I believe. Let's run code.

01:06:11

Float value cannot be interpreted as and integer. Why is that giving me an integer? I want this

01:06:21

to be an int. So I'm guessing this division is being interpreted as a float for whatever reason.

01:06:30

We might be able to do a floor divide to give us always an integer. Not positive. Let's try.

01:06:36

It should always be divisible. We know that from the constraints, but I guess it's just kind of how

01:06:41

Python implementation works here. Okay, we get that working. We can also try these other test cases.

01:06:48

Let's try two, four, five. And let's try this one right here.

01:06:59

Both of those are working. Cool. So I'm thinking that this solution works.

01:07:10

And realistically, you know, we could even make this smaller number of values, but I think it's

01:07:16

fairly neatly written out right now. I guess the only confusing part would be this what we're

01:07:23

taking it from. But hopefully the steps that I walk through to get to this point make sense.

01:07:29

with that whole triplet idea. So let's go ahead and submit our code.

01:07:38

Accepted. But it is kind of slow compared to some solutions. So I wonder if there's something

01:07:45

that we're missing here. I'm thinking maybe just the way we're iterating over here is kind of confusing.

01:07:53

Or maybe we have to do these calculations every time. So right now our solution is O of n log n.

01:08:04

Is there anything we can do better? I feel like n log n is realistically like one of the best you

01:08:10

can do unless you really want to like do something clever with a bucketing type solution

01:08:15

and knowing that your piles can only have range from one to 10,000 here.

01:08:31

Let's look at the hints. Which pile of coins will you never be able to pick up? Well,

01:08:37

it's from the left. I think we've already covered that in number two.

01:08:42

Okay, yeah, we covered both of these things. I'm just wondering about the implementation.

01:08:49

Honestly, I'm happy with this solution. So let's just look at the discussion to see if there's

01:08:54

anything better.

01:09:12

This scene is the exact same as what we're doing.

01:09:24

Yeah, so if we look at this solution, realistically,

01:09:54

what we can do is just do a smarter summation of things. Instead of doing a for loop, do something

01:10:01

like this. And that will be quicker. But the algorithm all seems the same.

01:10:10

Okay, we're going to do 1472 design browser history. And I really like problems like this.

01:10:18

From my own experience, if I'm interviewing candidates, I love to give them a problem like this

01:10:23

where you're actually building out a class or something. Because it really is more of a

01:10:28

realistic feel for what their software engineering skills are. So I usually in an interview

01:10:35

process would give one of these types of problems and maybe one algorithms type problem to get a

01:10:41

full feel. But I'd probably be more closely thinking about how they work on their classes and

01:10:47

whatnot than the algorithms questions. The algorithms question tells me that they're a good problem

01:10:53

solver. This type of question tells me that they have good software design principles.

01:10:58

So that's good to keep in mind with a problem like this. Okay, so what does this problem say? Well,

01:11:04

we're we have a browser and we always start on the home page. And in this browser, we can also

01:11:10

visit another URL as well as go back and forward in history, a certain number of steps. And so

01:11:18

we need to implement all of the details there that allow us to have a browser such as this. So

01:11:24

we're implementing what allows us to kind of keep track of the browser history. So what we need

01:11:31

to do is first and just be able to initialize it with string home page that gives it that first

01:11:38

item in the browser history. We need to be able to visit new things which clears up all of the

01:11:47

forward history. So that's good to know too. We need to go back a number of steps. So the couple

01:11:57

details there and it needs to be able to go forward a couple of steps until it returns to the most

01:12:04

last item that we've looked at the most recent item. The examples here, I would say like

01:12:13

probably did details here are going to be more clear than looking at the example.

01:12:19

Basically behind the scenes how they're testing this is using info like this.

01:12:26

But kind of following this trend can get you understanding what's going on.

01:12:34

So let's just kind of work off of this and see how we do. And then if we need to look at the

01:12:41

examples a little bit more closely we can do that. So I mean obviously it makes sense to start

01:12:47

with this initialization step. How do we get it to be able to be initialized with a home page?

01:12:55

And so let's keep the start thinking about the different data structures we might need in a

01:12:58

problem like this. The biggest thing that I'm thinking about is that we need to keep our history and

01:13:04

something. So it makes sense to me to keep that in an array and Python. So I'm thinking right here what

01:13:12

we'll do is that we'll initialize this home page into something that we call history. So we're

01:13:23

going to say self.history equals a list with just the home page in it. That's how we're going

01:13:31

to think about history. And now all these other functions in the class method they need to access

01:13:37

self.history and do certain things with it accordingly. So visit what does this visit do?

01:13:45

Well visit would append to our history on top of it. So what we would want to do here

01:13:53

is just do self.history.append whatever the URL is that we're now visiting.

01:14:03

Another thing to keep in mind here is that it says that it clears up all the forward history.

01:14:09

So if we are at certain index in this history then we know we should clear all the

01:14:17

so let's say we had like site one site two after the home page and we are currently on site one

01:14:27

and we wanted to visit a new URL. Well that's saying that this would be forward history and it's

01:14:34

saying that if we were on site one then we would want to go ahead and just say that this is like

01:14:40

the new URL and we'd have to clear everything. Let's think about that. How do we want to implement

01:14:46

that and clear all the stuff? Well I think what might make sense is to also keep track of our

01:14:57

current index. Maybe you would make this like a private method like current index. Sometimes you use

01:15:03

that a little underscore here to kind of signify this is like private to the browser history.

01:15:10

It's up to you how you want to go about this. I'm going to just say self.current index

01:15:15

equals well it's going to equal zero to start with just the home page. So when we append our URL

01:15:25

our new current index is going to be whatever it was so self.current index equals plus equal one

01:15:38

because we're going to take whatever we had before add one to that. We might not want to append

01:15:44

this URL right away because that would be a pending it on top of existing history. So instead

01:15:53

maybe let's say we update the current index first and then basically say that our history is now

01:16:05

equal to what it was before. So self.history from the start. So from zero to whatever the current

01:16:18

index is now. So self.current index. So that will allow us to keep everything that was already

01:16:28

there because and then we can go ahead and do self.history.append the URL. So I think this

01:16:38

would capture then the two things that we need to do here which is clear up the forward history

01:16:43

will do that first and then append the new URL as the head of the forward history. Now let's do

01:16:52

back. We'll back would just move our current index. So basically what we want to do here is do

01:17:00

self.current index is going to equal and let's be clever here. We can move steps back in history.

01:17:11

If you can only return x steps in history and steps is greater than x, you will return only

01:17:18

x steps. So it's basically like we can't go beyond the zero of index when we're going back.

01:17:24

So what we can think about here is we could do return the max of zero and steps are of and self.current

01:17:36

index minus steps because if we were we had a list of let's say site one, site two, site three,

01:17:50

site four. And let's say we were on index three and we wanted to go back two steps. Well then in

01:18:00

this case we would go index two, index one. That would be two steps. This would return one,

01:18:10

max of zero and one would allow us to get that right current index. So I think we're good there and

01:18:17

we don't have to update the history at all in that case. Similarly for forward we can do self.current

01:18:25

index equals, well it can't go farther than the max index. So we could say the min of

01:18:37

whatever the length of our history is. So self.history.

01:18:47

And then the last index there would be that minus one. Otherwise we would go just like in the last

01:18:55

one we do self.current index and this time we would probably plus the steps. Plus steps.

01:19:06

If I make this a little smaller it would be more nicely on a single line and you could change up

01:19:13

these names if you wanted to. But I honestly think okay and then these two steps were back and

01:19:19

forward we need to return the current URL that we're at. So that would be return self.history

01:19:31

of self.current index and this would be return self.history self.current index.

01:19:44

And this returns nothing and this is just the initialization method. I think that this is good. Let's

01:19:51

give it a shot.

01:19:56

Accept it. Look at that. Nice. And I think we can go ahead and submit.

01:20:06

Accept it. And it runs with pretty good memory usage pretty fast. And this type of a problem

01:20:13

as long as your solution is not particularly slow it's not doing anything obnoxious.

01:20:22

I would be more concerned about making this nice and readable than trying to

01:20:28

make it highly performant. And you would want to explain to whoever's interviewing you

01:20:33

like this is what you're doing. This is why you're doing it. If you find that performance is an issue

01:20:38

you know you could do some additional stuff to kind of speed up things. With that my kind of final

01:20:44

recommendation here would be comment your code as necessary or just clean up things as necessary.

01:20:51

Is this if someone didn't hear you walking through the problem could they understand what you're doing

01:20:57

here? And for some of the things that I implemented I think that it's pretty straightforward but

01:21:06

there might be some details here that aren't clear without an explanation. So like the one

01:21:13

line I'm really thinking about right now is that it's not immediately clear to me that this

01:21:21

clears forward history. So maybe I'd add a comment like clears forward history here right here

01:21:31

and then it would be a little bit more straightforward that okay you know you're adding one because

01:21:37

we're visiting a new site we're clearing the forward history and then add the new site.

01:21:44

Hopefully this probably made sense. Hopefully the solution made sense. If you have recommendations on

01:21:50

how this could be improved definitely let me know in the comments. And also just keep in mind like

01:21:56

this is another like designing classes and object-oriented programming. This is another great skill

01:22:02

set to have in an interview setting no matter if you're software engineer data scientist etc.

01:22:08

It's really nice to be able to see that someone can structure code in a succinct and understandable way.

01:22:17

So usually you're gonna have to do a problem like this as well as an algorithms type question.

01:22:25

And you know the better you can do in both areas the better case you're making to the interview

01:22:30

or that you're a good hire candidate. All right let's do problem 1110 delete nodes in return

01:22:36

forest. This is a medium problem. So we're given the root of a binary tree and each node in the

01:22:43

tree has a distinct value. After deleting all the nodes with a value in some list 2 delete we are

01:22:50

left with a forest and then our goal is to return the roots of the trees in the remaining forest

01:22:57

and we can return the result in any order. So to make that clear we see that we have a tree that

01:23:04

looks something like this. We see that each of these nodes has its own unique value and what we're

01:23:10

asked to do is delete a couple of those nodes. Particularly in this example 3 and 5. So it's

01:23:15

basically saying if we deleted node 3 and we deleted node 5 what are the resulting trees we're

01:23:21

left with. So as we can see if we delete a node 3, delete a node 5 we'd have one part of the tree

01:23:27

that would be 1, 2, 4 and this part of the binary tree would be null. And then because we deleted

01:23:35

that 3 you can be left with these two just single node roots 6 and 7 and that's what's our output.

01:23:43

So this is what we're trying to do in this problem. So when we're doing any sort of problem that

01:23:48

involves trees usually one of the things I start thinking about is traversing through the tree.

01:23:54

There's a couple different ways to do this. You can kind of do like an in order traversal

01:23:59

which would be like 1, 2, 4, 1, 2, 5, 1, 3, 6, 1, 3, 7 which would be an implementation of depth

01:24:08

for search. But in this case I think that really we want to work top down so something like 1, 2, 3,

01:24:15

4, 5, 6, 7. And the reason we want to work top down is I think that it'll be easier as we delete

01:24:22

nodes to kind of just be going from top to bottom and not have to like keep track of those deleted

01:24:29

nodes throughout the entire process. So how would we implement breadth for search? That's kind of

01:24:35

of the first thing I'm thinking about it. So I think when we start writing this solution I'm going

01:24:40

to just start with writing out something that could go through this entire tree and then we'll

01:24:47

modify that breadth for search to delete nodes and kind of return of result accordingly.

01:24:53

You can implement breadth for search using a queue which means that the first nodes you put

01:24:58

into some sort of queue are the first nodes that you take out of that queue. So we can imagine

01:25:05

defining some sort of queue and well to start off the queue would just be whatever root we have

01:25:13

so we could say root and then we could do something like while queue and so this would mean

01:25:20

it's basically while queue is not empty. We can pop off the root nodes so our node is going to be

01:25:27

equal to queue dot pop and then we want to look at for this given node we want to look at its left

01:25:37

child and its right child. So we could imagine we would add onto our queue the left child as well

01:25:48

as the right child. And given this is first and first out we don't want to pop off the end

01:25:58

really we want to pop off the beginning so that for future iterations we're always making sure we

01:26:04

grab the thing that was added to the queue first so we'll do we can do that like this and Python

01:26:12

okay and basically let's see something I think if we printed node dot value I guess it's node dot

01:26:23

value looking at this class implementation up here node dot value we could see that this would be

01:26:30

like a valid traversal of the tree so I'm just thinking about traversing the tree and then we'll

01:26:34

modify our breadth for search and this is typically with these tree problems you're taking some sort

01:26:39

of thing that you know about that data structure and then modifying it to fit whatever the problem

01:26:46

requests so let's just run what this does so as we can see for the example we're given here

01:26:56

this does go ahead and print 1234567 so that's good to know now what do we need to do

01:27:03

really the question is what do we need to do to change this so that we can delete nodes

01:27:09

and so to do this I would think about what items do we need to keep track of well one thing that

01:27:15

we need to keep track of is all the distinct roots so we'll have to implement that into our code

01:27:20

somehow and then the other thing is for a specific tree we need to modify that tree so that

01:27:26

any deleted nodes get updated in that tree accordingly so like if we had the node one here we need

01:27:33

to make sure that we said set node like the right node of this one to be none because it gets deleted

01:27:42

here so there's kind of two different tasks going on let's take care of just updating a

01:27:48

existing tree first so basically what we can think about is we're not going to automatically

01:27:54

append node.left or node.right to our queue we're going to first check to see if

01:28:00

node.left or node.right even exists in the first place so we can do something like

01:28:09

if node.left so if that's not none then we want to keep going down that part of the tree when

01:28:19

it continue our search so we can do a queue so this is basically what we're already doing before

01:28:24

but it's just we're adding an additional check that make sure that we actually have that

01:28:28

child defined so queue.append node.left and then the second thing we want to do is

01:28:35

for whatever tree we're looking at so for the tree with root we want to just make sure that

01:28:40

we should be counting this node that we're traversing down as part of that original root tree

01:28:48

and the only way we wouldn't include it is if that node happened to be deleted so let's say

01:28:54

if node.left.val is in so in two delete

01:29:07

well then we would want to set node.left equal to none so basically for the root node that we're

01:29:16

looking at if we find that its left child should be deleted then we ultimately just want to

01:29:21

remove that from the tree because now we've gotten to like a disjoint root or disjoint node

01:29:31

so we can repeat that with node.right so I'm just going to copy and paste

01:29:39

so node.right queue.append node.right if node.right.val is in two delete then node.right

01:29:51

equals none so that's just basically seeing that's basically taking care of this aspect of

01:30:00

things we are properly setting something in the case that we have deleted nodes so if I run the code

01:30:07

here and then I guess once we're done with the queue we could just

01:30:15

let's just to start return root and see how we've updated the tree

01:30:30

so as we can see by doing this so as we can see by doing this right here

01:30:39

we've updated the tree properly like we like we have here so let's look at that one our time

01:30:52

but now we need to figure out how do we capture those disjoint nodes so that's the big question

01:31:01

well maybe what we should do is let's keep track of our output kind of as a global parameter of

01:31:07

this class so I'm going to say when there's initialized this class to have an empty output list

01:31:16

by default and basically anytime we call this delete nodes method let's add

01:31:26

the whatever root we're looking at so in this case this root so self.output.append root

01:31:40

and so what do we accomplish by doing that well what we can smartly do is imagine that we had

01:31:47

in our nodes to delete from this tree up here in the example we were told to delete node 1

01:31:55

well if the node we were looking at was deleted then we would know we would have root nodes at 2 and 3

01:32:02

assuming that 3 wasn't actually in this list just assuming one was the only thing we're deleting

01:32:07

so basically you know that 2 and 3 are like the root nodes of the disjoint trees

01:32:14

well what we can do is basically another if statement so if node.val

01:32:21

in to delete so basically this is a different scenario if that value is in to delete then let's

01:32:32

recursively call delete nodes on the left and right child so we have to call self.delete nodes

01:32:42

on root her node.left and self.delete nodes on node.right and because of the way the functions

01:32:54

define will also pass in to delete to both of these things and so what we get with this recursive

01:33:03

call to delete nodes is that now the left and the right nodes of a deleted node will get

01:33:13

appended to our output and I guess one thing we should change right here is we need to only

01:33:19

append if root.val is not in to delete

01:33:34

okay so now if we look at our solution if we I think just run this

01:33:40

uh none type okay I think one thing we also need to do is double check that

01:33:55

node.left is defined and double check that node.right is defined before we try to call that

01:34:04

recursively otherwise we'll get an error when we try to do something like this

01:34:10

okay okay oh and then we need to now instead of returning root here we would want to do self.output

01:34:26

we want to return the output so return self.output

01:34:41

look at that we got it um

01:34:44

I think that looks good basically if we're need if we're looking at a node that needs to be

01:34:55

deleted then we want to just add um the left and right nodes as like new distinct roots and we

01:35:02

recursively call the function on that otherwise we just keep looking at the the tree the root that

01:35:10

currently examining and just keep adding things and just updating that existing tree accordingly

01:35:15

to get something like this so let's go ahead and submit this

01:35:22

oh no wrong answer hmm okay so we see here in this example we get an extra five node

01:35:32

that we shouldn't so that tells me that something is getting a pended twice for whatever reason

01:35:53

I think it's basically because we shouldn't both call a recursive call and continue this process

01:36:01

so I'm thinking if we add an else statement and append these inside I think that will fix

01:36:11

things so that we don't traverse down on the left or on the right multiple times when we've done

01:36:17

it already here

01:36:26

accepted

01:36:29

um not great um memory usage or runtime but let's just let's let's figure out what our runtime is

01:36:36

here so the way that we're doing this is we are looking at the root node we're doing both for a

01:36:46

search we're doing a modified breaths for search and breadth for search is going to have

01:36:52

a runtime of the number of edges plus the number of nodes and in this case it's a one-to-one

01:37:03

correspondence so it's really just a runtime of how many nodes we have so you could think oh

01:37:09

then and even though we're doing this you know I guess the thing I don't love about this solution

01:37:16

is that there's a lot of if else type stuff I guess one way to I mean it's not terrible

01:37:25

I think it's readable

01:37:28

I know that this solution is oh of n because we we modified that breadth for search so I'm happy

01:37:35

honestly I feel like most of these answers will just be like slightly smarter versions of what we

01:37:42

have here so I'm fine not changing things what I'd recommend is feel free to go to the discussion

01:37:49

check out like the top solution this guy lead 215 he's pops up all over place he does a great

01:37:56

job explaining these problems

01:38:05

and honestly this looks very similar to what we're doing but it's just being a little bit smarter

01:38:13

using this helper method instead of what we're doing with if else is so I'm happy with our solution

01:38:23

all right that's all the problems we're going to do in this video hopefully you found this useful

01:38:28

if you did enjoy it it would mean a lot if you throw it a thumbs up and also subscribe if you

01:38:32

have it also I just created a new second channel huge announcement right now but I'm going to be

01:38:41

posting a lot more frequently on that channel and I'll probably be doing a lot of these types of

01:38:45

problems so if you enjoyed this video definitely check out the second channel I'm going to start

01:38:50

posting on that once I hit 1000 subscribers and I will definitely take requests for additional

01:38:57

problems I could solve there whether that be lead code problems or problems from another site

01:39:03

if you have any questions about the contents of what we did in this video let me know down in the

01:39:06

comments but until next time everyone peace out

00:00

Introduction to Elite Code Problems

00:15

Importance of Problem-Solving Skills

00:51

Approaching Each Problem

01:57

Example Problem: Goal Parser Interpretation

06:05

Example Problem: Destination City

15:53

Implementing the Initial Solution

16:09

Understanding Time Complexity

16:21

Debugging the Code

17:41

Refining the Solution

20:15

Exploring a Better Approach

28:18

Handling Duplicates in Counting

30:47

Optimizing for Performance

33:14

Understanding Binary Search

45:00

Implementing Binary Search Algorithm

46:17

Understanding Palindromes in Strings

49:58

Tracking Odd Characters

51:03

Testing Edge Cases

51:52

Adding Logic for Final Count

55:06

Understanding the Coin Problem

58:01

Optimal Strategy for Coins

01:06:10

Float Division in Python

01:07:29

Optimizing the Code Solution

01:10:09

Understanding Browser History Implementation

01:11:10

Class Design for Browsers

01:20:05

Handling Browser Navigation

01:23:40

Tree Traversal Techniques

01:24:35

Implementing Breadth-First Search

01:27:23

Handling Node Deletion

01:29:40

Updating the Tree Structure

01:38:20

Final Code Submission and Review

00:22

How can elite code problems enhance coding skills?

02:21

What is the solution for the Goal Parser Interpretation problem?

06:33

How do we identify the destination city in a path problem?

07:21

What is the significance of counting outgoing paths?

16:15

How can we improve code efficiency beyond O(n^2)?

20:37

What's the value of sorting in solving this problem?

27:00

How does modifying the index improve our solution?

24:06

What role does a dictionary play in counting values?

28:30

How can we improve counting duplicate numbers effectively?

30:54

What techniques lead to a faster, optimized solution?

33:31

Why is binary search crucial for sorted arrays?

46:30

What's the essence of finding the longest palindrome?

50:00

How do we efficiently track odd character counts in strings?

51:05

What logic helps manage edge cases in our count?

58:05

How can we optimize our approach to selecting coins?

01:06:11

Why does float division lead to integer issues in Python?

01:07:39

How can we optimize our current solution for better performance?

01:10:10

What are the key components for a browser history implementation?

01:23:45

What are the key traversal methods for tree problems?

01:24:39

How do we implement a breadth-first search for tree nodes?

01:27:15

What adjustments are needed for deleting nodes in a tree?


DebuggingPalindromeAlgorithmPython (programming language)Data structureEdge case

Description

In this video, we'll explore how elite code problems can help you improve your problem-solving skills. We'll start with relatively easy problems and gradually increase the difficulty level. You can try to solve each problem on your own before watching the solution in the video. The goal is to train your problem-solving muscle and enhance your coding skills. We'll also discuss the general way to approach each problem and provide links to the problems in the description. By the end of this video, you should have a better understanding of how to improve your problem-solving abilities and become a more skilled software engineer or data scientist.