36、有效的数独
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
解:遍历即可
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
column=[[] for i in range(9)]
row=[[] for i in range(9)]
block=[[] for i in range(9)]
for i in range(9):
a=i//3
for j in range(9):
b=j//3
blockNum=a*3+b
num=board[i][j]
if num!=".":
if (num not in row[i]):
row[i].append(num)
else:
return False
if (num not in column[j]):
column[j].append(num)
else:
return False
if (num not in block[blockNum]):
block[blockNum].append(num)
else:
return False
return True
执行用时:48 ms, 击败了89.75%的用户
内存消耗:13.8 MB
37、解数独
编写一个程序,通过已填充的空格来解决数独问题。
一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 ‘.’ 表示。
解:暴力回溯,先把取过值的加入row,col和block中,然后一行一行去试,用深度优先搜索,初始为(0,0)。
self.flag是可以在函数中被改变值的全局变量,可以用来判断在递归的函数里是否已经都填完了。
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
col=[[] for i in range(9)]
row=[[] for i in range(9)]
block=[[] for i in range(9)]
for i in range(9):
a=i//3
for j in range(9):
b=j//3
blockNum=a*3+b
num=board[i][j]
if num !=".":
col[j].append(num)
row[i].append(num)
block[blockNum].append(num)
def dfs(i,j):#一行一行去回溯
if board[i][j]!=".":
if i==8 and j==8:
self.flag=True
return
if j<8:
dfs(i,j+1)
else:
dfs(i+1,0)
return
a=i//3
b=j//3
for ch in range(1,10):
ch=str(ch)
if ch not in col[j] and ch not in row[i] and ch not in block[a*3+b]:
col[j].append(ch)
row[i].append(ch)
block[a*3+b].append(ch)
board[i][j] = ch
if i==8 and j==8:
self.flag=True
return
if j<8:
dfs(i,j+1)
else:
dfs(i+1,0)
if self.flag:
return
board[i][j]='.'
col[j].remove(ch)
row[i].remove(ch)
block[a*3+b].remove(ch)
self.flag=False
dfs(0,0)
执行用时:224 ms, 在击败了49.18%的用户
内存消耗:13.9 MB
注:解答中的算法可以做如下改进:先算出所有空的所有可能取值,把不同空按取值数从小到大入栈,按照栈的顺序回溯会大大加快运行速度。
38、外观数列
给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。
注意:整数序列中的每一项将表示为一个字符串。
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:
1. 1
2. 11
3. 21
4. 1211
5. 111221
第一项是数字 1
描述前一项,这个数是 1 即 “一个 1 ”,记作 11
描述前一项,这个数是 11 即 “两个 1 ” ,记作 21
描述前一项,这个数是 21 即 “一个 2 一个 1 ” ,记作 1211
描述前一项,这个数是 1211 即 “一个 1 一个 2 两个 1 ” ,记作 111221
解:用两个指针向右移动读取即可
class Solution:
def countAndSay(self, n: int) -> str:
if n==1:
return "1"
num=self.countAndSay(n-1)
k=len(num)
i=j=0
ans=""
while j<k:
while num[j]==num[i]:
j=j+1
if j>=k:
break
s=str(j-i)
ans=ans+s+str(num[i])
i=j
return ans
执行用时:52 ms, 击败了36.67%的用户
内存消耗:13.6 MB, 击败了82.39%的用户
39、组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
解:想到的是回溯,每一次target减candidates中的任意元素,一直减,如果等于0,则路径path是一个解,如果>0,继续减,如果小于0,则停止,回到上一层。因此可以用dfs来遍历各种情况。
然后就是去重复,可以设置一个取candidates元素中的起始点begin,初值为0,前面的搜索会优先搜索candidates[0],后来的所有若是还搜candidates[0]就会重复,因此可以避免重复。
选取起点begin是因为在begin分支中(target第一个减的数是candidates[begin]),含有candidates[begin]的结果已经全部被列举出来了,因此后面的分支不需要再去列举candidates[begin]
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
if not candidates:
return []
n=len(candidates)
candidates.sort()
path=[]
ans=[]
self.dfsq(candidates,0,n,path,ans,target)
return ans
def dfsq(self,candidates,begin,n,path,ans,target):#设置begin是为了防止重复
#在搜索的时候,需要设置搜索起点的下标 begin ,由于一个数可以使用多次,下一层的结点从这个搜索起点开始搜索;在搜索起点 begin 之前的数因为以前的分支搜索过了,所以一定会产生重复。
if target==0:
ans.append(path[:])
# Python 中可变对象是引用传递,因此需要将当前 path 里的值拷贝出来
return
for i in range(begin,n):
temp=target-candidates[i]
if temp<0:
break
else:
path.append(candidates[i])
self.dfsq(candidates, i, n, path, ans, temp)
path.pop()#上面讨论的是加入candidates[i]的情况,要把它去掉再去重新加入
执行用时:52 ms, 击败了91.07%的用户
内存消耗:13.5 MB,击败了98.00%的用户
40、组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
解:和上一道题思路相同,但是更加简单。不同之处是这道题里candidates中可能有重复数字。
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
if not candidates:
return []
candidates.sort()
n=len(candidates)
path=[]
ans=[]
self.dfs(candidates,0,n,path,target,ans)
return ans
def dfs(self,candidates,begin,n,path,target,ans):
if target == 0:
if path not in ans:
ans.append(path[:])
return
if begin>=n:
return
if target==0:
if path not in ans:
ans.append(path[:])
return
for i in range(begin,n):
temp=target-candidates[i]
if temp<0:
break
else:
path.append(candidates[i])
self.dfs(candidates,i+1,n,path,temp,ans)
path.pop()
执行用时:64 ms, 击败了58.26%的用户
内存消耗:13.6 MB, 击败了86.14%的用户