Leetcode刷题记录22-37,python语言
22 括号生成 (回溯、递归算法)
回溯算法来生成有效的括号组合。在 backtrack 函数中,我们递归地生成可能的括号组合,并通过限制条件来确保生成的括号组合是有效的:左括号数量不能超过 n,右括号数量不能超过左括号数量。最终返回的 res 列表包含了所有有效的括号组合。您可以调用 generateParenthesis 方法并传入参数 n 来获得结果
定义一个函数generateParenthesis,接收一个整数参数n,表示生成括号的对数。函数的返回值是一个字符串列表,包含所有可能的并且有效的括号组合。创建一个空列表result,用于保存结果。调用辅助函数backtrack,传入参数result、空字符串""、左括号的数量和右括号的数量都为0,以及目标括号对数n。 在backtrack函数中,首先检查当前的组合长度是否达到了目标长度(2 * n)。如果是,则将当前组合添加到结果列表result中,并返回。 如果当前左括号的数量小于目标括号对数n,则可以添加一个左括号。递归调用backtrack函数,在当前组合后面添加一个左括号,并将左括号计数增加1。 如果当前右括号的数量小于当前左括号的数量,说明我们可以添加一个右括号。递归调用backtrack函数,在当前组合后面添加一个右括号,并将右括号计数增加1。 递归结束后,就可以得到所有可能的并且有效的括号组合。 返回结果列表result。
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
def backtrack(s='', left=0, right=0): # s为字符串,目前长度为0
if len(s) == 2*n: # 三个并列的判断条件 依次判断
res.append(s) # 将当前组合添加到结果列表中
return # 结束当前回溯路径
if left < n:
backtrack(s+'(', left+1, right) # 在当前组合后面添加一个左括号,递归调用回溯函数
if right < left:
backtrack(s+')', left, right+1) # 在当前组合后面添加一个右括号,递归调用回溯函数
res = []
backtrack()
return res
23 合并K个升序链表
分治法来将 k 个已排序的链表两两合并,直到只剩下一个链表为止。在 mergeKLists 方法中,我们首先定义了一个辅助函数 mergeTwoLists 来合并两个链表。然后,我们使用循环不断将相邻的链表两两合并,直到所有链表合并成一个链表为止。
最终返回的是合并后的链表头节点。您可以调用 mergeKLists 方法并传入参数 lists 来获得结果。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
if not lists: # lists为空
return None
def mergeTwoLists(l1, l2): # 将相邻两个链表升序合并为一个, 与之前写的21题合并两个升序链表代码一样
dummy = ListNode(0) # 虚拟头结点
current = dummy # 指针,从虚拟头结点开始
while l1 and l2: # 两个为非空链表
if l1.val < l2.val:
current.next = l1
l1 = l1.next
else:
current.next = l2
l2 = l2.next
current = current.next
current.next = l1 or l2 # 当l1或者l2有一个为空时
return dummy.next
amount = len(lists) # 看有多少个链表
interval = 1 # 要合并的两个链表的区间跨度,第一次合并就是lists中的第一个和第二个合成后放在第一位interval=1;第二次合并就直接将第三位和上次合并的再进行合并,interval=2
while interval < amount:
for i in range(0, amount - interval, interval * 2): # interval * 2步长,好像每次i都是0
lists[i] = mergeTwoLists(lists[i], lists[i + interval])
interval *= 2
return lists[0] if amount > 0 else None
24 两两交换链表
通过迭代的方式在链表中两两交换相邻节点。我们使用一个虚拟节点 dummy 来简化操作,并且在每次交换节点后更新前驱节点和当前节点的位置。最终返回的是交换后的链表头节点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(0) # 虚拟头结点
dummy.next = head # 虚拟在head前
prev_node = dummy # 前驱节点
while head and head.next: # 当两个非空时
# Nodes to be swapped
first_node = head # node1
second_node = head.next # node2
# Swapping 0 -> 2 -> 1 -> 3
prev_node.next = second_node # node0虚拟头结点 指向 node2
first_node.next = second_node.next # node1的下一个指向是node3
second_node.next = first_node # node2指向node1
# Reinitializing the pointers for next swap
prev_node = first_node # 用node1替换node0
head = first_node.next # 用node3 (first_node.next现在是node3)替换node1,进行下一轮迭代,相当于node1作为虚拟头结点,node3开始是下一个链表head
return dummy.next
25 K个一组翻转链表
栈就是先进后出,后进先出,理解为叠盘子,最早叠的盘子最后才能取出,最后叠上去的盘子最早取出。
借助栈,将k个数据分批进栈出栈再链接,一边顺序遍历链表,一边存入栈中,值得注意的是循环的出口条件以及处理在最后一趟的数据不能完全存入栈中的可能性(不够k)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
# dummy为结果数组
dummy = ListNode(-1)
p = dummy
while True:
tmp = head
# 栈用于先进后出
vector = []
# 每次倒计数
count = k
while count and tmp:
vector.append(tmp)
tmp = tmp.next
count -= 1
# 栈内无数据,链表未结束
# 即最后一趟
if count:
p.next = head
break
while vector:
p.next = vector.pop()
p = p.next
# 每倒完一次,与剩下的相连
p.next = tmp
head = tmp
return dummy.next
26 删除有序数组中的重复项
双指针的方法在原地修改输入数组,去除其中重复的元素。我们使用一个指针 unique_idx 来表示当前不重复序列的末尾,在遍历数组过程中,如果发现下一个元素与当前不重复序列的最后一个元素不同,则将该元素加入不重复序列中。
最终返回的是去重后数组的长度,同时原地修改了输入数组使得前面部分为不重复的元素序列。您可以调用 removeDuplicates 方法并传入参数 nums 来获得结果。
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
if not nums: # nums为空时
return 0
# 使用双指针,一个指向当前不重复的位置,一个用来遍历整个数组
unique_idx = 0 # 指针 unique_idx 来表示当前不重复序列的末尾
for i in range(1, len(nums)): # i指针用来遍历nums,注意从1开始循环
if nums[i] != nums[unique_idx]:
unique_idx += 1
nums[unique_idx] = nums[i] # 替换nums[0]变为nums[1]
return unique_idx + 1
27 移除元素
双指针的方法在原地修改输入数组,去除其中与给定值相同的元素。我们使用一个指针 valid_idx 来表示当前有效序列的末尾,在遍历数组过程中,如果发现下一个元素不等于给定值,则将该元素加入有效序列中。
最终返回的是去除给定值后数组的长度,同时原地修改了输入数组使得前面部分为有效的元素序列。您可以调用 removeElement 方法并传入参数 nums 和 val 来获得结果。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
# 使用双指针,一个指向当前有效元素的位置,一个用来遍历整个数组
valid_idx = 0
for i in range(len(nums)):
if nums[i] != val:
nums[valid_idx] = nums[i]
valid_idx += 1
return valid_idx
28 找出字符串中第一个匹配项的下标
在字符串 haystack 中找出字符串 needle 的位置。算法思想是遍历 haystack,逐个比较 haystack 中的子串与 needle 是否相等。如果找到了匹配的子串,则返回其起始位置索引;如果遍历完整个 haystack 都没有找到匹配的子串,则返回 -1。
具体步骤如下:
首先检查 needle 是否为空字符串,如果是,则直接返回 0,因为空字符串在任何位置都可以匹配。
检查 needle 的长度是否大于 haystack 的长度,如果是,则说明 needle 不可能在 haystack 中出现,直接返回 -1。
遍历 haystack 的每个字符,同时比较以当前字符为起始位置的长度与 needle 的长度相同的子串是否与 needle 相等,如果相等,则返回当前位置的索引。
如果遍历完整个 haystack 都没有找到匹配的子串,则返回 -1。
这个算法的时间复杂度为 O((n-m+1)m),其中 n 是 haystack 的长度,m 是 needle 的长度。
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
if not needle:
return 0
if len(needle) > len(haystack):
return -1
for i in range(len(haystack) - len(needle) + 1):
if haystack[i:i+len(needle)] == needle:
return i
return -1
29 两数相除
计算 dividend 除以 divisor 的商。算法思想是通过二进制的左移操作来模拟除法的过程。
具体步骤如下:
首先定义了最大值和最小值的常量,用于处理溢出情况。
处理特殊情况:
如果除数为0,则根据被除数的符号返回最大值或最小值。
如果被除数为0,则直接返回0。
如果被除数为最小值且除数为-1,这种情况会溢出,返回最大值。
确定结果的符号,通过异或运算来确定。
将被除数和除数都转换为正数,以便进行计算。
使用一个循环来模拟除法的过程:
首先找到除数能够左移多少次(即找到最大的temp,使得被除数仍大于等于temp * 2),并将除数左移该次数,同时将对应的因子(factor)记录下来。
然后将被除数减去temp,商加上对应的因子。
最后根据结果的符号返回商。
这个算法的时间复杂度为 O(logN),其中 N 是被除数的绝对值大小。
class Solution:
def divide(self, dividend: int, divisor: int) -> int:
MAX_INT = 2**31 - 1
MIN_INT = -2**31
# 处理除数为0的情况
if divisor == 0:
return MAX_INT if dividend >= 0 else MIN_INT
# 处理被除数为0的情况
if dividend == 0:
return 0
# 处理溢出情况
if dividend == MIN_INT and divisor == -1:
return MAX_INT
# 确定结果的符号
sign = -1 if (dividend < 0) ^ (divisor < 0) else 1 # ^ 异或运算符,
# 异或算符的值为真仅当两个运算元中恰有一个的值为真,而另外一个的值为非真。转化为命题,就是:“两者的值不同。”或“有且仅有一个为真。”
dividend = abs(dividend)
divisor = abs(divisor)
quotient = 0 # 商
while dividend >= divisor:
temp, factor = divisor, 1 # 除数,因子
while dividend >= (temp << 1): # temp的二进制左移一位,相当于temp乘以2
temp <<= 1 # temp的二进制继续左移一位,temp乘以2作为新的temp
factor <<= 1 #而>>=和<<=,就是对变量进行位运算移位之后的结果再赋值给原来的变量,可以类比赋值运算符+=和-=可以理解
dividend -= temp # 被除数减去temp
quotient += factor # 商
return sign * quotient
30 串联所有单词的子串
找出字符串 s 中所有包含给定单词列表 words 中所有单词的子串的起始位置。算法思想是通过滑动窗口的方法遍历字符串 s,并将窗口内的子串与 words 中的单词进行比较。
具体步骤如下:
首先检查输入的字符串 s 和单词列表 words 是否为空,如果其中之一为空,则直接返回空列表。
计算单词的长度 word_len,以及单词列表中单词的总数 words_count,以及单词列表中所有单词的总长度 total_len。
使用 Counter 统计单词列表 words 中各单词的频率。
遍历字符串 s,使用滑动窗口的方法,窗口的大小为 total_len,每次滑动一个单词的长度 word_len:
在当前窗口内,截取长度为 total_len 的子串 substr。
使用 Counter 统计 substr 中各单词的频率。
如果 substr 中各单词的频率与 words_freq 相同,则说明 substr 是一个符合条件的子串,将其起始位置加入结果列表中。
返回结果列表。
这个算法的时间复杂度取决于字符串 s 的长度和单词列表 words 中单词的总数,为 O(N * M),其中 N 是字符串 s 的长度,M 是单词列表 words 中单词的总数。
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
if not s or not words:
return []
word_len = len(words[0])
words_count = len(words)
total_len = word_len * words_count
result = []
words_freq = Counter(words) # 每个word的频率为一次
for i in range(len(s) - total_len + 1):
substr = s[i:i+total_len]
substr_freq = Counter([substr[j:j+word_len] for j in range(0, total_len, word_len)]) # 滑动窗口
# 每个单词的长度一样word_len, 所以每次取一个word来计算频率
if substr_freq == words_freq:
result.append(i)
return result
31 下一个排列 (交换,翻转)
字典序就是相邻两个数字比较大小,小的在前大的在后则打勾,相反则打叉,√x在x√前,如231的下一个排列为312。这段代码实现了找出给定排列的下一个排列。算法思想是通过一系列交换和翻转操作,使得排列变为下一个更大的排列。
具体步骤如下:
从右往左找到第一个递减的位置 i,即找到满足 nums[i] < nums[i+1] 的最大的 i。
如果找到了递减的位置 i,则从右往左找到第一个大于 nums[i] 的位置 j,交换 nums[i] 和 nums[j]。
将从 i+1 开始到末尾的部分翻转,使得该部分变为递增的序列,因为原来是递减的。
如果在步骤1中未找到递减的位置 i,说明整个排列已经是最大的排列,直接将整个排列翻转即可。
这个算法的时间复杂度为 O(N),其中 N 是排列的长度。
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 从右往左找到第一个递减的位置 i
i = len(nums) - 2 # 倒数第二个index
while i >= 0 and nums[i] >= nums[i+1]: # 左边比右边大,则向左移动一个
i -= 1
# 如果找到了递减的位置 i,则从右往左找到第一个大于 nums[i] 的位置 j
if i >= 0:
j = len(nums) - 1
while j >= 0 and nums[j] <= nums[i]:
j -= 1
# 交换 nums[i] 和 nums[j]
nums[i], nums[j] = nums[j], nums[i]
# 将从 i+1 开始到末尾的部分翻转
left, right = i + 1, len(nums) - 1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
32 最长有效括号
这段代码实现了找出给定字符串中最长的有效括号子串的长度。有效括号是指左右括号匹配且括号嵌套规则正确。
具体步骤如下:
初始化一个栈 stack,并将 -1 放入栈中作为合法序列的起始位置。
遍历字符串 s 中的每个字符:
如果遇到左括号 (,将其位置 i 入栈。
如果遇到右括号 ),弹出栈顶元素。
如果栈为空,说明当前右括号没有匹配的左括号,将当前右括号的位置 i 入栈,作为新的合法序列的起始位置。
如果栈不为空,计算当前合法序列的长度 i - stack[-1](即当前右括号的位置减去栈顶元素的位置),并与当前最大长度 max_len 比较更新最大长度。
返回最大长度 max_len。
这个算法的时间复杂度为 O(N),其中 N 是字符串 s 的长度。
class Solution:
def longestValidParentheses(self, s: str) -> int:
stack = [-1] # 使用栈来记录括号的位置,初始时将 -1 放入栈中作为合法序列的起始位置
max_len = 0
for i in range(len(s)):
if s[i] == "(":
stack.append(i) # 遇到左括号,将其位置index入栈
else:
stack.pop() # 遇到右括号,弹出栈顶元素
if len(stack) == 0:
stack.append(i) # 如果栈为空,将当前右括号的位置入栈作为新的合法序列起始位置
else:
# 计算当前合法序列的长度,并与最大长度比较更新
max_len = max(max_len, i - stack[-1])
return max_len
33 搜索旋转排序数组
在旋转有序数组中搜索目标值的功能。旋转有序数组是指原本是递增的数组经过旋转后的数组,例如 [4, 5, 6, 7, 0, 1, 2]。
具体步骤如下:
初始化两个指针 left 和 right 分别指向数组的起始位置和结束位置。
进入循环,直到 left 大于 right:
计算中间位置 mid,并判断 nums[mid] 是否等于目标值 target。如果是,则返回 mid。
判断左半边是否是递增的(即 nums[left] <= nums[mid]):
如果是,判断目标值是否在左半边范围内(即 nums[left] <= target < nums[mid]):
如果是,在左半边继续二分查找,将 right 更新为 mid - 1。
如果不是,在右半边继续二分查找,将 left 更新为 mid + 1。
如果不是,说明右半边是递增的,判断目标值是否在右半边范围内(即 nums[mid] < target <= nums[right]):
如果是,在右半边继续二分查找,将 left 更新为 mid + 1。
如果不是,在左半边继续二分查找,将 right 更新为 mid - 1。
如果循环结束仍未找到目标值,则返回 -1。
这个算法的时间复杂度为 O(logN),其中 N 是数组 nums 的长度。
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1 # 左右指针
while left <= right: # while循环,直到左指针大于右指针
mid = (left + right) // 2 # //表示整数除法,向下取整6//4=1
if nums[mid] == target:
return mid
# 找到递增的那一边,进行二分查找mid
# 如果左半边是递增的
if nums[left] <= nums[mid]:
# 如果目标值在左半边范围内,则在左半边继续二分查找
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
# 如果右半边是递增的
else:
# 如果目标值在右半边范围内,则在右半边继续二分查找
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
34 在排序数组中查找元素的第一个和最后一个位置 (定义两函数,左右指针)
递增序列数组。
这段代码实现了在有序数组中找到目标值的第一个和最后一个位置。
具体步骤如下:
定义一个内部函数 findFirst(nums, target) 用于找到目标值的第一个位置,使用二分查找法。
初始化左右指针 left 和 right 分别指向数组的起始位置和结束位置。
当 left 小于等于 right 时,执行循环:
计算中间位置 mid。
如果 nums[mid] 小于目标值 target,则将 left 更新为 mid + 1,表示目标值在右半部分。
否则,将 right 更新为 mid - 1,表示目标值在左半部分。
返回 left,即为目标值的第一个位置。
使用 findFirst(nums, target) 找到目标值的第一个位置 first_pos。
如果 first_pos 超出数组范围或者对应的值不等于目标值,则说明数组中不存在目标值,直接返回 [-1, -1]。
定义另一个内部函数 findLast(nums, target) 用于找到目标值的最后一个位置,同样使用二分查找法。
初始化左右指针 left 和 right 分别指向数组的起始位置和结束位置。
当 left 小于等于 right 时,执行循环:
计算中间位置 mid。
如果 nums[mid] 小于等于目标值 target,则将 left 更新为 mid + 1,表示目标值在右半部分。
否则,将 right 更新为 mid - 1,表示目标值在左半部分。
返回 right,即为目标值的最后一个位置。
使用 findLast(nums, target) 找到目标值的最后一个位置 last_pos。
返回 [first_pos, last_pos],即为目标值的第一个和最后一个位置。
这个算法的时间复杂度为 O(logN),其中 N 是数组 nums 的长度。
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# 二分查找找到目标值的第一个位置
def findFirst(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2 # 向下取整,整数除法
if nums[mid] < target: #mid左边没有target值,必在右半边,左指针更新
left = mid + 1
else: # 必在左半边
right = mid - 1
return left # 左指针大于右指针,循环停止
first_pos = findFirst(nums, target) # 找到第一个位置
# 如果找不到目标值,则直接返回 [-1, -1]
if first_pos >= len(nums) or nums[first_pos] != target:
return [-1, -1]
# 二分查找找到目标值的最后一个位置
def findLast(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] <= target: # 在右半边
left = mid + 1
else:
right = mid - 1
return right
last_pos = findLast(nums, target)
return [first_pos, last_pos]
35 搜索插入位置 (二分查找,左右指针)
段代码实现了在有序数组中搜索目标值的插入位置。
具体步骤如下:
初始化左右指针 left 和 right 分别指向数组的起始位置和结束位置。
进入循环,条件为 left <= right。
在循环中,计算中间位置 mid。
如果中间值等于目标值 target,则直接返回 mid。
如果中间值小于目标值 target,则将 left 更新为 mid + 1,表示目标值在右半部分。
如果中间值大于目标值 target,则将 right 更新为 mid - 1,表示目标值在左半部分。
循环结束后,返回 left,即为目标值应该插入的位置。
这个算法的时间复杂度为 O(logN),其中 N 是数组 nums 的长度。
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return left
36 有效的数独
段代码实现了判断一个数独是否是有效的。
具体步骤如下:
创建三个数组 rows、cols 和 boxes,分别用于存储每一行、每一列和每个3x3子数独中已经出现过的数字。
遍历整个数独棋盘,对于每一个非空格的数字,进行如下检查:
检查当前行是否已经存在相同数字,如果存在则返回 False。
检查当前列是否已经存在相同数字,如果存在则返回 False。
计算当前数字在哪个3x3子数独中,然后检查该子数独是否已经存在相同数字,如果存在则返回 False。
如果以上三个检查都通过,则将当前数字加入对应的行、列和子数独集合中。
如果遍历完整个数独棋盘后没有出现重复数字,则返回 True,表示这是一个有效的数独。
这个算法的时间复杂度为 O(1),因为数独棋盘的大小固定为 9x9,所以算法的运行时间不会随着输入规模的增加而增加。
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
rows = [set() for _ in range(9)] # 用于存储每一行的数字集合
cols = [set() for _ in range(9)] # 用于存储每一列的数字集合
boxes = [set() for _ in range(9)] # 用于存储每个3x3子数独的数字集合 , 9个小宫格
for i in range(9):
for j in range(9):
if board[i][j] != '.': # 非空白格的数字
num = int(board[i][j]) # 取值
# 检查行是否有重复数字
if num in rows[i]: # 判断改行是否有重复
return False
rows[i].add(num)
# 检查列是否有重复数字
if num in cols[j]:
return False
cols[j].add(num)
# 计算子数独的索引
box_index = (i // 3) * 3 + j // 3 # //向下取整除法,找到是第几个小宫格
# 检查子数独是否有重复数字
if num in boxes[box_index]: # 判断该小宫格是否有重复
return False
boxes[box_index].add(num)
return True
37 解数独
法一:速度最快,
我们首先对整个数独数组进行遍历,当我们遍历到第 i行第 j 列的位置:
如果该位置是一个空白格,那么我们将其加入一个用来存储空白格位置的列表中,方便后续的递归操作;
如果该位置是一个数字 x,那么我们需要将 line[i][x−1],column[j][x−1]以及 block[⌊i/3⌋][⌊j/3⌋][x−1]均置为 True。
当我们结束了遍历过程之后,就可以开始递归枚举。当递归到第 i 行第 j 列的位置时,我们枚举填入的数字 x。根据题目的要求,数字 x 不能和当前行、列、九宫格中已经填入的数字相同,因此 line[i][x−1],column[j][x−1] 以及 block[⌊i/3⌋][⌊j/3⌋][x−1] 必须均为 False。
当我们填入了数字 x之后,我们要将上述的三个值都置为 True,并且继续对下一个空白格位置进行递归。在回溯到当前递归层时,我们还要将上述的三个值重新置为 False。
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
def dfs(pos: int):
nonlocal valid
if pos == len(spaces):
valid = True
return
i, j = spaces[pos]
for digit in range(9):
if line[i][digit] == column[j][digit] == block[i // 3][j // 3][digit] == False:
line[i][digit] = column[j][digit] = block[i // 3][j // 3][digit] = True
board[i][j] = str(digit + 1)
dfs(pos + 1)
line[i][digit] = column[j][digit] = block[i // 3][j // 3][digit] = False
if valid:
return
line = [[False] * 9 for _ in range(9)]
column = [[False] * 9 for _ in range(9)]
block = [[[False] * 9 for _a in range(3)] for _b in range(3)]
valid = False
spaces = list()
for i in range(9):
for j in range(9):
if board[i][j] == ".":
spaces.append((i, j))
else:
digit = int(board[i][j]) - 1
line[i][digit] = column[j][digit] = block[i // 3][j // 3][digit] = True
dfs(0)
法二
具体步骤如下:
定义 solveSudoku 函数,作为解数独问题的入口函数。
在 solveSudoku 函数中调用 solve 函数来解决数独问题。
在 solve 函数中,使用双重循环遍历数独棋盘,当遇到空格时,尝试填入数字(1-9)并递归调用 solve 函数。
在递归调用中,如果当前数字符合数独规则,则继续尝试填充下一个空格;如果不符合规则,则回溯到上一个状态并尝试其他数字。
当数独棋盘全部填充完毕时,返回 True。
在 isValid 函数中,检查当前填充的数字是否符合数独规则,即行、列和3x3子数独中是否存在重复数字。
示例中,调用 solveSudoku 函数解决数独问题,并打印出解决后的数独棋盘。
这个算法通过回溯的方式逐步填充数独棋盘,直到找到解或者无解为止。算法的时间复杂度取决于解数独问题的复杂度,通常情况下为指数级别。
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
def solve(board):
for i in range(9):
for j in range(9):
if board[i][j] == '.':
for num in map(str, range(1, 10)):
if isValid(board, i, j, num):
board[i][j] = num
if solve(board):
return True
else:
board[i][j] = '.' # 回溯
return False
return True
def isValid(board, row, col, num):
for i in range(9):
if board[row][i] == num or board[i][col] == num or \
board[3 * (row // 3) + i // 3][3 * (col // 3) + i % 3] == num:
return False
return True
if not board:
return
solve(board)