This approach is pretty much the same as others but I've chosen to use positive/negative to "encode" additional information. Nice thing about this is that you can use mod operator which only cares about the absolute values. I also handle my bounds a bit differently than most.

```
def gameOfLife(self, board):
"""
:type board: List[List[int]]
:rtype: void Do not return anything, modify board in-place instead.
"""
num_rows, num_cols = len(board), len(board[0])
for r in range(num_rows):
is_firstr = not r
is_lastr = (r == num_rows-1)
for c in range(num_cols):
is_firstc = not c
is_lastc = (c == num_cols-1)
# Inclusive bounds.
rstart, rend = r-1, r+1
cstart, cend = c-1, c+1
# Update bounds if we are on edge.
if is_firstr: rstart = r
if is_lastr: rend = num_rows-1
if is_firstc: cstart = c
if is_lastc: cend = num_cols-1
# If a cell is 0, and it needs to live, assign -1.
# If a cell is 0, and it needs to remain dead, assign -2.
# If a cell is 1, and it needs to die, assign 2.
# If a cell is 1, and it needs to remain live, assign 1.
total_live = 0
for ir in range(rstart, rend+1):
total_live += sum(1 if x > 0 else 0 for x in board[ir][cstart:cend+1])
# Check current cell value and assign new value.
if board[r][c]:
# Previously summed 3x3 block but current cell needs to be omitted.
total_live -= 1
if total_live < 2 or total_live > 3:
board[r][c] = 2
else:
board[r][c] = 1
else:
if total_live == 3:
board[r][c] = -1
else:
board[r][c] = -2
# Normalize to 1s and 0s.
for r in range(num_rows):
for c in range(num_cols):
if board[r][c] % 2:
board[r][c] = 1
else:
board[r][c] = 0
```