I'm working on the Island Perimeter Problem from LeetCode.
I tried to use the DFS algorithm, based on LeetCode#200, to solve this problem. For example 1, the output should be 16 but my output was 10. It seemed that not all neighbor cells were counted. Can anyone see any problems with the algorithm below?
class Solution(object):
def islandPerimeter(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
# plus 4 edges for the first cell
cnt = 4
def dfs(grid,i,j,cnt):
# return if the cell is out of the grid or equals to 0
if not 0 <= i < len(grid) or not 0 <= j < len(grid[0]) or grid[i][j] == 0: return
# put counted cell to 0 for avoiding duplicate count
grid[i][j] = 0
# plus 2 edges for each neighbor cell
cnt += 2
# search all neighbor nodes
dfs(grid,i-1,j,cnt)
dfs(grid,i+1,j,cnt)
dfs(grid,i,j-1,cnt)
dfs(grid,i,j+1,cnt)
return cnt
for i in range (len(grid)):
for j in range (len(grid[0])):
# find the first node equals to 1
if grid[i][j] == 1:
cnt += dfs(grid,i,j,cnt)
return cnt
from typing import List
# perimeter means count the edges that touches the water
class Solution:
def islandPerimeter(self,grid:List[List[int]])->int:
ROWS,COLS=len(grid),len(grid[0])
visited=set()
def dfs(r,c):
# base case we are out of bound or we reached water
if r<0 or r==ROWS or c==COLS or c<0 or grid[r][c]==0:
# that means we are on the border. so we return 1
return 1
if (r,c) in visited:
return 0
visited.add((r,c))
return dfs(r+1,c)+dfs(r-1,c)+dfs(r,c+1)+dfs(r,c-1)
# we start from land portion
for r in range(ROWS):
for c in range(COLS):
if grid[r][c]==1:
return dfs(r,c)
You can simplify your code to the following
def island_perimeter(grid):
island_size = sum([i for l in grid for i in l])
perimeter_size = island_size * 2 + 2
# correct for inner tiles
for i in range(len(grid) - 1):
for j in range(len(grid[i]) - 1):
if (grid[i][j] == 1 and grid[i+1][j] == 1
and grid[i][j+1] == 1 and grid[i+1][j+1] == 1):
perimeter_size -= 2
return perimeter_size
Given the problem description in the link you provided, both the island and the surrounding see are connect spaces. There is only one island and there are no lakes.
You can show by induction that an island of size one has a perimeter of four. And by adding a piece of island (size 1) to the some arbitrary but fixed sized island (size n) it will extend the perimeter by 2. As a correction, whenever an island of size 2x2 is created the perimeter size needs to be decreased again by two. Hence the equation given in the function above.
I am given an (nxm) matrix of positive and negative integers.
The goal is to try and convert all negative numbers into positive ones, and if this is possible, return the number of passes of the matrix required to do this, otherwise return -1.
Here are the rules:
You can only convert a negative number into a positive one, if the negative number has an adjacent (directly above, below, left or right) entry that is positive.
If you convert a number from negative to positive in a certain pass, you cannot then use this (recently) turned positive number to turn other negative numbers positive- at least for this pass.
I have written an accepted solution to the problem, but I can't seem to work out the Time Complexity of the solution.
My solution:
Create a Boolean matrix to mark the negative entries that have been turned positive in the current pass (we will reset this matrix after each pass)
Iterate over all entries of the matrix
For every negative number we stumble across check all 4 of its adjacent entries and see if there are any positive entries.
If so, convert it to positive and mark it in the boolean matrix.
Increment number of passes each time we iterate over the whole matrix.
We are done when we iterate over the entire matrix and have not made a single change to it (i.e. conversion from negative to positive).
If there are any negative entries left, return -1, otherwise return the number of passes.
I can't seem to think of a worst case- any suggestions on the time complexity of this solution?
My initial thought are that it is O(n), where n is the size of the matrix.
For reference, here is my solution:
def minimumPassesOfMatrix(matrix):
numberOfPasses = 0
ChangesMade = True
while ChangesMade:
negToPosMarket = [[False for _ in range(len(matrix[0]))] for _ in range(len(matrix))]
ChangesMade = False
for row in range(len(matrix)):
for col in range(len(matrix[0])):
if matrix[row][col] < 0:
positiveAdjacent = checkAdjacentEntriesForPositive(matrix, row, col, negToPosMarket)
if positiveAdjacent:
matrix[row][col] *= -1
negToPosMarket[row][col] = True
ChangesMade = True
if not ChangesMade:
break
numberOfPasses += 1
if all(matrix[row][col] >= 0 for row in range(len(matrix)) for col in range(len(matrix[0]))): #notebook double for loop list comp!
return numberOfPasses
print(matrix)
return -1
def checkAdjacentEntriesForPositive(matrix, row, col, negToPosMarket):
matrixHeight = len(matrix) - 1
matrixWidth = len(matrix[0]) - 1
if not OutOfBounds(row + 1, col, matrixHeight, matrixWidth) and not negToPosMarket[row + 1][col]:
if matrix[row + 1][col] > 0:
return True
if not OutOfBounds(row - 1, col, matrixHeight, matrixWidth) and not negToPosMarket[row - 1][col]:
if matrix[row - 1][col] > 0:
return True
if not OutOfBounds(row, col + 1, matrixHeight, matrixWidth) and not negToPosMarket[row][col + 1]:
if matrix[row][col + 1] > 0:
return True
if not OutOfBounds(row, col - 1, matrixHeight, matrixWidth) and not negToPosMarket[row][col - 1]:
if matrix[row][col - 1] > 0:
return True
return False
def OutOfBounds(row, col, matrixHeight, matrixWidth):
return row < 0 or col < 0 or row > matrixHeight or col > matrixWidth
The worst case scenario would be a positive number at one corner of the matrix with everything else being negative. This will require (n+m-2) passes to flip the opposite corner. The time complexity would be (n+m)xnxm if you go through every position on each pass.
If you use a set of coordinates instead, this can be reduced to nxm
Place the coordinates of positive values in a set. At each pass convert their negative neighbours to positive and place these former negative's coordinates into a new set for the next pass. This way, you only process each item once.
Here's a example in Python:
def makePos(matrix):
m = len(matrix)
n = len(matrix[0])
plusCoords = {(r,c) for r,row in enumerate(matrix)
for c,val in enumerate(row)
if val>0} # initial set of + coordinates
passes = 0
iterations = 0
while plusCoords: # more passes for new + coordinates
passes += 1 # count passes
nextCoords = set() # coordinates of new positives
for r,c in plusCoords: # go through this pass's coords
iterations += 1
for dr,dc in [(-1,0),(0,-1),(0,1),(1,0)]: # neigbors
nr,nc = r+dr, c+dc
if nr not in range(m): continue
if nc not in range(n): continue
if matrix[nr][nc] < 0: # flip to positive
nextCoords.add((nr,nc)) # track flips
for nr,nc in nextCoords:
matrix[nr][nc] *= -1 # update matrix
plusCoords = nextCoords # coords for next pass
return passes - 1, iterations
# passes
M = [ [10,-1,-1,-1,-1], # 0 1 2 3 4
[-1,-1,-1,-1,-1], # 1 2 3 4 5
[-1,-1,-1,-1,-1]] # 2 3 4 5 6
print(*makePos(M)) # 6 15 (6 passes,15 iterations)
Note that the for dr,dc in [(-1,0),(0,-1),(0,1),(1,0)]: loop counts as O(1) here because it does a fixed amount of work for the r,c coordinates. The nextCoords.add((nr,nc)) function is O(1) because nextCoords is a set.
I was completing this Leetcode problem: https://leetcode.com/problems/word-search/ and I randomly chose to implement the DFS iteratively with a while loop and a stack but I encountered some inconveniences when backtracking that I wouldn't normally occur if I had done the problem recursively i.e. I could only think of implementing a list (visited_index) to keep track of the indexes I had visited and pop values off to set the boolean matrix visited back to False when backtracking.
from collections import defaultdict
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
starting_points = defaultdict(list)
m, n = len(board), len(board[0])
for i in range(m):
for j in range(n):
starting_points[board[i][j]].append((i,j))
start = starting_points[word[0]]
visited = [[False] * n for _ in range(m)]
stack = []
directions = [(1,0), (0,-1), (-1,0), (0,1)]
for s in start:
stack.append((s[0], s[1], 0))
visited_index = [] # EXTRA LIST USED
while stack:
x, y, count = stack.pop()
while len(visited_index) > count:
i, j = visited_index.pop()
visited[i][j] = False # SETTING BACK TO FALSE WHEN BACKTRACKING
if x < 0 or x >= m or y < 0 or y >= n or visited[x][y] or board[x][y] != word[count]:
continue
else:
visited[x][y] = True
visited_index.append((x,y))
if count + 1 == len(word):
return True
for d in directions:
i, j = x + d[0], y + d[1]
stack.append((i,j, count + 1))
else:
stack.clear()
for i in range(m):
for j in range(n):
visited[i][j] = False
return False
I believe that in a recursive approach I could have reset the the visited boolean value back to False at the end of the function without the need to use an extra list. Does anyone have any suggestions to not introduce an extra data structure when doing an iterative DFS with a stack?
I would keep the parent node on the stack for as long there are children being processed. Then when all children have been processed and you pop the parent from the stack you'll have the right moment to also remove the visited mark for that parent.
One way to implement that idea is to put one more information in the tuple that you put on the stack: the last direction that was taken. You can use that info to look for the next direction to take, and if there is a valid direction available, push the current node back on the stack with that new direction, and then push the corresponding child on the stack. The latter gets some default value for that "previous" direction indication. For example -1.
I reworked your code to align with that idea:
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
stack = []
m, n = len(board), len(board[0])
for i in range(m):
for j in range(n):
if board[i][j] == word[0]:
# 4th member of tuple is previous direction taken. -1 is none.
stack.append((i, j, 1, -1)) # count=1, side=-1
visited = [[False] * n for _ in range(m)]
directions = [(1,0), (0,-1), (-1,0), (0,1)]
while stack:
x, y, count, side = stack.pop()
# perform the success-check here, so it also works for 1-letter words.
if count == len(word):
return True
visited[x][y] = True # will already be True when side > -1
# find next valid direction
found = False
while side < 3 and not found:
side += 1
dx, dy = directions[side]
i, j = x + dx, y + dy
found = 0 <= i < m and 0 <= j < n and not visited[i][j] and board[i][j] == word[count]
if not found: # all directions processed => backtrack
visited[x][y] = False
continue
stack.append((x, y, count, side)) # put node back on stack
stack.append((i, j, count + 1, -1))
return False
I am building a sudoku function to learn how to code in python. I seem to be creating an infinite loop with a for loop but I don't understand how. The code attempts to look at each empty square of the sudoku board and check if a value counter is allowed by the rules of sudoku. If counter is allowed the board is updated and the function moves on to the next empty square. If counter is not allowed than counter is incremented by 1 and tested again.
The issue I am having comes when counter is greater then 9. When this happens I want to look at the previous square which was empty on the original board (named puzzle) and delete the value in this square. The function than should set counter equal to the value in the previous square +1 and call itself to run again.
In essence the function is testing each empty square for possible values until it finds a value and than move on to the next square. If there are no possible values the function will back track, delete the last square and try running again.
My problem seems to happen in the else condition when counter is greater than 9. This part of the function is causing an infinite loop which prints out 'no' repeatedly.
I'm assuming that my function is getting stuck on the while loop but I'm not sure why.
puzzleBoard =[[1,2,3,4,5,6,7,8,9],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0]]
def solvePuzzle():
#start by looking at each square in the 9x9 sudoku grid and check if that square is empty (=0)
for i in range(9):
for j in range(9):
counter = 1
topX = 3*(i//3)
topY = 3*(j//3)
# while the board at index [i][j] is empty check if the value of 'counter' fits in the square and adheres to the sudoku rules
# if counter is not an allowed value increment counter by 1 and try again
while puzzleBoard[i][j] ==0:
if counter < 10:
row = all([counter != puzzleBoard[i][x] for x in range(9)])
column = all([counter != puzzleBoard[y][j] for y in range(9)])
box = all([counter != puzzleBoard[x][y] for x in range(topX, topX+3) for y in range(topY, topY+3)])
if row and column and box == True:
puzzleBoard[i][j]=counter
uploadBoard()
else:
counter = counter + 1
# if counter is larger than ten set the previous square ([i][j-1]) equal to zero, set the counter equal to one more than the previous squares value, and call the solvePuzzle function again.
else:
for k in range(i,0,-1):
for l in range(j-1,0,-1):
if puzzle[k][l]==0:
counter = puzzleBoard[k][l] + 1
puzzleBoard[k][l]=0
solvePuzzle()
return
else:
print("no")
I was able to work out the answer. There are a few issues with the code but the main problem is that in the lower else statement counter = puzzleBoard[k][l] + 1 and then the function was being called again, which resets the variable counter to 1.
I was able to fix this problem by creating a global variable countholder and modifying the else statement to say countholder = puzzleBoard[k][l] + 1
The full working code looks like this:
puzzleBoard =[[0,2,0,0,0,0,0,0,0],[0,0,0,6,0,0,0,0,3],
[0,7,4,0,8,0,0,0,0],[0,0,0,0,0,3,0,0,2],
[0,8,0,0,4,0,0,1,0],[6,0,0,5,0,0,0,0,0],
[0,0,0,0,1,0,7,8,0],[5,0,0,0,0,9,0,0,0],
[0,0,0,0,0,0,0,4,0]]
puzzle =[[0,2,0,0,0,0,0,0,0],[0,0,0,6,0,0,0,0,3],
[0,7,4,0,8,0,0,0,0],[0,0,0,0,0,3,0,0,2],
[0,8,0,0,4,0,0,1,0],[6,0,0,5,0,0,0,0,0],
[0,0,0,0,1,0,7,8,0],[5,0,0,0,0,9,0,0,0],
[0,0,0,0,0,0,0,4,0]]
countholder = 1
def solvePuzzle():
#start by looking at each square in the 9x9 sudoku grid and check if that square is empty (=0)
for i in range(9):
for j in range(9):
global countholder
counter = countholder
topX = 3*(i//3)
topY = 3*(j//3)
# while the board at index [i][j] is empty check if the value of 'counter' fits in the square and adheres to the sudoku rules
# if counter is not an allowed value increment counter by 1 and try again
while puzzleBoard[i][j] ==0:
if counter < 10:
row = all([counter != puzzleBoard[i][x] for x in range(9)])
column = all([counter != puzzleBoard[y][j] for y in range(9)])
box = all([counter != puzzleBoard[x][y] for x in range(topX, topX+3) for y in range(topY, topY+3)])
if (row and column and box) == True:
puzzleBoard[i][j]=counter
print(puzzleBoard)
countholder = 1
else:
counter = counter + 1
# if counter is larger than ten set the previous square ([i][j-1]) equal to zero, set the counter equal to one more than the previous squares value, and call the solvePuzzle function again.
else:
run_One = True
for k in range(i,-1,-1):
if run_One == True:
run_One = False
for l in range(j,0,-1):
if l == 0:
print(run_One)
elif puzzle[k][l-1]==0:
countholder = puzzleBoard[k][l-1] + 1
puzzleBoard[k][l-1]=0
solvePuzzle()
return
else:
continue
else:
for l in range(8,-1,-1):
if puzzle[k][l]==0:
countholder = puzzleBoard[k][l] + 1
puzzleBoard[k][l]=0
solvePuzzle()
return
else:
continue
solvePuzzle()
I need to place N queen pieces on a MxM board so that no two queens attack each other. The difference from the original is that the queens can only attack up to 5 squares from their location, and not the whole row, column or diagonal as in the original problem.
One idea I'm playing around in my head is to place the first Queen on the board at board[i][j], but then also place it in its 5 neighboring squares as shown in the image below.
The following snipped is the isSafe function from the original N-Queens problem.
Looking for guidance as to how to check if a queen placement is safe.
def isSafe(board, row, col):
# Check this row on left side
for i in range(col):
if board[row][i] == 1:
return False
# Check upper diagonal on left side
for i,j in zip(range(row,-1,-1), range(col,-1,-1)):
if board[i][j] == 1:
return False
# Check lower diagonal on left side
for i,j in zip(range(row,N,1), range(col,-1,-1)):
if board[i][j] == 1:
return False
return True
In summary, how do I detect if a queen is safe if I limit the queen's range to 5 squares?
There are two main approaches:
1) For every new queen check whether another ones are at horizontals/verticals/diagonals in range. Longer to check, faster to add/remove
2) Mark beaten cells with numbers: 0 for safe one, K for the cell beaten by K queens.
In this case you can easily determine if cell is safe, increment beaten cells values for new queen and decrement them if you need to remove a queen. Faster to check, longer to add/remove.
To modify code sample, restrict ranges. For example, left-bottom diagonal:
for i,j in zip(range(row, max(-1, row - 5),-1), range(col, min(N, col + 5), 1)):
if board[i][j] == 1:
return False
Here is another approach to solve the problem.
It based on a fact that two queens can clashes only and only if they share the same row or column or diagonal, so if this condition is False, it is a safe place for the queen.
Here is an implementation :
def share_diagonal(x0, y0, x1, y1):
""" Check if two coordinates share diagonal or not ? """
dx = abs(x0 - x1)
dy = abs(y0 - y1)
return dy == dx
def col_clashes(bs, c):
""" Return True if the queen at column c clashes
with any queen to its left.
"""
for i in range(c): # Look at all columns to the left of c
if share_diagonal(i, bs[i], c, bs[c]):
return True
return False
def has_clashes(the_board):
""" Determine whether we have any queens clashing on the diagonals.
We're assuming here that the_board is a permutation of column
numbers, so we're not explicitly checking row or column clashes.
"""
for col in range(1, len(the_board)):
if col_clashes(the_board, col):
return True
return False
def main():
import random
rng = random.Random() # Instantiate a generator
bd = list(range(8)) # Generate the initial permutation
num_found = 0
tries = 0
result = []
while num_found < 10:
rng.shuffle(bd)
tries += 1
if not has_clashes(bd) and bd not in result:
print("Found solution {0} in {1} tries.".format(bd, tries))
tries = 0
num_found += 1
result.append(list(bd))
print(result)
main()