《算法通关之路》学习笔记,记录一下自己的刷题过程,详细的内容请大家购买作者的书籍查阅。
外观数列
力扣第38题
给定一个正整数 n ,输出外观数列的第 n 项。
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。
'''
方法一:迭代法
时间复杂度:O(mn)
'''
class Solution:
def countAndSay(self, n: int) -> str:
s = '1'
for _ in range(n-1):
tmp = ''
left, right = 0, 0 # 具有相同数字的区间
while right < len(s):
while right < len(s) and s[right] == s[left]: # 区间增长
right += 1
tmp += str(len(s[left: right])) + s[left] # 记录区间长度和对应数字
left = right
s = tmp
return s
# 试运行
n = 4
solu = Solution()
solu.countAndSay(n)
'''
方法二:递归
时间复杂度:O(mn)
'''
class Solution:
def countAndSay(self, n: int) -> str:
# 因为tmp每次都要置空,而s每次使用上一轮迭代结果,所以可以使用递归
def helper(n: int) -> str:
if n == 1:
return '1'
s = helper(n-1)
tmp = ''
left, right = 0, 0 # 具有相同数字的区间
while right < len(s):
while right < len(s) and s[right] == s[left]: # 区间增长
right += 1
tmp += str(len(s[left: right])) + s[left] # 记录区间长度和对应数字
left = right
return tmp
return helper(n)
# 试运行
n = 4
solu = Solution()
solu.countAndSay(n)
24点
力扣第679题
给定一个长度为4的整数数组 cards 。你有 4 张卡片,每张卡片上都包含一个范围在 [1,9] 的数字。您应该使用运算符 [‘+’, ‘-’, ‘*’, ‘/’] 和括号 ‘(’ 和 ‘)’ 将这些卡片上的数字排列成数学表达式,以获得值24。
如果可以得到这样的表达式,其计算结果为 24 ,则返回 true ,否则返回 false 。
'''
方法一:全排列(回溯递归法)
'''
class Solution:
def judgePoint24(self, cards: list[int]) -> bool:
n = len(cards)
cards.sort()
used = [False] * n # 构造全排列时用到的访问数组
tmp = list() # 存全排列数
def dfs() -> bool:
'''构造全排列序列,并当即进行判断'''
if len(tmp) == len(cards):
return compute(tmp) # 是否组成24点
for i in range(n):
if i > 0 and cards[i] == cards[i-1] and used[i-1] == 0: # 去重
continue
if used[i]:
continue
else:
tmp.append(cards[i])
used[i] = True # 选择card[i]
if dfs():
return True
tmp.pop() # 不选择card[i]
used[i] = False
return False
def compute(cards: list[int]) -> bool:
'''判断四个数字能否组成24'''
if len(cards) == 1:
return abs(cards[0] - 24) < 1e-5 # 满足题目中的实数除法
for i in range(0, len(cards)-1):
if cards[i+1] == 0:
compute_res = [cards[i] * cards[i+1], cards[i] + cards[i+1], cards[i] - cards[i+1]]
else:
compute_res = [cards[i] * cards[i+1], cards[i] / cards[i+1], cards[i] + cards[i+1], cards[i] - cards[i+1]]
for c_res in compute_res:
new_cards = cards[:]
new_cards[i] = c_res
new_cards.pop(i + 1)
if compute(new_cards):
return True
return False
return dfs()
# 试运行
cards = [4, 1, 8, 7]
solu = Solution()
solu.judgePoint24(cards)
'''
方法二:组合(迭代递归法)
'''
class Solution:
def judgePoint24(self, cards: list[int]) -> bool:
def compute(nums: list[int], n: int) -> bool:
if n == 1:
return abs(nums[0] - 24) < 1e-6
new_nums = [0] * 4
# 双指针计算组合
for left in range(n-1):
for right in range(left + 1, n):
idx = 0
# 将未参与计算的数字移动到数组的前面
for i in range(n):
if i != left and i != right:
new_nums[idx] = nums[i]
idx += 1
# 将运算后的结果附在其他数字末尾
if nums[left] and nums[right]:
compute_res = [nums[left] + nums[right], nums[left] * nums[right], nums[left] / nums[right],\
nums[right] / nums[left], nums[right] - nums[left], nums[left] - nums[right]]
else:
compute_res = [nums[left] + nums[right], nums[left] * nums[right], \
nums[left] - nums[right], nums[right] - nums[left]]
for c_res in compute_res:
new_nums[idx] = c_res
if compute(new_nums, idx + 1):
return True
return False
return compute(cards, 4)
# 试运行
cards = [4, 1, 8, 7]
solu = Solution()
solu.judgePoint24(cards)
解数独
力扣第37题
编写一个程序,通过填充空格来解决数独问题。
数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
'''
方法一:回溯
'''
from pprint import pprint
class Solution:
def solveSudoku(self, board: list[list[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
row_state = [[False for i in range(10)] for _ in range(9)] # 行状态
column_state = [[False for i in range(10)] for _ in range(9)] # 列状态
box_state = [[False for i in range(10)] for _ in range(9)] # 九宫格状态
# 初始化row_state, column_state, box_state三个状态数组
for i in range(9):
for j in range(9):
if board[i][j] != '.':
num = int(board[i][j])
row_state[i][num] = True # i行的num已经使用过
column_state[j][num] = True # j列的num已经使用过
box_state[(i // 3) * 3 + j // 3][num] = True # 第[(i // 3) * 3 + j // 3]个九宫格中的num已经使用过
def recursive_place_number(row: int, column: int) -> bool:
if column == 9: # 一行已经走完
row += 1
column = 0
if row == 9: # 所有行都已走完
return True
if board[row][column] != '.':
return recursive_place_number(row, column + 1)
else:
for i in range(1, 10):
if row_state[row][i] or column_state[column][i] or box_state[(row // 3) * 3 + column // 3][i]:
continue
else:
place_number(row, column, i) # 填数并修改三个数组的状态
if recursive_place_number(row, column + 1):
return True
undo_number_placement(row, column, i) # 删数并修改三个数组的状态
return False
def place_number(row: int, column: int, i: int) -> bool:
row_state[row][i] = True
column_state[column][i] = True
box_state[(row // 3) * 3 + column // 3][i] = True
board[row][column] = str(i)
def undo_number_placement(row:int, column:int, i:int) -> bool:
row_state[row][i] = False
column_state[column][i] = False
box_state[(row // 3) * 3 + column // 3][i] = False
board[row][column] = '.'
recursive_place_number(0, 0)
# 试运行
board = [["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]]
solu = Solution()
solu.solveSudoku(board)
pprint(board)
'''
方法一:回溯优化
'''
from pprint import pprint
class Solution:
def solveSudoku(self, board: list[list[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
row_state = [[False for i in range(10)] for _ in range(9)] # 行状态
column_state = [[False for i in range(10)] for _ in range(9)] # 列状态
box_state = [[False for i in range(10)] for _ in range(9)] # 九宫格状态
# 初始化row_state, column_state, box_state三个状态数组
for i in range(9):
for j in range(9):
if board[i][j] != '.':
num = int(board[i][j])
row_state[i][num] = True # i行的num已经使用过
column_state[j][num] = True # j列的num已经使用过
box_state[(i // 3) * 3 + j // 3][num] = True # 第[(i // 3) * 3 + j // 3]个九宫格中的num已经使用过
def recursive_place_number(row: int, column: int) -> bool:
if column == -1 and row == -1: # 所有空格已填完
return True
if board[row][column] != '.': # 非空格不填
return False
else:
for i in range(1, 10):
if row_state[row][i] or column_state[column][i] or box_state[(row // 3) * 3 + column // 3][i]:
continue
else:
place_number(row, column, i)
x, y = get_max_possible_coordinate()
if recursive_place_number(x, y):
return True
undo_number_placement(row, column, i)
return False
def place_number(row: int, column: int, i: int) -> bool:
row_state[row][i] = True
column_state[column][i] = True
box_state[(row // 3) * 3 + column // 3][i] = True
board[row][column] = str(i)
def undo_number_placement(row:int, column:int, i:int) -> bool:
row_state[row][i] = False
column_state[column][i] = False
box_state[(row // 3) * 3 + column // 3][i] = False
board[row][column] = '.'
# 选取确定性最大的空格返回坐标
def get_max_possible_coordinate() -> (int, int):
x, y, min_count = -1, -1, 9
for i in range(9):
for j in range(9):
if board[i][j] != '.':
continue
tmp_count = 9
for k in range(9):
if row_state[i][k] or column_state[j][k] or box_state[(i // 3) * 3 + j // 3][k]:
tmp_count -= 1
if tmp_count == 1:
return i, j
if min_count > tmp_count:
min_count = tmp_count
x = i
y = j
return x, y
x, y = get_max_possible_coordinate()
recursive_place_number(x, y)
# 试运行
board = [["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]]
solu = Solution()
solu.solveSudoku(board)
pprint(board)
生命游戏
力扣第289题
根据 百度百科 , 生命游戏 ,简称为 生命 ,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机。
给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态: 1 即为 活细胞 (live),或 0 即为 死细胞 (dead)。每个细胞与其八个相邻位置(水平,垂直,对角线)的细胞都遵循以下四条生存定律:
如果活细胞周围八个位置的活细胞数少于两个,则该位置活细胞死亡;
如果活细胞周围八个位置有两个或三个活细胞,则该位置活细胞仍然存活;
如果活细胞周围八个位置有超过三个活细胞,则该位置活细胞死亡;
如果死细胞周围正好有三个活细胞,则该位置死细胞复活;
下一个状态是通过将上述规则同时应用于当前状态下的每个细胞所形成的,其中细胞的出生和死亡是同时发生的。给你 m x n 网格面板 board 的当前状态,返回下一个状态。
from pprint import pprint
class Solution:
def gameOfLife(self, board: list[list[int]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
def get_neighbor_count(i, j, board):
# 邻居坐标边界
top = max(0, i - 1)
bottom = min(len(board) - 1, i + 1)
left = max(0, j - 1)
right = min(len(board[0]) - 1, j + 1)
count = 0
for x in range(top, bottom + 1):
for y in range(left, right + 1):
if board[x][y] == 1 or board[x][y] == -1:
count += 1
return count
# 标记将要死亡或者复活的格子
for i in range(len(board)):
for j in range(len(board[0])):
res = get_neighbor_count(i, j, board)
if board[i][j] == 1 and res in [3, 4]:
board[i][j] = 1
elif board[i][j] == 1:
board[i][j] = -1
elif board[i][j] == 0 and res == 3:
board[i][j] = -2
# 更新格子
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] == -2:
board[i][j] = 1
elif board[i][j] == -1:
board[i][j] = 0
# 试运行
board = [[0,1,0],[0,0,1],[1,1,1],[0,0,0]]
solu = Solution()
solu.gameOfLife(board)
pprint(board)