总结使用python3的刷题之路,日积月累,天道酬勤。
1.两数之和--2020/3/31
暴力方法,即迭代num1,同时每次迭代去寻找满足num2=target-num1的值。使用pyhton的in操作是一次遍历搜索。在C中一次循环。暴力方法要注意判断num2出现次数和避免找到num1自己。进一步提升:迭代num1(除第一个数),但在每次迭代中只对该数前面的数进行搜索(列表切片)。但最有意思的使用哈希求解
def twoSum(nums, target):
hashmap={}
for ind,num in enumerate(nums):
hashmap[num] = ind#获得哈希表
for i,num in enumerate(nums):
j = hashmap.get(target - num)
if j is not None and i!=j:
return [i,j]
2.二维数组中的查找--2020/4/2
若使用暴力法遍历矩阵 matrix ,则时间复杂度为 O(N*M) 。暴力法未利用矩阵 “从上到下递增、从左到右递增” 的特点,显然不是最优解法。
本题解利用矩阵特点引入标志数,并通过标志数性质降低算法时间复杂度。
标志数引入: 此类矩阵中左下角和右上角元素有特殊性,称为标志数。左上角和右下角分别是最大和最小的数,没有标志作用。
左下角元素: 为所在列最大元素,所在行最小元素。
右上角元素: 为所在行最大元素,所在列最小元素。
算法流程: 根据以上性质,设计算法在每轮对比时消去一行(列)元素,以降低时间复杂度。
从矩阵 matrix 左下角元素(索引设为 (i, j) )开始遍历,并与目标值对比:
当 matrix[i][j] > target 时: 行索引向上移动一格(即 i--),即消去矩阵第 i 行元素;
当 matrix[i][j] < target 时: 列索引向右移动一格(即 j++),即消去矩阵第 j 列元素;
当 matrix[i][j] == target 时: 返回true 。
若行索引或列索引越界,则代表矩阵中无目标值,返回 false 。
算法本质: 每轮 i 或 j 移动后,相当于生成了“消去一行(列)的新矩阵”, 索引(i,j) 指向新矩阵的左下角元素(标志数),因此可重复使用以上性质消去行(列)。
复杂度分析:
时间复杂度 O(M+N) :其中,N 和 M分别为矩阵行数和列数,此算法最多循环 M+N次。
空间复杂度 O(1) : i, j 指针使用常数大小额外空间。
3. 替换空格
第一种是使用replace函数,return s.replace(' ','%20'),第二种是遍历整个字符串,字符串是不可变类型,即无法直接修改字符串的某一位字符,可以通过s[i]来提取。所以第一步将其抓换成list,时间复杂度O(n)
,空间复杂度O(n)
class Solution(object):
def replaceSpace(self, s):
s = list(s)
for i in range(len(s)):
if s[i] == ' ':s[i] = '%20'
return ''.join(s)#将空给join in s
4.重建二叉树,采用递归的方法,先在前序中找到根节点,然后再中序中用根节点来区分左孩子和右孩子,依此递归。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
#递归法
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if len(inorder) == 0:
return None#由于递归中先序要弹出根节点,叶子节点也会被当作根节点判断所以不会为空。
root = TreeNode(preorder[0])
idx = inorder.index(preorder[0])#找到根节点在中序中的index.说明有index个左孩子
#由定义,先序中的[1:idx+1]为左孩子,中序中的[:idx]为左孩子
root.left = self.buildTree(preorder[1:idx+1],inorder[:idx])
root.right = self.buildTree(preorder[idx+1:],inorder[idx+1:])
return root
5.用两个栈实现队列,栈是先进后出,队列是先进先出。A栈用来接收,B栈用来倒序后弹出最先进的元素。
class CQueue:
def __init__(self):
self.A,self.B = [],[]
def appendTail(self, value: int) -> None:
self.A.append(value)
def deleteHead(self) -> int:
if self.B:return self.B.pop()
if not self.A:return -1
while self.A:
self.B.append(self.A.pop())
return self.B.pop()
6.斐波拉且数列,自己使用了一种动态规划的方式,用a,b记录,没有直接按定义迭代return fib(n-1) + fib(n-2)。取模是因为防止数字太大溢出。
class Solution:
def fib(self, n: int) -> int:
self.a,self.b= 0,1
if n== 0:return self.a
elif n == 1:return self.b
else:
for i in range(2,n+1):
self.a,self.b = self.b,int((self.b+self.a)%(1e9+7))#py语言的特性省略中间变量
i += 1
return self.b
7.青蛙跳台阶问题,这个问题是斐波拉且数列数列的改编。最后一步有两张选择,一种跳一步,一种跳两步,即所有可能为f(n)=f(n-1)+f(n-2)
8.旋转数组的最小数字,把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转,例如,数组 [3,4,5,1,2]
为递增数组[1,2,3,4,5]的一个旋转,1为旋转点,由于是递增数组,即为数组中最小的数字。直接return(min(numbers)),时间复杂O(N),空间O(1)。使用二分法搜索时,时间复杂度O(logn)
,空间复杂度O(1)
9.矩阵中的路径查找,使用深度优先搜索DFS,首先遍历整个矩阵,尝试将每个点作为起点开始搜索,对于超越边界和当前匹配字符不正确的情况,返回False。只有一种情况返回True,对于查找到字符个数等于目标字符长度k == len(word)-1。如果前面的这两次条件筛选都没有返回值,说明只是匹配当前值成功,继续往下递归查找,对当前字符的左右上下(具体时只有三种选择,另外一种为来时的路'/')进行匹配尝试,当然在尝试前要将当前字符进行替换,防止二次进入。时间复杂度 O(3^KMN), 空间复杂度 O(K) 。
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
def dfs(i,j,k):
if not (0<=i
10.机器人的运动范围计算,和前面这道题一样都是矩阵中的搜索,可以使用DFS和BFS两种方式,题目只用到矩阵的坐标,所以没有对已访问进行修改,而是使用一个元组进行记录。其中在求数位时还可以进行一定的优化。
#BFS,一般使用队列来存储同级的元素,方便按序逐个筛选。BFS把每走一步的所有可能计算完,才走下一步。
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
queue, visited, = [(0, 0)], set()
while queue:
i, j = queue.pop(0)
if i >= m or j >= n or i%10+i//10+j%10+j//10 >k or (i, j) in visited: continue
visited.add((i,j))
queue.append((i + 1, j))
queue.append((i, j + 1))
return len(visited)
#DFS,类似按照一条道走到最远,看看能不能找到。
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
def dfs(i,j):
if not 0<=ik or (i,j) in visited:return 0
visited.add((i,j))
return 1+dfs(i+1,j)+dfs(i,j+1)#从(0,0)开始只有两个方向。
visited = set()
n = dfs(0,0)
return n
11.剪绳子,使用动态规划进行求解,当然也有方法使用通过一定分析后得到的分治法求解。我们发现任何大于 3 的数都可以拆分为数字 1,2,3 的和,乘4或5...都没有拆分合算。且它们对 3 的余数总是0,1,2,因此我们可以仅用 dp[0],dp[1],dp[2] 表示所有大于 3 的值,这样空间复杂度可降到 O(1),时间复杂度O(N)
动态规划与其说是一个算法,不如说是一种方法论。该方法论主要致力于将合适的问题拆分成三个子目标一一击破:
class Solution:
def cuttingRope(self, n: int) -> int:
dp = [0 for _ in range(n+1)]
dp[2] = 1
for i in range(3,n+1):
for j in range(i):#后面可优化range(i)为range(1,4)
dp[i] = max(dp[i],max((i-j)*j,dp[i-j]*j))#缓存复用,剪掉j不再剪和继续剪比较
return dp[n]
优化后:拆分为1,2,3较合算,模3是为了节省空间。
class Solution:
def cuttingRope(self, n: int) -> int:
dp = [0,1,1]#数组存储,缓存复用,进行优化,与拆分不同,模3是出于节省空间,且比4好。
for i in range (3,n+1):
dp[i%3] = max(max(dp[(i-1)%3],i-1),#拆分为1,2,3,依次尝试
2*max(dp[(i-2)%3],i-2),
3*max(dp[(i-3)%3],i-3))#比较第一次剪3后,继续剪(拆分)和不剪得长度。
return dp[n%3]
12.数值得整数次方,用x =x**2进行优化,使得时间复杂度从单纯的O(N)到O(log2n)。一般分治奇偶,但使用数位运算可以统一,&1与>>=1...值得注意x*=x与x=x**2的计算结果相同,但python处理方式不同,前者数大溢出结果变为inf,后者溢出会报错。
class Solution:
def myPow(self, x: float, n: int) -> float:
if x == 0:return 0#考虑各种情况,分而治之
res = 1
if n<0:x,n = 1/x,-n
while n:
if n&1:res *= x #把幂n用二进制表示,&1
x*=x
n>>=1
return res
13.正则表达式匹配,较为困难,使用递归或DP,递归容易理解,时间复杂度较高,DP较为复杂。
class Solution:
def isMatch(self, s: str, p: str) -> bool:
#返回not s,若p为空,s为空,则匹配返回True,若p为空,s不为空,则不匹配。
if not p :return not s
#比较当前的第一个字符,p中的第一个字符肯定不能是*,
first_match = bool(s and p[0] in {s[0],'.'})
#当*在第二位p[1]时,有两种可能:*和*前的没用,如##与a*##,从p[2:]开始比较。
#有用,此时f_m比为真,如aaab和a*b,先丢掉一个,比较aab和a*b,递归变为b和a*b,成为第一种。
if len(p)>=2 and p[1] =='*':
return self.isMatch(s,p[2:]) or first_match and self.isMatch(s[1:],p)
else:
return first_match and self.isMatch(s[1:],p[1:])#第二位不为*时,递归
14.合并两个排序的链表,对于链表的操作:双指针,创建新链表,创建伪节点(伪节点.next = head)等。
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
tail = head = ListNode(0)#伪节点,head指向结果的头,tail一步一步指向尾
while l1 and l2:
if l1.val>=l2.val:
tail.next,l2 = l2,l2.next
else:
tail.next,l1 = l1,l1.next
tail=tail.next
tail.next = l1 if l1 else l2
return head.next
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
if not l1:return l2#逃出递归
if not l2:return l1
if l1.val>=l2.val:
head = ListNode(l2.val)
head.next = self.mergeTwoLists(l1,l2.next)#递归项
else:
head = ListNode(l1.val)
head.next = self.mergeTwoLists(l1.next,l2)
return head#递归需要找出逃出递归的结果,初始值,和递归项。时复较大。
15.树的子结构,采用递归+DFS,首先找到A树中余B树根节点相等的点,然后调用方法进行匹配,标志res只有在找到相同点,调用匹配方法,且DFS后,B树为空,才为真。
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
res = False
if A and B:#A与B都不能为空,题目要求
if A.val == B.val:
res = self.isSubTree(A,B)
if not res:#没有找到子结构继续找
res = self.isSubStructure(A.left,B)
if not res:
res = self.isSubStructure(A.right,B)
return res
def isSubTree(self,root_A,root_B):
if not root_B:return True#B被匹配完,返回真
if not root_A:return False
if root_A.val!=root_B.val:return False
return self.isSubTree(root_A.left,root_B.left) and self.isSubTree(root_A.right,root_B.right)
16.顺时针打印矩阵,一般才用简单模拟规则的方式,这里充分利用了pyhon中对list的操作。
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
res = []
while matrix:#注意
# 从左到右
res.extend(matrix.pop(0)) #pop(index)弹出当前的首行。弹出后matrix中没有此行
# 从上到下
for i in range(len(matrix)-1):#注意此处计算当前的matrix的行数
if matrix[i]:res.append(matrix[i].pop())#弹出每行的最后一个。
# 从右到左
if matrix:res.extend(matrix.pop()[::-1])#弹出最后一行,倒序
# 从下到上
for i in range(len(matrix)-1,-1,-1):#减到-1跳出
if matrix[i]:res.append(matrix[i].pop(0))#弹出每行的第一个。
return res
17.包含min函数的栈,题目中要求包括min()在内的几个函数的时间复杂度为O(1),普通的需要O(N)才能解决min(),这里使用辅助栈。
class MinStack:
def __init__(self):
self.A,self.B = [],[]#A为主栈,B为辅助栈,B栈接收比B栈栈顶还小的数,即当前最小数。
def push(self, x: int) -> None:
self.A.append(x)
if not self.B or self.B[-1]>=x:self.B.append(x) #B为空或者B中栈顶还小
def pop(self) -> None:
if self.A.pop() == self.B[-1]:self.B.pop()#同步B栈
def top(self) -> int:return self.A[-1]
def min(self) -> int:return self.B[-1]
18.栈的压入,弹出序列,题目是判断一个序列是否可能为压入序列的弹出序列。采用模拟规则的方式,由于pushed已经完成,所以必须申请一个新的栈来模仿正在进行操作的pushed。在判定时and具有先后顺序,遇到假就返回,全为真则是真。先判断正在鉴定的poped和tem都没有空,然后两个存在值相同。模拟后,新栈没有元素则为真。
class Solution:
def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
tem = []
while pushed:#把pushed中的元素按照一样的存储顺序给tem
tem.append(pushed.pop(0))
while popped and tem and tem[-1] == popped[0]:#注意顺序,不然出现超出index错误
popped.pop(0)
tem.pop()
return False if tem else True
19.从上到下打印二叉树,从左至右打印子节点。对于子节点需要储存,可以使用List栈,collections.deque(),deque双端队列,collections是python内建的一个集合模块,里面封装了许多集合类,其中队列相关的集合只有一个:deque,双端队列两边都可以增删元素,作用上相当于两个list栈。按照题目应该使用BFS,需要储存同宽度的数据这也是BFS的特点。
class Solution:
def levelOrder(self, root: TreeNode) -> List[int]:
if not root:return []
res,queue = [],[root]#res存储结果,queue缓存节点。
while queue:#没有子节点跳出
out = []#先加左节点,后加右节点,但要先弹出左节点,因此使用out缓存queue当前点的子节点
for i in queue:
res.append(i.val)
if i.left:out.append(i.left)
if i.right:out.append(i.right)
queue = out#out,queue可以使用collection中的deque代替。
return res
20.二叉搜索树的后序遍历序列,判断序列是否为二叉搜索树的后续遍历(左右根,根节点在最后一位)。二叉搜索树:左子树中所有节点的值 < 根节点的值;右子树中所有节点的值 >>根节点的值;其左、右子树也分别为二叉搜索树。这里使用递归解答。时间复杂度O(N*2),空间复杂度O(N)
class Solution:
def verifyPostorder(self, postorder: List[int]) -> bool:
def recur(i,j):
if i>=j:return True#说明已经递归到底,此子树节点数量≤1 ,无需判别正确性,因此直接返回
#上面是终止递归的条件,下面是通项操作
p = i
while postorder[p]postorder[j]:p+=1
return p == j and recur(i,m-1) and recur(m,j-1)
return recur(0,len(postorder)-1)
21.二叉树中和为某一值的路径,二叉树方案搜索问题,使用DFS解决,其包含 先序遍历 + 路径记录 两部分。由于需要记录路径所以申请path[]。回溯是DFS的特点,一般判断子结构通过返回真假进行回溯,搜索路径问题通过弹出该点返回上一个状态进行回溯,目的就是为了保证现有状态的正确,再去搜索每种可能。时间和空间复杂度都为O(N)
class Solution:
def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
res,path = [],[]
def dfs(root,tar):
if not root:return None#遍历每个节点前,为空,则返回空。
path.append(root.val)#加入当前遍历的节点
tar -= root.val
if tar == 0 and not root.left and not root.right:
res.append(list(path))
dfs(root.left,tar)
dfs(root.right,tar)
path.pop()#回溯,保证path中都是正确的路径。无论已经加入res,尝试其他,还是返回了空。
dfs(root,sum)
return res
22.复杂链表的复制,复制有深浅拷贝之分,可使用copy模块中的copy()和deepcopy()方法,它们都是深拷贝,可以直接retrun copy.deepcopy(head)。复制链表可以使用DFS方法。
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
def dfs(head):
if not head: return None
if head in visited:return visited[head]#
copy = Node(head.val, None, None)# 创建新结点
visited[head] = copy# 赋值是简单的引用,复制内存地址,其中一个改变另外一个也会改变。
copy.next = dfs(head.next)
copy.random = dfs(head.random)
return copy#DFS必定会向上一级递归返回,回溯
visited = {}#添加是否被访问过的标识字典。原链表的节点head为索引,相应新节点为值
return dfs(head)
23.二叉搜索树与双向链表,将二叉搜索树变为排序后的循环双向链表。可以知道,二叉搜索树的中序遍历是升序的遍历。同时将树节点的指向左儿子的指针变为指向前驱,指向右儿子的指针指向后继。时间空间:O(N)
class Solution:#方法嵌套要比分开写要好理解一点儿。父方法主流程,子方法遍历
def treeToDoublyList(self, root: 'Node') -> 'Node':
def dfs(cur):#中序遍历
if not cur: return
dfs(cur.left) # 递归左子树
if self.pre: # 修改节点引用
self.pre.right, cur.left = cur, self.pre # 右孩子指针指向后继
else: # 为空,则记录头节点
self.head = cur
self.pre = cur # 保存 cur
dfs(cur.right) # 递归右子树
if not root: return
self.pre = None # 先申请一个全局变量pre,指向当前节点的前驱
dfs(root)
self.head.left, self.pre.right = self.pre, self.head # 添加首尾循环
return self.head
24.字符串的排列,不重复地返回该字符串的全排列。首先,可以考虑DFS的方法,逐一尝试,同时记得要回溯。将该字符串分为两个部分,一个是当前位,当前位后面的每一位依次和当前为互换,知道当前位为最后一位记录结果,然后回溯。注意,要在例如“abb”中剪枝掉重复的。即在当前位为a时,后面的第一个b可以和a互换,但第二个b不能与a互换。此处可以设置一个元组,使得当前位置上,每个字符只能出现一次。第一个b和a交换后,首位出现过b了,将b加入元组,则后面的b不能在首位。时间O(N!),空间O(N*2)。
class Solution:
def permutation(self, s: str) -> List[str]:
c,res = list(s),[] # 相对dfs为全局变量。
def dfs(x): # x 为当前位,当前位后面的位依次和当前位交换,当前位前的固定
if x == len(c)-1: # 完成当前方案
res.append(''.join(c)) # ''.join(c)将list的c转化为一个字符串
return None
dic=set() # 注意dic元组为局部变量
for i in range(x,len(c)):
if c[i] in dic:continue # i的范围为[当前位,最后位]且全排列
dic.add(c[i])
c[x],c[i] = c[i],c[x] # 进行交换
dfs(x+1) # 交换后DFS下一位
c[x],c[i] = c[i],c[x] # 回溯
dfs(0)
print(res)
return res
25.数组中出现次数超过一半的数字,找出一个数组中出现次数超过长度一般的数,容易想到的方法有:
哈希表统计法: 遍历数组 nums ,用 HashMap 统计各数字的数量,最终超过数组长度一半的数字则为众数。此方法时间和空间复杂度均为 O(N)。
数组排序法: 将数组 nums 排序,由于众数的数量超过数组长度一半,因此 数组中点的元素 一定为众数。此方法时间复杂度 O(N log2 N)
摩尔投票法: 核心理念为 “正负抵消” ;时间和空间复杂度分别为 O(N) 和 O(1);是本题的最佳解法。注:核心就是减少数组的长度,最后减少到只剩众数(定义众数个数超过一半)。设真正的众数为x,当备选众数不为x,则减少了数组长度,减少长度中不超过一半得众数,则后面众数仍然超过一半。当备选为x,后面的数则不是x,抵消一半众数。
class Solution:
def majorityElement(self, nums: List[int]) -> int:
votes, count = 0, 0
for num in nums:
if votes == 0: x = num # 正负抵消后,设置当前首字符为备选众数
votes += 1 if num == x else -1 # 是备选众数的为正,不是为负。
# 验证数组中是否有众数,若确定有,后面可以省略
for num in nums:
if num == x: count += 1
return x if count > len(nums) // 2 else 0
26.数据流中的中位数,方法一:我们将添加的数保存在数组中,返回中位数时,只需将数组排序,返回中间位置数即可,时间O(nlogn),空间O(n)。
方法二:方法一的缺点在于对数组进行了排序操作,导致时间复杂度较高,假如每次插入一个值前数组已经排好序了呢?这样我们只需考虑每次将值插在合适的位置即可,所以使用二分查找来找到这个合适的位置,会将时间复杂度降低到O(n)(查找: O(logn),插入: O(n),将其他位往后移动),使用 bisect.insort_left(self.store, num) 插入每个数,bisect是python模块。
方法三:通过大小顶堆,Python 中 heapq 模块是小顶堆。实现 大顶堆 方法: 小顶堆的插入和弹出操作均将元素 取反 即可。取中位数只需要排序数组的中间两位,大小顶堆就是确保中间两位的正确,不管其它位,即保存较大部分的小顶堆的最小位和保存较小部分的大顶堆的最大位。每次加进去一个数,重点是对两个顶堆都要进行调整。单一从一个堆开始插入,这样无法利用起来两个堆,因此交叉对两个进行操作,先对B插入,弹出B的堆顶到A,下一次插入反过来。
from heapq import *
class MedianFinder:
def __init__(self):
self.A = [] # 小顶堆,保存较大的一半
self.B = [] # 大顶堆,保存较小的一半
def addNum(self, num: int) -> None:#
if len(self.A) != len(self.B):
heappush(self.A, num)
heappush(self.B, -heappop(self.A))
else:
heappush(self.B, -num)
heappush(self.A, -heappop(self.B))
def findMedian(self) -> float:
return self.A[0] if len(self.A) != len(self.B) else (self.A[0] - self.B[0]) / 2.0
27.1~n整数中1出现的次数,递归,f(n))函数的意思是1~n这n个整数的十进制表示中1出现的次数,将n拆分为两部分,最高一位的数字high和其他位的数字last,分别判断情况后将结果相加。high为1时,假如n为1234,递归寻找[1-999]中的+确定最高位1后[1001-1234](==last即[001-234])+在[1001-1234]确定最高位1的次数+1000(100,10,1)中的1。high为其他,类似,分别递归,最后+1000-1999(100-199,10-19)最高位的1的次数。
class Solution:
def countDigitOne(self, n: int) -> int:
if n <= 0:return 0
nums = str(n)
high = int(nums[0])
pows = 10**(len(nums)-1)
last = n - pows*high
if high == 1:
return self.countDigitOne(pows-1)+self.countDigitOne(last)+last+1
else:
return self.countDigitOne(pows-1)*high+self.countDigitOne(last)+pows
28.把数组排成最小数,将一个数组中的所有数来组成一个最小的数。本质上是一种排序,与普通的数值大小排序不同,本题需要的是一种'x'+'y'<'y'+'x',则'x'应该排在'y'前。可以使用改编快排或标准排序。修改的标准排序使用了functools.cmp_to_key
(func ),将旧式的comparison函数转换位key函数,即转换为sort()能接受的key函数。还有回溯的方法
#快排,O(nlogn),每次只确定一个数的位置,下面只修改了while中的比较,每次strs[l]为标准
class Solution:
def minNumber(self, nums: List[int]) -> str:
def fast_sort(l,r):
if l>=r:return None
i,j=l,r
while i=strs[l]+strs[j] and i
29.把数字翻译成字符串,dp,时间O(n),空间采用取余也省去dp数组,O(1)。由于本题动态具有对称,取余可以看作从右到左统计。当前状态依赖前面的两个状态,可以看出采用dp
class Solution:
def translateNum(self, num: int) -> int:
a = b = 1 # 倒数一位和倒数两位的状态都为1,即倒数一位和倒数两位组成的都只有一种翻译
y = num % 10 # y初始化为个位
while num != 0:
num //= 10
x = num % 10 # 在y前面一位
tmp = 10 * x + y
c = a + b if 10 <= tmp <= 25 else a # dp状态转移方程
a, b = c, a # a在前,b在后
y = x
return a
30.礼物的最大值,动态规划,从左边最上角道右边最下角所取得礼物最大值,只能向下或者向右。
class Solution:
def maxValue(self, grid: List[List[int]]) -> int:
for i in range(len(grid)):
for j in range(len(grid[0])):
if i == 0 and j == 0: continue
if i == 0: grid[i][j] += grid[i][j - 1] # 首行的只可能来自旁边的
elif j == 0: grid[i][j] += grid[i - 1][j] # 首列的只可能来自上面的
else: grid[i][j] += max(grid[i][j - 1], grid[i - 1][j]) # 一般的状态转移
return grid[-1][-1]
31.最长不含重复字符的子字符串,哈希+双指针,也可以使用哈希+dp,
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
dic, res, i = {}, 0, -1
for j in range(len(s)):
if s[j] in dic: # 当出现重复,更新左指针 i
i = max(dic[s[j]], i) #dic[s[j]],即s[j]上一次出现可能在i-j内,也可能之外
dic[s[j]] = j # 哈希表记录
res = max(res, j - i) # 更新结果,找到最长
return res
32.丑数,dp ,只包含因子 2、3 和 5 的数称作丑数。因此,丑数 = 某较小丑数× 某因子(2,3,5),则丑数序列的首个元素开始 *2 or*3 or*5 来计算, 丑数序列也是不会产生漏解的。
合并 3 个有序序列, 最简单的方法就是每一个序列都各自维护一个指针, 然后比较指针指向的元素的值, 将最小的放入最终的合并数组中, 并将相应指针向后移动一个元素。
class Solution:
def nthUglyNumber(self, n: int) -> int:
dp,a,b,c = [1]*n,0,0,0
for i in range(1,n):
n2,n3,n5 = dp[a]*2,dp[b]*3,dp[c]*5 # 三个指针,即三个数组
dp[i] = min(n2,n3,n5)
if dp[i] == n2: a+=1 # 采用的if...if...if...可以在合并时跳过重复解
if dp[i] == n3: b+=1
if dp[i] == n5: c+=1
return dp[-1]
33.数组中的逆序对,可以使用暴力法,也就是 O(N^2) 将所有可能枚举出来,如果满足逆,则 cnt+1,最后返回 cnt 即可。也可以使用归并排序对其进行优化。因为逆序对是前面的数大于后面的数就可以作为逆序对,因此,归并排序中归分完后,进行合并时,判断前后指针指向数的大小,如果前指针指向的数比后指针指向的数大,则前指针后面的数都比后指针指向的数大,后指针在后移前将该数加入temp中。后指针指向的数对于逆序对的贡献则为mid-i+1。时间O(nlogn),空间O(n)。
class Solution:
def reversePairs(self, nums: List[int]) -> int:
self.cnt = 0
def merge(nums, start, mid, end, temp):
i, j = start, mid + 1
while i <= mid and j <= end:
if nums[i] <= nums[j]:
temp.append(nums[i]) # 将小的加入temp
i += 1
else:
self.cnt += mid - i + 1 # nums[j]对逆序对数的贡献为 mid-i+1
temp.append(nums[j])
j += 1
while i <= mid:
temp.append(nums[i])
i += 1
while j <= end:
temp.append(nums[j])
j += 1
for i in range(len(temp)): # 修改nums中的排序,是的下次递归两部分是有序的
nums[start + i] = temp[i]
temp.clear()
def mergeSort(nums, start, end, temp): # 归并排序中的“归”,划分到最细粒度
if start >= end: return
mid = (start + end) >> 1
mergeSort(nums, start, mid, temp) #避免每次都开辟一个新的temp,因此也当作参数传入
mergeSort(nums, mid + 1, end, temp)
merge(nums, start, mid, end, temp) # 归并排序中的“并”
mergeSort(nums, 0, len(nums) - 1, [])
return self.cnt