-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNumberofIslands.py
482 lines (390 loc) · 19.6 KB
/
NumberofIslands.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
#Given an m x n 2D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands.
#An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.
#Python3 solution:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(down, right, grid):
if down < 0 or down >= len(grid) or right < 0 or right >= len(grid[0]) or grid[down][right] == '0': #skipping this block if line 24 equals 1 on the 1st call iteration
return #out of bounds, so return out of the function
grid[down][right] = '0' #sinking the 1 into a 0 so we don't double count it - we go here from line 24
dfs(down + 1, right, grid) #and then we call dfs again to go down one tile and then we boundary check this new tile in line 14
dfs(down - 1, right, grid)
dfs(down, right + 1, grid)
dfs(down, right - 1, grid)
count = 0 #we will return this at the end
for down in range(len(grid)): #right - rows - flipping the ordering of rows vs. columns also works!
for right in range(len(grid[0])): #down - columns
if grid[down][right] == '1': #we found a 1, so we found atleast 1 island - if grid[I[j] == 1, then we increment count, and then we call dfs, and we skip the if block and directly call grid[I][j] = '0'? and then we execute dfs(I + 1, j) - so we are boundary checking dfs(I + 1, j) aka the new tile, not the old tile with 1 we already visited
count += 1
dfs(down, right, grid) #we must do a boundary check before sinking that 1 into a 0!!!!!!
return count
#sequence 22 > 21 (this is due to backtracking as we need to check all adjacent cells where it left off) - This backtracking ensures that every part of an island (group of connected '1's) is visited and marked, and no potential connections are missed. Once an island is fully explored and marked, the algorithm continues to search for other islands in the grid.
#sequence 21 > 15 > 16 > 18 > 19 > 15 > 16 > 17 > 19 (we have to start over from the 1st recursive call all over again because we have to explore all possible directions for that one direction!!!! islands can be connected to other islands!)
#sequence 16 > 18 > 19 > 15 (this just means that the direction we are going is fine and is part of the same island, so sink the 1 tile we found to 0 and keep going in the same direction)
#sequence 19 > 15 > 16 > 17 > 20 (this means the tile we visited was either out of bounds or was already visited, so move in another direction to find more 1s to sink IN THE SAME ISLAND)
#sequence 22 > 15 > 16 > 17 > 22 > 25 (this means we've exhausted all directions and are looking for a new island)
#after a DFS call completes,
#the algorithm returns to the for loop to continue scanning the grid for any other '1's that might represent a different, unvisited island.
#The DFS call is responsible for an entire island, and the loops are responsible for ensuring every cell in the grid is checked. - THIS IS THE KEY - and, remember that whether rows or columns loop comes first dosen't matter, so we could be returning to j loop or i loop if you wanted to flip the order of loops
#the 1st time this if statement in line 14 is evalvulated, it will never execute the inner block because it came from the if statement where grid[I]jj] was equal to 1 from line 24. It's only after one of the recursive calls below executes that this if block can become true?
#return
# grid[i][j] = '0'
#dfs(i + 1, j, grid) #recursive calls below refers to one of these 4 in lines 17 - 20
#if you wanted to flip the order of loops, your solution would be:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(down, right, grid):
if down < 0 or down >= len(grid) or right < 0 or right >= len(grid[0]) or grid[down][right] == '0':
return
grid[down][right] = '0' #the 0 check here is to be sure that either we haven't revisited a cell because the previous cell was already flipped from 1 to 0 or we are not stepping onto water - 0 means it can't be part of the same island in either one of these two scenarios
dfs(down + 1, right, grid)
dfs(down - 1, right, grid)
dfs(down, right + 1, grid)
dfs(down, right - 1, grid)
count = 0
for right in range(len(grid[0])): # Iterate over columns first
for down in range(len(grid)): # Then iterate over rows
if grid[down][right] == '1': #the dfs function looks for all adjacent 1s aka 1s that comprise of the same island while the loops look for non adjacent 1s that would signal a new island and would increase count
count += 1
dfs(down, right, grid)
return count
#time, space, and memory complexities are the same whether we iterate over rows first or columns first
#Time Complexity
#The time complexity of this algorithm is primarily determined by the number of cells in the grid and the number of times each cell is visited. Let's assume the grid is of size m x n, where m is the number of rows and n is the number of columns.
#Outer Loops: The outer for loops iterate through each cell of the grid once. Therefore, the total number of iterations here is m * n.
#DFS Calls: For each cell that is a '1', a DFS (Depth-First Search) is performed. In the worst case, DFS can visit each cell in the grid once (if the entire grid is filled with '1's). However, each cell is visited only once across all DFS calls because the visited '1' cells are marked as '0', preventing revisits.
#Combining these factors, the worst-case time complexity is O(m * n).
#Space Complexity
#The space complexity of the algorithm is determined by the maximum size of the call stack during the recursive DFS calls.
#Call Stack in DFS: In the worst case, where the grid is filled with '1's, the DFS could potentially go as deep as the total number of cells in the grid before backtracking. However, this is a highly unlikely scenario since it would require a pathological case of a grid where DFS traverses the entire grid in a snake-like pattern. A more realistic worst-case scenario would involve a DFS depth equivalent to the maximum of m and n.
#Therefore, the worst-case space complexity is O(max(m, n)).
#Memory Complexity
#In terms of memory usage, the algorithm modifies the input grid in place and does not use any additional significant memory that scales with the size of the input. Thus, the memory complexity is essentially the same as the space complexity, O(max(m, n)).
#Summary
#Time Complexity: O(m * n)
#Space Complexity: O(max(m, n))
#Memory Complexity: O(max(m, n))
#11/24 refresher:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def search(rows, columns, grid):
if rows < 0 or rows >= len(grid) or columns < 0 or columns >= len(grid[0]) or grid[rows][columns] == '0': #if we are on water, then return. If 1, then keep going to the next tile because it's part of the same island.
return
grid[rows][columns] = '0' #set any 1s to 0 so we don't double count - the if block will always be false, and this line will always execute the first time this recursive function is called
search(rows + 1, columns, grid)
search(rows - 1, columns, grid)
search(rows, columns + 1, grid)
search(rows, columns - 1, grid)
count = 0
for rows in range(len(grid)):
for columns in range(len(grid[0])): #cannot be grid[1] because it would fail test case [["1"]], so grid[0] and grid[-1] work!!!!!!!!!!!!!!!!!
if grid[rows][columns] == '1':
count += 1
search(rows, columns, grid)
return count
#12/17/23 refresher:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def f(row, column, grid):
#the grid[row][column] checks if we are on water
#len(grid[-1]) works for test case grid = [["1"]]
if row < 0 or row >= len(grid) or column < 0 or column >= len(grid[-1]) or grid[row][column] == "0":
return
grid[row][column] = "0" #will always execute on the 1st turn. this is for making sure we don't double count islands since any 1s in 4 directions are part of the same island, so flip them to 0
f(row + 1, column, grid)
f(row - 1, column, grid)
f(row, column + 1, grid)
f(row, column - 1, grid)
res = 0
for row in range(len(grid)):
for column in range(len(grid[-1])): #works for test case grid = [["1"]]
if grid[row][column] == '1':
res += 1
f(row, column, grid)
return res
#1/6/24 refresher:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, row, column):
if row < 0 or row >= len(grid) or column < 0 or column >= len(grid[0]) or grid[row][column] == "0":
return
#we are only given 1s and 0s, so if the grid dosen't contain a string 0 and is in bounds, then it must be a 1 and is part of the same island, so turn that 1 into a 0 so we don't keep calling that 1 tile forever
grid[row][column] = "0" #avoid infinite recursion
dfs(grid, row + 1, column)
dfs(grid, row - 1, column)
dfs(grid, row, column + 1)
dfs(grid, row, column - 1)
res = 0
for row in range(len(grid)):
for column in range(len(grid[0])):
if grid[row][column] == "1":
res += 1
dfs(grid, row, column)
return res
#1/14/24 refresher:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, r, c):
#won't be true in 1st turn bc we already found start of island (a 1 string cell that is in bounds)
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
grid[r][c] = "0" #prevents infinite recursion
dfs(grid, r + 1, c)
dfs(grid, r - 1, c)
dfs(grid, r, c + 1)
dfs(grid, r, c - 1)
res = 0
#vertical
for r in range(len(grid)):
#horizontal - wide
for c in range(len(grid[0])):
if grid[r][c] == "1":
res += 1
dfs(grid, r, c)
return res
#1/21/24 refresher:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, r, c):
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
#prevent infinite recursion while looking for parts of the same island
grid[r][c] = "0"
dfs(grid, r + 1, c)
dfs(grid, r - 1, c)
dfs(grid, r, c + 1)
dfs(grid, r, c - 1)
res = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
res += 1
dfs(grid, r, c)
return res
#1/24/24 refresher:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, r, c):
#if we are out of bounds or see water, which won't be true the 1st time this conditional is evaluated, we return nothing back to the parent caller
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
#set it equal to 0 so we don't double count it and run into infinite recursion
grid[r][c] = "0"
dfs(grid, r + 1, c)
dfs(grid, r - 1, c)
dfs(grid, r, c + 1)
dfs(grid, r, c - 1)
count = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
count += 1
dfs(grid, r, c)
return count
#1/28/24 review:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
#grid is a list of list of strings with cell values of either "1" or "0" as string
#any 1 we see, we know we can count that as 1 island, and since all the 1st adjacent to that 1 also count as the same island, we want to find them so we don't overcount islands, so we do that with this recursive call
def dfs(grid, r, c):
#this if block won't be true in the 1st turn that it executes since we already determined that atleast one island exists
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
#if we are out of bounds of the grid or find water, then we return nothing back to the parent function call that caused it
return
#we found another 1, so we have to set it to 0 so we don't double count it
grid[r][c] = "0"
dfs(grid, r + 1, c)
dfs(grid, r - 1, c)
dfs(grid, r, c + 1)
dfs(grid, r, c - 1)
count = 0
for r in range(len(grid)):
#this is the width since we know that all lists of lists are of the same width
for c in range(len(grid[0])):
if grid[r][c] == "1":
count += 1
dfs(grid, r, c)
return count
#2/3/24 refresher:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, r, c):
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
grid[r][c] = "0"
dfs(grid, r + 1, c)
dfs(grid, r - 1, c)
dfs(grid, r, c + 1)
dfs(grid, r, c - 1)
count = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
count += 1
dfs(grid, r, c)
return count
#2/8/24 - my own solution python3:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, r, c):
# grid[r][c] != 1 works too since we are not on an island meaning we are on a 0
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] != "1":
return
grid[r][c] = "0"
dfs(grid, r + 1, c)
dfs(grid, r - 1, c)
dfs(grid, r, c + 1)
dfs(grid, r, c - 1)
count = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
count += 1
dfs(grid, r, c)
return count
#2/22/24:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, r, c):
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
grid[r][c] = "0"
dfs(grid, r + 1, c)
dfs(grid, r - 1, c)
dfs(grid, r, c + 1)
dfs(grid, r, c - 1)
count = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
count += 1
dfs(grid, r, c)
return count
#3/10/24:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, r, c):
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
grid[r][c] = "0"
dfs(grid, r + 1, c)
dfs(grid, r - 1, c)
dfs(grid, r, c + 1)
dfs(grid, r, c - 1)
count = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
count += 1
dfs(grid, r, c)
return count
#3/12/24:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, r, c):
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
grid[r][c] = "0"
dfs(grid, r + 1, c)
dfs(grid, r - 1, c)
dfs(grid, r, c + 1)
dfs(grid, r, c - 1)
count = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
count += 1
dfs(grid, r, c)
return count
#4/7/24:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(r, c):
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
grid[r][c] = "0"
dfs(r + 1, c)
dfs(r - 1, c)
dfs(r, c + 1)
dfs(r, c - 1)
res = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
res += 1
dfs(r, c)
return res
#5/2/24 refresher:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
#0 = water
#1 = land
def dfs(grid, r, c):
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
grid[r][c] = "0"
dfs(grid, r + 1, c)
dfs(grid, r - 1, c)
dfs(grid, r, c + 1)
dfs(grid, r, c - 1)
res = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
res += 1
dfs(grid, r, c)
return res
#5/27/24 review:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, r, c):
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
grid[r][c] = "0"
dfs(grid, r + 1, c)
dfs(grid, r - 1, c)
dfs(grid, r, c + 1)
dfs(grid, r, c - 1)
res = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
res += 1
dfs(grid, r, c)
return res
#6/22/24 review:
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(r, c, grid):
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
grid[r][c] = "0"
dfs(r + 1, c, grid)
dfs(r - 1, c, grid)
dfs(r, c + 1, grid)
dfs(r, c - 1, grid)
res = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
res += 1
dfs(r, c, grid)
return res
#8/18/24 review (exactly identical to the problem battleships in a board):
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(r, c):
if r < 0 or r >= len(grid) or c < 0 or c >= len(grid[0]) or grid[r][c] == "0":
return
grid[r][c] = "0"
dfs(r + 1, c)
dfs(r - 1, c)
dfs(r, c + 1)
dfs(r, c - 1)
count = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "1":
count += 1
dfs(r, c)
return count