目录
longestValidParentheses(最长有效括号)
Title34 searchRange(搜索开始和结束位置)
Title39 combinationSum(组合总和) 复习回溯的好题
Day14
nextPermutation(下一个排列)
像是一个数学问题,没想出来这道题的解法,但是下次遇到应该会写
class Solution(object):
def nextPermutation(self, nums):
"""
:type nums: List[int]
:rtype: None Do not return anything, modify nums in-place instead.
"""
if len(nums)<=1:
return
i=len(nums)-2
j=len(nums)-1
while i>=0 and nums[i]>=nums[j]:
i-=1
j-=1
#当并不为-1时,说明找到了可以进行调整的地方
if i!=-1:
#跳出while循环时,i指向第一个出现递减的位置,即需要进行交换的位置
k=len(nums)-1
while nums[i]>=nums[k]:
k-=1
#找到了需要和i进行交换的元素
nums[i],nums[k]=nums[k],nums[i]
nums[j:]=nums[j:][::-1]
longestValidParentheses(最长有效括号)
错误示范:
我自己在写时,考虑的太少了,直接求成匹配到了几对
stackList=['']
num=0
for e in s:
if e =='(':
stackList.append(e)
elif stackList[len(stackList)-1]=='(':
stackList.pop()
num+=2
else:
stackList.append(e)
return num
正确解法:
class Solution(object):
def longestValidParentheses(self, s):
"""
:type s: str
:rtype: int
"""
max_length = 0
stack = [-1] # 初始化栈,防止空栈访问
for i in range(len(s)):
if s[i] == '(':
stack.append(i)
else:
stack.pop() # 弹出匹配的左括号或无效右括号标记
if not stack:
stack.append(i) # 栈空时,记录当前索引为新的基准
else:
# 当前索引到栈顶的差即为当前有效长度
max_length = max(max_length, i - stack[-1])
return max_length
代码分析:
首先利用了工作栈的想法,为了把下表和字符串下标对齐,且防止访问空栈,需要为栈先设一个值,表示栈最初没有匹配到的右括号所在位置为-1
先明确一个概念在本题:
基准索引:最后一个没有被匹配到的右括号索引或-1
接下来开始访问字符数组,当为左括号时候,直接左括号索引进栈等待被匹配,当为右括号时,先把栈顶元素弹出,当弹出的是左括号时候,栈一定不为空,因为栈只用来记录左括号和基准索引,要么留有最开始的-1,要么留有上一个没有被匹配到的右括号。当弹出的元素是基准位置(如初始的 -1
或之前未匹配的右括号索引)时,栈会变为空,此时需将当前右括号的索引作为新基准压栈
search(搜索旋转按钮)
进阶的二分查找:
仍然按照常规的找到中间元素,但既然是对有序列表进行旋转,那么切分之后,肯定还是会存在有序数组的那一半,始终对有序数组的那一半进行查找,如果左边一半是有序的且target在该范围内,则右指针收缩,如果不在这个范围,则左指针移到无序那一般,继续对无序那一半进行划分,肯定还会分出有序的一半和无序的一半,重复此过程
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
#二分查找plus,虽然被切开了,但是仍然可以使用二分查找
if not nums:
return -1
l,r=0,len(nums)
while l<=r:
mid=(l+r)//2
if nums[mid]==target:
return mid
if nums[l]<=nums[mid]:
if nums[l]<=target<nums[mid]:
r=mid-1
else:
l=mid+1
else:
if nums[mid]<target<=nums[len(nums)-1]:
l=mid+1
else:
r=mid-1
return -1
Day15
Title34 searchRange(搜索开始和结束位置)
二分查找后左右扩张
class Solution(object):
def searchRange(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
if not nums:
return [-1,-1]
position=0
left=0
right=len(nums)-1
while left<right:
mid=(left+right)//2
if nums[mid]==target:
position=mid
break
elif nums[mid]<target:
left=mid+1
else:
right=mid-1
if position==0:
return [-1,-1]
else:
left,right=position,position
while left>0:
if nums[left-1]==target:
left-=1
else:
break
while right<len(nums)-1:
if nums[right+1]==target:
right+=1
else:
break
return [left,right]
Title36 isValidSudoku(有效的数独)
尝试一次遍历解决问题
字典换成列表效率更高,减少了哈希开销
class Solution(object):
def isValidSudoku(self, board):
"""
:type board: List[List[str]]
:rtype: bool
"""
dictcols=[{k:0 for k in range(1,10)} for _ in range(9)]
dictrows=[{k:0 for k in range(1,10)}for _ in range(9)]
dicttables=[[{k:0 for k in range(1,10)}for _ in range(3)]for _ in range(3)]
for i in range(9):
for j in range(9):
num=board[i][j]
if num!='.':
num=int(num)
dictrows[i][num]+=1
dictcols[j][num]+=1
dicttables[i//3][j//3][num]+=1
if(dictrows[i][num]>1 or dictcols[j][num]>1 or dicttables[i//3][j//3][num]>1):
return False
return True
Title37 solveSudoku(解数独) 难题!
方法一:回溯法
太难了,本解法的思想在于“回溯”+“剪枝” ,首先遍历整个二维数组,将非零位置在对应的行列和表格中数字是否出现标记为True,以此实现了剪枝,当已经出现过了在行列表格中,就不需要在遍历了。回溯法的具体代码逻辑如下:
根据之前电话号码总结的模板,首先写结束递归的条件,即当所有数字均填完时,即可向上层返回结果了,注意:return退出的是当前递归层,在返回后,仍然需要处理上一层递归层次。
具体代码逻辑部分,需要使用for循环,列举每个部分数字的可能性,通过if剪枝后直接填入,继续填下一个位置,直到所有填完或者出现冲突——当前行列和9宫格一个没出现过的数字也没有了,那么此时需要返回上一层,经过 if self.valid:return,发现并不是true,那么便需要回溯,将当前数组再次填入space,并将该数字再次在三个表格当中置为false
class Solution(object):
def solveSudoku(self, board):
"""
:type board: List[List[str]]
:rtype: None Do not return anything, modify board in-place instead.
"""
# 初始化三个布尔数组,记录每行、每列、每个3x3子块中数字是否已使用
self.line = [[False] * 9 for _ in range(9)] # 行标记,line[i][num]表示第i行是否已包含数字num+1
self.column = [[False] * 9 for _ in range(9)] # 列标记,column[j][num]表示第j列是否已包含数字num+1
self.block = [[[False] * 9 for _ in range(3)] for _ in range(3)] # 3x3子块标记
self.valid = False # 是否找到有效解
self.spaces = [] # 存储所有待填充的空格位置
# 第一遍遍历:预处理已填数字,并记录空格位置
for i in range(9):
for j in range(9):
if board[i][j] == '.': # 如果是空格,记录位置
self.spaces.append((i, j))
else: # 已填数字,更新标记数组
num = int(board[i][j]) - 1 # 将字符数字转成0-8的索引(例如 '5' -> 4)
self.line[i][num] = True
self.column[j][num] = True
self.block[i // 3][j // 3][num] = True
# 开始深度优先搜索,从第一个空格开始填充
self.dfs(board, 0)
def dfs(self, board, pos):
if pos == len(self.spaces): # 所有空格已填完,找到解
self.valid = True
return
i, j = self.spaces[pos] # 获取当前要填充的空格位置
# 尝试填入数字1-9(对应索引0-8)
for num in range(9):
# 如果当前数字num+1在行、列、子块中均未使用
if not self.line[i][num] and not self.column[j][num] and not self.block[i // 3][j // 3][num]:
# 标记为已使用
self.line[i][num] = True
self.column[j][num] = True
self.block[i // 3][j // 3][num] = True
board[i][j] = str(num + 1) # 将数字写入数独板(转成字符)
# 递归处理下一个空格
self.dfs(board, pos + 1)
# 回溯:如果已经找到解,提前终止;否则撤销当前标记
if self.valid:
return
self.line[i][num] = False
self.column[j][num] = False
self.block[i // 3][j // 3][num] = False
board[i][j] = '.' # 恢复为空格
Day16
Title38. countAndSay(外观字符串)
方法一:读写指针
本题我沿用前面学到的读写指针的方式去遍历字符串
class Solution(object):
def countAndSay(self, n):
"""
:type n: int
:rtype: str
"""
s='1'
for i in range(n-1):
s=self.countAndReturn(s)
return s
def countAndReturn(self, str1):
writeIndex = 0
readIndex = 0
result = ''
while readIndex < len(str1):
current_char = str1[writeIndex]
count = 0
# 内层循环统计连续相同字符
while readIndex < len(str1) and str1[readIndex] == current_char:
count += 1
readIndex += 1
# 将计数和字符拼接为字符串
result += str(count) + current_char
# 更新指针到下一组字符的起始位置
writeIndex = readIndex
return result
方法二:穷举
官方题解上给了个穷举的方式,很离谱,不太推荐使用
Title39 combinationSum(组合总和) 复习回溯的好题
先再复习一遍回溯的模板:
def backtrack(路径, 选择列表): # 终止条件 if 满足结束条件: 结果集.append(路径的副本) # 注意需要深拷贝 return # 遍历所有选择 for 选择 in 选择列表: # 剪枝:跳过不符合条件的选择 if 选择不合法: continue # 做选择 将选择加入路径 更新选择列表(如标记已选元素) # 递归进入下一层决策树 backtrack(路径, 新选择列表) # 撤销选择(回溯核心) 将选择从路径中移除 恢复选择列表(如取消标记)
接下来就是套模板
我自己在解题时,一直在想怎么处理当为负值时,但是如果直接套用模板,便可通过剪枝的方式实现跳出循环,并将原有数据弹出。所有思考的难度就在于,循环列表是什么?路径又是什么?
路径很好理解,就是每轮循环时所添加入的数字。循环列表需要构思,我们所生成的列表可以采用从前向后遍历的方式,在保证前面数字出现的可能性都用完后,再去尝试下一个开端。所以循环列表我们只需要传入从数组中的那个位置开始作为起点访问。之所以每轮需要多传一个remain,是因为需要判断结束条件,因此在原有模板的基础上加一个remain,代码如下:
class Solution(object):
def combinationSum(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
candidates.sort() # 排序以便剪枝
res = []
def backtrack(start, path, remain):
# 终止条件:剩余值为0时记录有效组合
if remain == 0:
res.append(list(path)) # 深拷贝当前路径
return
for i in range(start, len(candidates)):
num = candidates[i]
if num > remain:
break # 剪枝:后续元素更大,无需继续
path.append(num) # 做选择
backtrack(i, path, remain - num) # 允许重复选当前元素
path.pop() # 撤销选择
backtrack(0, [], target)
return res
Day17
isMatch(通配符匹配)
错误解法:没有考虑*可以不匹配任何字符。
class Solution(object):
def isMatch(self, s, p):
"""
:type s: str
:type p: str
:rtype: bool
"""
s_index=0
p_index=0
while s_index<len(s) and p_index<len(p):
if p[p_index]=='*':
s_index=len(s)-(len(p)-p_index-1)
p_index+=1
elif p[p_index]=='?':
s_index+=1
p_index+=1
else:
if s[s_index]==p[p_index]:
s_index+=1
p_index+=1
else:
return False
if p_index>=len(p) and s_index>=len(s):
return True
else:
return False
正确解法:
方法一:官方题解(动态规划)
关于动态规划:
需要考虑一下几个问题:
1.状态方程怎么写?
2.边界条件是什么?
3.dp[i][j]表示的含义是什么?
在官方题解当中,横向的为模式串,用于去匹配纵向的被匹配串。
先回答第三个问题dp[i][j]表示的含义为从0到j的模式串长度是否和从0到i的被匹配串匹配成功
接着回答第一个问题,状态方程怎么去写?
需要分情况考虑:
1.当p[j]==s[i]时,需要考虑dp[i-1][j-1]是否为True,若为True,则dp[i][j]置为True,若为False,则置为False,这是第一个情况。
2.当p[j]==?时,此时只要不是空格,就一定能被匹配到,此时依然是看dp[i-1][j-1]的值,如果为True则True,否则反之即可
3.当p[j]==*时,此时需要看的就不是dp[i-1][j-1]了,而是只要dp[i][j-1]匹配成功,或者是dp[i-1][j]匹配成功,都可以直接置为True,对于dp[i-1][j],即被匹配串的s[i-1]之前都被p[j]之前成功匹配,此时在该位则是p[j]继续匹配s[i],*又多对应了一位相当于。对于dp[i][j-1],即相当于p[j]在该位置匹配了空格,两种情况都被包含了,完美解决问题
最后考虑边界条件的赋值
我们在模式串和匹配串的0号位置都加上一个空格,方便后续对应。dp[0][j]为空格和模式串匹配,模式串仅仅当为前j个均为*号时需要置为True,其余都为False。dp[i][0]则为被匹配串前i位是否和空格对应上,一定为False。所以大部分均为False,我们在初始化时,便可以为所有元素置为False。
以上便是逻辑梳理,代码如下:
class Solution(object):
def isMatch(self, s, p):
"""
:type s: str
:type p: str
:rtype: bool
"""
m, n = len(s), len(p)
dp = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True
for i in range(1, n + 1):
if p[i - 1] == '*':
dp[0][i] = True
else:
break
for i in range(1, m + 1):
for j in range(1, n + 1):
if p[j - 1] == '*':
dp[i][j] = dp[i][j - 1] | dp[i - 1][j]
elif p[j - 1] == '?' or s[i - 1] == p[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
return dp[m][n]