赞
踩
https://www.runoob.com/python3/python3-built-in-functions.html
sorted(iterable, key=None, reverse=False)
key为函数引用:如:int,len,也可以是lambda函数
如:
l = [[2, 2, 3], [1, 4, 5], [5, 4, 9]]
l.sort(lambda x:x[0]) ##按照第一个元素进行排序
print(l) ##输出:[[1, 4, 5], [2, 2, 3], [5, 4, 9]]
reverse= False:默认从小到大
其他字典排序,字符排序例子可见:
https://www.cnblogs.com/dyl01/p/8563550.html
多列排序:
d1 = [{'name':'alice', 'score':38}, {'name':'bob', 'score':18}, {'name':'darl', 'score':28}, {'name':'christ', 'score':28}]
l = sorted(d1, key=lambda x:(-x['score'], x['name']))
print(l)
//输出
//[{'name': 'alice', 'score': 38}, {'name': 'christ', 'score': 28}, {'name': 'darl', 'score': 28}, {'name': 'bob', 'score': 18}]
重大发现——自定义排序,类似于java中重写比较器
functools包的cmp_to_key使用方法,cmp_to_key接受一个比较器
from functools import cmp_to_key
class Solution:
def minNumber(self, nums: List[int]) -> str:
return "".join(sorted([str(i) for i in nums],key=cmp_to_key(lambda x,y:1 if x+y>y+x else -1)))
filter(function, iterable)
function返回bool值,可以是lambda表达式
def is_odd(n):
return n % 2 == 1
newlist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(newlist)
》》[1, 3, 5, 7, 9]
enumerate(sequence, [start=0])
sequence – 一个序列、迭代器或其他支持迭代对象。
start – 下标起始位置。
返回 enumerate(枚举) 对象。
>>>seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>>list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>>list(enumerate(seasons, start=1)) # 小标从 1 开始
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
eval(expression[, globals[, locals]])
expression – 表达式。
globals – 变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。
locals – 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。
>>>x = 7
>>> eval( '3 * x' )
21
>>> eval('pow(2,2)')
4
>>> eval('2 + 2')
4
>>> n=81
>>> eval("n + 4")
85
x – 整数。
返回2,16,8进制字符串。
ASCII、字符转换chr(),ord()
chr(ASCII)–把ASCII码转换为字符
ord(char)–把字符转换为ASCII码
即去重后的字典{},可以执行交并差的集合运算,在表达式求值中可能用到
//定义 set([iterable]) //使用 >>>x = set('runoob') >>> y = set('google') >>> x, y (set(['b', 'r', 'u', 'o', 'n']), set(['e', 'o', 'g', 'l'])) # 重复的被删除 >>> x & y # 交集 set(['o']) >>> x | y # 并集 set(['b', 'e', 'g', 'l', 'o', 'n', 'r', 'u']) >>> x - y # 差集 set(['r', 'b', 'u', 'n']) //常用 set().add()---------自动去重 set ().remove()
详细可见:
https://www.cnblogs.com/traditional/p/10444299.html
常数:e,pi,tau
三角函数:
反三角函数:
对数:
指数:
幂函数:pow(x,y)=x**y
最大公约数:gcd(a,b)
阶乘:factorial(n)
二范数:hypot(x,y)
a)Counter: 计数器,用于统计元素的数量,返回字典,在统计字符串中字母出现个数时非常方便
b)OrderDict:有序字典
c)defaultdict(参数):值带有默认类型的字典,默认类型为参数类型(如:list,int,None等),缺少的键使用get()返回一个该类型的缺省值
注:普通字典类,使用get(键,默认值):没有该键就返回缺省值
d)namedtuple:可命名元组,通过名字来访问元组元素
e)deque :双向队列,队列头尾都可以放,也都可以取(与单向队列对比,单向队列只能一头放,另一头取)
heappush(heap,item) 往堆中插入一条新的值
heappop(heap) 从堆中弹出最小值
heapreplace(heap,item) 从堆中弹出最小值,并往堆中插入item
heappushpop(heap,item) Python3中的heappushpop更高级
heapify(x) 以线性时间将一个列表转化为堆
merge(*iterables,key=None,reverse=False) 合并对个堆,然后输出
nlargest(n,iterable,key=None) 返回可枚举对象中的n个最大值并返回一个结果集list
nsmallest(n,iterable,key=None) 返回可枚举对象中的n个最小值并返回一个结果集list
浅拷贝
a2 = copy.copy(a)
深拷贝
a2 = copy.deepcopy(a)
count()
生成无界限序列,count(start=0, step=1) ,示例从100开始,步长为2,循环10,打印对应值;必须手动break,count()会一直循环。
product(iter1,iter2, … iterN, [repeat=1]);
创建一个迭代器,生成表示item1,item2等中的项目的笛卡尔积的元组,repeat是一个关键字参数,指定重复生成序列的次数
permutations() :全排列
普通队列 和 栈 直接可以用list模拟就好了,使用pop和append就好,可以完成
栈:
尾部FIFO:.pop() .append()
队列:
尾进头出:append() pop(0)
头进尾出:insert(0,obj) pop()
使用queue的PriorityQueue模块可以实现优先队列:
按照优先级出队列和入队列,默认优先级数字越小越高,当没有传递优先级时,采用数字本身大小的排序号作为优先级,越小的数字优先级越低。
PriorityQueue提供的方法:
put:入queue
get:出queue
empty():判空
from queue import PriorityQueue
class Solution:
def connectSticks(self, sticks: list) -> int:
cost=0
q=PriorityQueue()
for i in sticks:
q.put(i)
while not q.empty():
a=q.get()
if q.empty():
return cost
else:
b=q.get()
q.put(a+b)
cost+=a+b
总结:
适用题型:
index查找或数值查找,不一定需要有序,重点是选择区间的条件需要根据题目意思来构造
代码模板:
1、初始化左右指针
2、循环终止条件:while left <= right或while left < right:
3、迭代:mid = left + (right - left) // 2
4、判定条件,选择下一个搜索区间:修改左右指针:
left = mid + 1,防止整除使得right-left为1时,left不变,进入死循环
right = mid-1,防止left与left相等,但是mid不满足return条件,进入死循环
5、循环终止返回值:left,与right需要根据题意不同,返回不同
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
输入: [1,3,5,6], 5
输出: 2
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid-1
return left
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842…,
由于返回类型是整数,小数部分将被舍去。
class Solution:
def mySqrt(self, x: int) -> int:
if x==0 or x==1:
return x
low=0
up=x
while (up>=low):
mid = (low + up) // 2
if mid>x/mid:
up=mid-1
elif mid<x/mid:
low=mid+1
else:
return mid
return up
注:此处采用除法判断,因此需要处理0,1特殊值
也可采用乘法判断
class Solution:
def mySqrt(self, x: int) -> int:
low=0
up=x
while (up>=low):
mid = (low + up) // 2
if mid*mid>x:
up=mid-1
elif mid*mid<x:
low=mid+1
else:
return mid
return up
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
这个题由于是找出两个值,所以感觉二分不太好用,大多数采用的双指针
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
class Solution: def firstBadVersion(self, n): """ :type n: int :rtype: int """ left=1 right=n while left<=right: mid=(left+right)>>1 if isBadVersion(mid): if not isBadVersion(mid-1): return mid right=mid-1 else: left=mid+1 return left
二分查找进阶(中等)
分段有序
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
class Solution: def search(self, nums: List[int], target: int) -> int: n = len(nums) if n == 0: return -1 left = 0 right = n - 1 while left < right: mid = left + (right - left) // 2 if nums[mid] == target: return mid elif 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 #print(left,right) return left if nums[left] == target else -1
注意:
1、循环条件不包含等于=
2、程序结束输出前判断
说明:
在二分查找的判断中嵌套了一层判断,用于判断mid分割的区间,那一个为有序区间,若为有序区间,则判断target是否在该区间内
有重复数字
class Solution: def searchRange(self, nums: list, target: int): idx=[-1,-1] if not nums: return idx left,right=0,len(nums)-1 while left<=right: mid=(left+right)>>1 if nums[mid]==target: idx[0]=mid while idx[0]>0 and nums[idx[0]-1]==target: idx[0]-=1 idx[1]=mid while idx[1]<len(nums)-1 and nums[idx[1]+1]==target: idx[1]+=1 return idx elif nums[mid]<target: left=mid+1 else: right=mid-1 return idx
思路是二分找到mid,依次向两边拓展找到第一个和最后一个
数组没有绝对的大小关系,但是可以根据mid处的斜率(mid与mid-1和mid+1的大小关系,判断下一个区间为左右哪个区间)
峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞。
示例 1:
输入: nums = [1,2,3,1]
输出: 2
解释: 3 是峰值元素,你的函数应该返回其索引 2。
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
left,right=0,len(nums)-1
while left<right:
mid=(left+right)>>1
if nums[mid]<nums[mid+1]:
left=mid+1
elif nums[mid-1]>nums[mid]:
right=mid-1
else:
return mid
return left
1、初始化队列,加入根节点
2、while 循环条件:队列不为空:
执行:
(做你需要做的任务:如打印当前值,记录层数等),从队列取出节点,如果该节点左孩子不为空,则加入队列,如果右孩子不为空,则继续加入队列。
https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
class Solution: def levelOrder(self, root: TreeNode) -> List[List[int]]: if not root: return [] ans=[] q=[root] while q: ceng=[]#这一层的数值 qtmp=[]#下一层的节点 for i in range(len(q)): node=q.pop(0) ceng.append(node.val) if node.left: qtmp.append(node.left) if node.right: qtmp.append(node.right) q=qtmp ans.append(ceng) return ans
最简单的解法就是递归,首先确认树非空,然后调用递归函数 helper(node, level),参数是当前节点和节点的层次。程序过程如下:
1、输出列表称为 levels,当前最高层数就是列表的长度 len(levels)。比较访问节点所在的层次 level 和当前最高层次 len(levels) 的大小,如果前者更大就向 levels 添加一个空列表。
2、将当前节点插入到对应层的列表 levels[level] 中。
3、递归非空的孩子节点:helper(node.left / node.right, level + 1)
样例程序:
https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
class Solution: def levelOrder(self, root): """ :type root: TreeNode :rtype: List[List[int]] """ levels = [] if not root: return levels def helper(node, level): # start the current level if len(levels) == level: levels.append([]) # append the current node value levels[level].append(node.val) # process child nodes for the next level if node.left: helper(node.left, level + 1) if node.right: helper(node.right, level + 1) helper(root, 0) return levels
给定两个二叉树,编写一个函数来检验它们是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
全部都对比完,没有提前返回,则为True
class Solution1: def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: # 使用队列 pl, ql = [p], [q] while pl and ql: a = pl.pop(0) b = ql.pop(0) if a == None and b == None: continue if a == None or b == None or a.val != b.val: return False pl.append(a.left) ql.append(b.left) pl.append(a.right) ql.append(b.right) return True
也可采用递归,依次判断当前值和左右子树是否相同
#递归判断方法,写起来简单
class Solution:
def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
if not p and not q:
return True
if not p or not q:
return False
return p.val==q.val and self.isSameTree(p.left,q.left) \
and self.isSameTree(p.right, q.right)
class Solution: def isSymmetric(self, root: TreeNode) -> bool: p=[root] q=[root] while q and p: r1=p.pop(0) r2=q.pop(0) if not r1 and not r2: continue if not r1 or not r2 or r1.val!=r2.val: return False p.append(r1.left) p.append(r1.right) q.append(r2.right) q.append(r2.left) return True
采用递归:
#创建镜像子函数,递归调用该函数
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
return self.ismirror(root, root)
def ismirror(self, r1, r2):
if not r1 and not r2:
return True
if not r1 or not r2:
return False
return r1.val == r2.val and \
self.ismirror(r1.right, r2.left) and self.ismirror(r2.right, r1.left)
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
class Solution: def levelOrderBottom(self, root: TreeNode) -> List[List[int]]: if not root: return [] ceng_all=[] p=[root] while p: l=len(p) ceng=[] for i in range(l): if p[0].val!=None: ceng.append(p[0].val) if p[0].left: p.append(p[0].left) if p[0].right: p.append(p[0].right) p.pop(0) ceng_all.append(ceng) return ceng_all[::-1]
该广度优先遍历,while中嵌套for循环,记录每层的信息,还可用于计算树的深度
(最大深度:while循环,处理一层深度加一,while循环结束即可;
最小深度,处理到叶子节点即可返回深度)
你现在手里有一份大小为 N x N 的『地图』(网格) grid,上面的每个『区域』(单元格)都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,你知道距离陆地区域最远的海洋区域是是哪一个吗?请返回该海洋区域到离它最近的陆地区域的距离。
我们这里说的距离是『曼哈顿距离』( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个区域之间的距离是 |x0 - x1| + |y0 - y1| 。
如果我们的地图上只有陆地或者海洋,请返回 -1。
示例 1:
输入:[[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释:
海洋区域 (1, 1) 和所有陆地区域之间的距离都达到最大,最大距离为 2。
解题思路:
采用四叉树的数据结构;把地图上每块陆地作为初始根节点,像前后左右四个方向进行广度优先遍历(考虑边界条件),遍历到的海洋置为陆地,并将距离数据存入队列当中,这样最后到达的一块海洋则是我们要找的海洋(所有海洋中距离陆地最远):
class Solution: def maxDistance(self, grid: list) -> int: r, c = len(grid), len(grid[0]) rot = list() for i in range(r): for j in range(c): if grid[i][j] == 1: rot.append((i, j, 0)) d = [(0, 1), (0, -1), (1, 0), (-1, 0)] res = 0 while rot: i, j, res = rot.pop(0) for xd, yd in d: x = i + xd y = j + yd if 0 <= x < r and 0 <= y < c and grid[x][y] == 0: grid[x][y] = 1 rot.append((x, y, res + 1)) return res if res != 0 else -1
给你 n 个盒子,每个盒子的格式为 [status, candies, keys, containedBoxes] ,其中:
状态字 status[i]:整数,如果 box[i] 是开的,那么是 1 ,否则是 0 。
糖果数 candies[i]: 整数,表示 box[i] 中糖果的数目。
钥匙 keys[i]:数组,表示你打开 box[i] 后,可以得到一些盒子的钥匙,每个元素分别为该钥匙对应盒子的下标。
内含的盒子 containedBoxes[i]:整数,表示放在 box[i] 里的盒子所对应的下标。
给你一个 initialBoxes 数组,表示你现在得到的盒子,你可以获得里面的糖果,也可以用盒子里的钥匙打开新的盒子,还可以继续探索从这个盒子里找到的其他盒子。
请你按照上述规则,返回可以获得糖果的 最大数目 。
示例 1:
输入:status = [1,0,1,0], candies = [7,5,4,100], keys = [[],[],[1],[]], containedBoxes = [[1,2],[3],[],[]], initialBoxes = [0]
输出:16
解释:
一开始你有盒子 0 。你将获得它里面的 7 个糖果和盒子 1 和 2。
盒子 1 目前状态是关闭的,而且你还没有对应它的钥匙。所以你将会打开盒子 2 ,并得到里面的 4 个糖果和盒子 1 的钥匙。
在盒子 1 中,你会获得 5 个糖果和盒子 3 ,但是你没法获得盒子 3 的钥匙所以盒子 3 会保持关闭状态。
你总共可以获得的糖果数目 = 7 + 4 + 5 = 16 个。
https://leetcode-cn.com/problems/maximum-candies-you-can-get-from-boxes/
思路: 队列实现BFS
对于第 i 个盒子,我们只有拥有这个盒子(在初始时就拥有或从某个盒子中开出)并且能打开它(在初始时就是打开的状态或得到它的钥匙),才能获得其中的糖果。我们用数组 has_box 表示每个盒子是否被拥有,数组 can_open 表示每个盒子是否能被打开。在搜索前,我们只拥有数组 initialBoxes 中的那些盒子,并且能打开数组 status 值为 0 对应的那些盒子。如果一个盒子在搜索前满足这两条要求,就将其放入队列中。
在进行广度优先搜索时,每一轮我们取出队首的盒子 k 将其打开,得到其中的糖果、盒子 containedBoxes[k] 以及钥匙 keys[k]。我们将糖果加入答案,并依次枚举每个盒子以及每把钥匙。在枚举盒子时,如果该盒子可以被打开,就将其加入队尾;同理,在枚举钥匙时,如果其对应的盒子已经被拥有,就将该盒子加入队尾。当队列为空时,搜索结束,我们就得到了得到糖果的最大数目。
class Solution: def maxCandies(self, status: List[int], candies: List[int], keys: List[List[int]], containedBoxes: List[List[int]], initialBoxes: List[int]) -> int: n = len(status) can_open = [status[i] == 1 for i in range(n)] has_box, used = [False] * n, [False] * n q = collections.deque() ans = 0 for box in initialBoxes: has_box[box] = True if can_open[box]: q.append(box) used[box] = True ans += candies[box] while len(q) > 0: big_box = q.popleft() # 遍历盒子中的钥匙,更新q for key in keys[big_box]: can_open[key] = True if not used[key] and has_box[key]: q.append(key) used[key] = True ans += candies[key] # 遍历盒子中的盒子,更新q for box in containedBoxes[big_box]: has_box[box] = True if not used[box] and can_open[box]: q.append(box) used[box] = True ans += candies[box] return ans
深度优先遍历包含:先序、中序、后序遍历;其中的先中后值得是根节点,左右孩子的先后顺序都是左孩子–》右孩子
图解
一个重要的知识点是,根据中序,和先序(后序),写出后序(先序)
知道先序是根->左->右,中序是左->根->右,后序是左->右->根;
最重要的一点就是:(根据先序或后序)找到根(根在先序第一个,后序最后一个)->(根据根在中序)找到左右子树;一直重复这个操作,直到最后一个子节点。
先序:
def preorder(self, root):
"""递归实现先序遍历"""
if root == None:
return
print root.elem
self.preorder(root.lchild)
self.preorder(root.rchild)
中序:
def inorder(self, root):
"""递归实现中序遍历"""
if root == None:
return
self.inorder(root.lchild)
print root.elem
self.inorder(root.rchild)
后序:
def postorder(self, root):
"""递归实现后续遍历"""
if root == None:
return
self.postorder(root.lchild)
self.postorder(root.rchild)
print root.elem
def preorder(self, root):
stack=[root]
"""栈实现先序遍历"""
while stack:
cur=stack.pop()
print(cur.val)
if cur.left:
stack.append(cur.left)
if cur.right:
stack.append(cur.right)
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
示例:
给定如下二叉树,以及目标和 sum = 22,
class Solution: def hasPathSum(self, root: TreeNode, sum: int) -> bool: if not root: return False q = [(root.val, root)] allsum = [] while q: cursum, node = q.pop() if not node.right and not node.left: allsum.append(cursum) if node.left: q.append((cursum + node.left.val, node.left)) if node.right: q.append((cursum + node.right.val, node.right)) return sum in allsum
深度优先搜索没有层数信息,一般可以将层数和其他信息,与节点一起加入栈中。
也可使用递归:
class Solution:
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
if not root:
return False
if not root.left and not root.right:
return sum==root.val
else:
return self.hasPathSum(root.right,sum-root.val) or \
self.hasPathSum(root.left,sum-root.val)
给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。
说明:
你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。
class Solution: def kthSmallest(self, root: TreeNode, k: int) -> int: global num,ans num=0 ans=None def dfs(root,k): global num,ans if not root: return None dfs(root.left,k) num+=1 if num==k: ans=root.val return None dfs(root.right,k) dfs(root,k) return ans
中序遍历的顺序,可将BST按大小遍历
https://leetcode-cn.com/contest/weekly-contest-143/problems/path-in-zigzag-labelled-binary-tree/
二叉树按照层次遍历,赋给下标,给定某个节点下标,可以根据下标计算其父亲节点的下标
输入:label = 14
输出:[1,3,4,14]
class Solution: def dfs(self,lev,x:int): if lev==1: return [1] rtn=self.dfs(lev-1,x//2) node=(1<<(lev-1));real=x if lev&1: # 实际下标=当前层数可容纳的最多的数字-当前层的个数 real=node*2-(x-node)-1 rtn.append(real) return rtn def pathInZigZagTree(self, label: int): lev=0;now=label while now>0: now//=2 lev+=1 node=(1<<(lev-1));real=label if lev&1:#奇偶判断 real=(node*2-label)+node-1 return self.dfs(lev,real)
可拓展4连通为8连通
给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例 2:
输入:
11000
11000
00100
00011
采用DFS,
class Solution1: def numIslands(self, grid: list) -> int: try: m = len(grid) n = len(grid[0]) except: return 0 # -------------------------DFS 开始------------------------ # 定义dfs递归方程 def dfs(i, j): if 0 <= i < m and 0 <= j < n and int(grid[i][j]): grid[i][j] = '0' for a, b in ((1, 0), (0, -1), (-1, 0), (0, 1)): dfs(i + a, j + b) # --------------------------------------------------------- r = 0 for i in range(m): for j in range(n): r += int(grid[i][j]) dfs(i, j) # 调用dfs沉没一整块陆地 return r
8连通修改一下元组即可
有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 )。
我们从一块陆地出发,每次可以往上下左右 4 个方向相邻区域走,能走到的所有陆地区域,我们将其称为一座「岛屿」。
如果一座岛屿 完全 由水域包围,即陆地边缘上下左右所有相邻区域都是水域,那么我们将其称为 「封闭岛屿」。
请返回封闭岛屿的数目。
输入:grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]]
输出:2
解释:
灰色区域的岛屿是封闭岛屿,因为这座岛屿完全被水域包围(即被 1 区域包围)。
在上一题的基础上判断岛屿有没有与边界接壤
简单暴力的办法可以先dfs边缘的数据;我是采用类变量flag在dfs过程中判断
class Solution: def __init__(self): # 类里面的全局变量失效,应该采用这个形式传递参数 self.flag=0 def closedIsland(self, grid) -> int: try: m = len(grid) n = len(grid[0]) except: return 0 # -------------------------DFS 开始------------------------ # 定义dfs递归方程 def dfs(i, j): if 0 <= i < m and 0 <= j < n and not grid[i][j]: # 如果是碰到边缘的陆地,则不是我们想要的岛屿 if (i == 0 or i == m - 1 or j == 0 or j == n - 1): self.flag = 0 grid[i][j] = 1 for a, b in ((1, 0), (0, -1), (-1, 0), (0, 1)): dfs(i + a, j + b) # --------------------------------------------------------- r = 0 for i in range(m): for j in range(n): self.flag = 1 - grid[i][j] # 当前是一块陆地则 flag=1 dfs(i, j) # 调用dfs沉没一整块陆地 r += self.flag return r
你有一套活字字模 tiles,其中每个字模上都刻有一个字母 tiles[i]。返回你可以印出的非空字母序列的数目。
输入:“AAB”
输出:8
解释:可能的序列为 “A”, “B”, “AA”, “AB”, “BA”, “AAB”, “ABA”, “BAA”。
示例 2:
输入:“AAABBC”
输出:188
提示:
1 <= tiles.length <= 7
tiles 由大写英文字母组成
采用递归实现DFS,完成全排列,使用set去重
核心部分:回溯的时候,将后继节点的状态修改为可发现(这一点和一般的图DFS不同)
class Solution(object): def numTilePossibilities(self, tiles): """ :type tiles: str :rtype: int """ ans = set() v=[True]*len(tiles) now = [] def dfs(x): if x >= len(tiles): return for i in range(len(tiles)): if v[i]: v[i] = False now.append(tiles[i]) ans.add(''.join(now)) dfs(x+1) # 发现完该节点的最深的树后,将该节点置为可见,以备下次发现 # now用于存储当前发现的子树的所有信息 v[i] = True now.pop() dfs(0) return len(ans)
用以太网线缆将 n 台计算机连接成一个网络,计算机的编号从 0 到 n-1。线缆用 connections 表示,其中 connections[i] = [a, b] 连接了计算机 a 和 b。
网络中的任何一台计算机都可以通过网络直接或者间接访问同一个网络中其他任意一台计算机。
给你这个计算机网络的初始布线 connections,你可以拔开任意两台直连计算机之间的线缆,并用它连接一对未直连的计算机。请你计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 -1 。
输入:n = 4, connections = [[0,1],[0,2],[1,2]]
输出:1
解释:拔下计算机 1 和 2 之间的线缆,并将它插到计算机 1 和 3 上。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-operations-to-make-network-connected
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解法1: 并查集
并查集原理详见:https://www.cnblogs.com/yscl/p/10185293.html
主要包含:初始化方式,查的递归实现和迭代实现(路径压缩),并
class Solution: def makeConnected(self, n: int, connections: List[List[int]]) -> int: uf = [-1 for i in range(n + 1)] global sets_count sets_count = n # 查 def find(p): while uf[p] >= 0: p = uf[p] return p # 并 def union(p, q): global sets_count proot = find(p) qroot = find(q) if proot == qroot: return elif uf[proot] > uf[qroot]: # 负数比较, 左边规模更小 uf[qroot] += uf[proot] uf[proot] = qroot else: uf[proot] += uf[qroot] # 规模相加 uf[qroot] = proot sets_count -= 1 # 连通后集合总数减一 if n-1 >len(connections): return -1 for a,b in connections: union(a,b) return sets_count-1
一个简版:
class Solution: def makeConnected(self, n: int, connections: List[List[int]]) -> int: D = list(range(n)) def T(a): if D[D[a]] == D[a]: return D[a] D[a] = T(D[a]) return D[a] def C(a, b): if T(a) != T(b): D[T(a)] = T(b) return 1 return 0 if len(connections) < n - 1: return -1 return n - 1 - sum(C(a, b) for a, b in connections)
解法2:图遍历
参考https://leetcode-cn.com/problems/number-of-operations-to-make-network-connected/submissions/
from collections import deque class Solution: def makeConnected(self, n: int, connections: List[List[int]]) -> int: num_component = 0 #G中连通分量的数目 num_remains = 0 #G中冗余边的数目 #构建图G的邻接表表示: G = [] for i in range(0, n): G.append([]) for pair in connections: G[pair[0]].append(pair[1]) G[pair[1]].append(pair[0]) #以BFS统计G中连通分量和冗余边的数目: color = [0] * n for computer in range(0, n): if(color[computer] != 0): continue #对新发现的包含computer的连通分量进行遍历 num_component += 1 num_node = 0 #该连通分量中的结点数 num_degree = 0 #该连通分量中所有结点度的和 Q = deque([]) Q.append(computer) color[computer] = 1 while(len(Q) != 0): u = Q.popleft() num_node += 1 for v in G[u]: if not color[v]: color[v] = 1 Q.append(v) num_degree += 1 num_remains += (num_degree / 2) - (num_node - 1) #判断是否可行: if num_component - 1 <= num_remains: return num_component - 1 else: return -1
有N个快递盒子,表示为[长,宽,高],数值均小于10000,要把小盒子装在大盒子里,一个盒子只能直接装一个盒子,问你你个盒子最多可包含多少个盒子(加上自己)
class Solution: def is_bigger(self, box1, box2): if box1[0] > box2[0] and box1[1] > box2[1] and box1[2] > box2[2]: return True return False # 以box当做节点深度遍历 def dfs(self, box, cnt): if len(self.d[box]) == 0: # 更新最大深度 self.max_cnt = max(self.max_cnt, cnt) for box in self.d[box]: # 如果box已经被访问了,说明是该box孩子的子孙,肯定深度没有之前大(该节点->孙子 小于 该节点->孩子->孩子) # 所以不用访问了 if self.visit[box] == False: self.visit[box] = True self.dfs(box, cnt + 1) def maxBoxes(self, boxes): # write code here boxes = [(x[0], x[1], x[2]) for x in boxes] boxes = sorted(boxes, key=lambda x: x[0] * x[1] * x[2]) import collections # d是记录每个box的孩子 self.d = collections.defaultdict(list) box_cnt = len(boxes) # roots记录森林的根节点 self.roots = [x for x in boxes] for i in range(box_cnt): for j in range(i + 1, box_cnt): if self.is_bigger(boxes[j], boxes[i]): self.d[boxes[i]].append(boxes[j]) if boxes[j] in self.roots: self.roots.remove(boxes[j]) self.max_cnt = 1 # visit作用见dfs函数 self.visit = dict() for root in self.roots: # 每次遍历树,都要把所有节点置为未访问 for i in range(box_cnt): self.visit[boxes[i]] = False self.root = root self.dfs(root, 1) return self.max_cnt s = Solution() boxes = [[5, 4, 3], [5, 4, 5], [6, 6, 6],[4, 3, 2]] print(s.maxBoxes(boxes))
所有的DP都可以使用递归来实现,往往来说,递归实现起来最简单;可以先从递归入手,找到最优子结构(就是核心的状态转移方程,如何从上一步——-》下一步),再尝试采用DP来求解。
其实不是很重要;维数只是DP数组的维数,代表着DP数组存储的结果与多少个参数有联系;而并不是循环的次数,一般来说DP都是两层循环,根据题意可能增加循环层数。
一般来说,最终写出来效率最高的是自底向上的迭代方法;该方法与递归的自顶向下有所不同,需要初始化DP数组,其一般步骤为:
(1)初始化DP[][]=0/±inf;根据边界条件获得部分DP数组的值,该值可以满足后续的计算的要求,即状态转移方程,右边的式子的每一项都可以在边界条件中找到
(2)根据影响DP数组结果的参数个数,确定循环层数,根据题意、一些边界条件,确定循环的起始和终止值,即DP数组的下标取值
(3)设置合理的条件,if():满足计算这一次的值
有最优子结构:原问题的最优解包含子问题的最优解
子问题重叠:子问题空间足够小,递归算法会反复求解相同的子问题,而不是产生新的子问题
(1)自顶向下递归(一般不用,时间复杂度太大)
(2)自顶向下递归+备忘录(有时候备忘录的记录会比较复杂,详见本文leetcode5274)
(3)自底向上迭代(最为高效,但是边界条件有时候比较麻烦)
注意,lru_cache神器可以使得自顶向下的递归,获得近似于备忘录
from functools import lru_cache
class Solution:
@lru_cache()
def cuttingRope(self, n: int) -> int:
if n == 2:
return 1
res = -1
for i in range(1, n):
res = max(res, max(i * self.cuttingRope(n - i),i * (n - i)))
return res
(1)
(2)
(3)
以下为面试典型题:参考
https://github.com/datawhalechina/Daily-interview/blob/master/coding/dp.md
此处所说的一维或二维dp指的只是使用的空间:一维dp是元素列表,二维dp是数组;而不是算法的复杂度
核心代码
dp = [1] * size
for i in range(1, size):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
# 最后要全部走一遍,看最大值
return max(dp)
给出一个由无重复的正整数组成的集合,找出其中最大的整除子集,子集中任意一对 (Si,Sj) 都要满足:Si % Sj = 0 或 Sj % Si = 0。
如果有多个目标子集,返回其中任何一个均可。
与之前的不同之处,这一题不是求最大子集的元素个数,而需要重构最优解:
class Solution(object): def largestDivisibleSubset(self, nums): """ :type nums: List[int] :rtype: List[int] """ if not nums: return nums if len(nums) == 1: return nums l = len(nums) nums.sort() dp = [1]*l #核心代码与最长上升子序列类似 for i in range(1, l): for j in range(i): if not nums[i]%nums[j]: dp[i] = max(dp[j] + 1, dp[i]) #重构最优解 maxl=max(dp) ans=[] for i in range(l-1,-1,-1): if maxl==dp[i]: if not ans: ans.append(nums[i]) maxl-=1 elif not ans[-1]%nums[i]: ans.append(nums[i]) maxl-=1 return ans[::-1]
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
状态转移方程
前i日最大利润=max(前(i−1)日最大利润,第i日价格−前i日最低价格)
代码:
迭代的同时维持价格最小,利润最大
class Solution:
def maxProfit(self, prices: List[int]) -> int:
maxprofit=0
minprice=1<<31
for i in range(1,len(prices)):
minprice=min(minprice,prices[i-1])
maxprofit=max(maxprofit,prices[i]-minprice)
return maxprofit
我们把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1 是丑数。
n 不超过1690。
class Solution { public int nthUglyNumber(int n) { int dp[]=new int[1690]; dp[0]=1; int idx2=0,idx3=0,idx5=0; int n2,n3,n5; for (int i=1;i<n;i++){ n2=dp[idx2]*2;n3=dp[idx3]*3;n5=dp[idx5]*5; dp[i]=Math.min(Math.min(n2,n3),n5); if (dp[i]==n2) idx2++; if (dp[i]==n3) idx3++; if (dp[i]==n5) idx5++; } return dp[n-1]; } }
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
解析
我刚开始的想法是,使用一个队列,依次遍历字符;如果该字符包含在队列中,则队头出队直到遇到队头元素和该字符相同;如果字符不在队列中,则字符入队;每次统计队列长度;这样的空间复杂度和时间复杂度都是O(n)
但是看到了大佬的解析,使用动规,把空间复杂度再压缩到O(1)
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();
int res = 0, tmp = 0;
for(int j = 0; j < s.length(); j++) {
int i = dic.containsKey(s.charAt(j)) ? dic.get(s.charAt(j)) : -1; // 获取索引 i
dic.put(s.charAt(j), j); // 更新哈希表
tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j]
res = Math.max(res, tmp); // max(dp[j - 1], dp[j])
}
return res;
}
}
核心分析:
核心代码
int lcs(string &A, string &B) {
int dp[A.size()+1][B.size()+1] = {0};
for(int i=1;i<=A.size();i++){
for(int j=1;j<=B.size();j++){
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
if(A[i-1] == B[j-1]){
dp[i][j] = max(dp[i][j],dp[i-1][j-1]+1);
}
}
}
return dp[A.size()][B.size()];
}
与LCS不同,最长公共子串需要连续,对于两个字符串X和Y,长度分别为m,n,初始化c为(m+1)*(n+1)的矩阵
(1)如果i=0或者j=0:即其中一个长度为0,则c=0
(2)最后一个字符相同,即xi=yj,则c[i][j]=c[i-1][j-1]+1
(3)如果xi!=yj,由于子串需要连续,因此,c[i][j]=0
代码:
class LongestSubstring: def findLongest(self, A, B): # write code here n,m=len(A),len(B) maxl=0 dp=[[0]*(m+1) for _ in range(n+1)] for i in range(1,n+1): for j in range(1,m+1): if A[i-1]==B[j-1]: dp[i][j]=dp[i-1][j-1]+1 # maxl=max(maxl,dp[i][j]) # 重构解 if maxl<dp[i][j]: maxl=dp[i][j] endi = i else: dp[i][j]=0 return maxl,A[endi-maxl:endi]
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
动态规划:
代码:
class Solution: def longestPalindrome(self, s: str) -> str: if not s: return '' maxl=1 start=0 l=len(s) dp=[[False]*l for _ in range(l)] # init dp,初始化长度为1、2的回文串 for i in range(l): dp[i][i]=True if i+1<l and s[i]==s[i+1]: dp[i][i+1]=True maxl=2 start=i #判断长度大于等于3的回文串 for ls in range(3,l+1): for i in range(l-ls+1): j=i+ls-1 dp[i][j] = dp[i+1][j-1] and s[i]==s[j] # print(s[i:j+1],dp[i][j]) if dp[i][j] and j-i+1>maxl: maxl=j-i+1 start=i return s[start:start+maxl]
还有马拉车算法,复杂度为O(n)
非马拉车O(n)算法:源自牛客网
class Solution:
def longestPalindrome(self, s: str) -> str:
if s == s[::-1]: return s
maxLen = 0
for i in range(len(s)):
if i - maxLen >= 1 and s[i - maxLen - 1:i + 1] == s[i - maxLen - 1:i + 1][::-1]:
maxLen += 2
end = i
continue
if i - maxLen >= 0 and s[i - maxLen:i + 1] == s[i - maxLen:i + 1][::-1]:
maxLen += 1
end=i
return s[end-maxLen+1:end+1]
给你一个整数数组 nums,请你找出并返回能被三整除的元素最大和。
示例 1:
输入:nums = [3,6,5,1,8]
输出:18
解释:选出数字 3, 6, 1 和 8,它们的和是 18(可被 3 整除的最大和)。
两种解法:
dp[i][j]=max(dp[i-1][j],dp[i-1][((j-nums[i-1])%3]+nums[i-1])
由于(j-nums[i])%3可能产生负数,因此可以需要做些处理:
把余数+3再模除3
dp[i][j]=max(dp[i-1][j],dp[i-1][((j-nums[i-1])%3+3)%3]+nums[i-1])
或者,根据正向思路直接写等式
dp[i][(j+nums[i])%3] = max(dp[i - 1][(j+nums[i-1])%3],dp[i -1][j] + nums[i - 1])
注意:初始化条件,在初始化时考虑迭代处理时max操作,需要将所有除dp[0][0]外的数据初始化为-inf
以下为代码:
class Solution:
def maxSumDivThree(self, nums) -> int:
l=len(nums)
dp=[[float("-inf")]*3 for _ in range(l+1)]
dp[0][0]=0
for i in range(1,l+1) :
for j in range(3):
# dp[i][j]=max(dp[i-1][j],dp[i-1][((j-nums[i-1])%3+3)%3]+nums[i-1])
dp[i][(j+nums[i-1])%3] = max(dp[i - 1][(j+nums[i-1])%3],dp[i -1][j] + nums[i - 1])
return dp[l][0]
优化空间复杂度
我们还可以发现,所有的 f[i][…] 只会从 f[i - 1][…] 转移得来,因此我们在动态规划时只存储这两行的结果,减少空间复杂度。
dp=[float("-inf")]*3
dp[0]=0
for num in nums:
dpnext=dp[:]
for i in range(3):
dpnext[(i+num)%3]=max(dp[(i+num)%3],dp[i]+num)
dp=dpnext
return dp[0]
有一个长度为 arrLen 的数组,开始有一个指针在索引 0 处。
每一步操作中,你可以将指针向左或向右移动 1 步,或者停在原地(指针不能被移动到数组范围外)。
给你两个整数 steps 和 arrLen ,请你计算并返回:在恰好执行 steps 次操作以后,指针仍然指向索引 0 处的方案数。
由于答案可能会很大,请返回方案数 模 10^9 + 7 后的结果。
示例 1:
输入:steps = 3, arrLen = 2
输出:4
解释:3 步后,总共有 4 种不同的方法可以停在索引 0 处。
向右,向左,不动
不动,向右,向左
向右,不动,向左
不动,不动,不动
示例 2:
输入:steps = 2, arrLen = 4
输出:2
解释:2 步后,总共有 2 种不同的方法可以停在索引 0 处。
向右,向左
不动,不动
提示:
1 <= steps <= 500
1 <= arrLen <= 10^6
思路:
dp[i][j]表示花费j步走到第i个位置的所有方案数,
dp[i][j]的来源有三种,左边位置往右一步、右边位置往左一步以及原地不动,对于两端只有两种来源
状态转移方程为:
dp[i][j] = (dp[i][j - 1] + dp[i + 1][j - 1]) % mod, i == 0;
dp[i][j] = (dp[i][j - 1] + dp[i + 1][j - 1]) % mod, i == arrLen - 1,即到数组末尾;
dp[i][j] = (dp[i][j - 1] + dp[i][j - 1] + dp[i + 1][j - 1]) % mod, 其它位置;
class Solution:
def numWays(self, steps: int, arrLen: int) -> int:
arrLen=min(arrLen,steps) #为了降低空间复杂度和减少内层循环数
dp = [[0] * arrLen for i in range(steps + 1)]
dp[0][0]=1
for i in range(1,steps+1):
for j in range(arrLen):
if j==0:
dp[i][j] = (dp[i-1][j ] + dp[i-1][j + 1])
elif j==arrLen-1:
dp[i][j]=dp[i-1][j]+dp[i-1][j-1]
else:
dp[i][j] = dp[i - 1][j - 1] +dp[i -1][j ] + dp[i-1][j +1]
return dp[steps][0]%int((1e9+7))
注意10^9=1e9, 但是python中%运算可以用于浮点数,运算结果可能与实际不符(这个问题把我坑了,调了好久bug)
写成:%int((1e9+7))或者 109+7**
这个是我刚开始采用的,但是发现时间降不下来,经过反复调试发现,其实在我的递归过程中第一次备忘记录是发生在最复杂的情况求解出来,这样其实根本就没有得到优化:
我的代码如下:
class Solution2: def numWays(self, steps: int, arrLen: int) -> int: st=[[False for i in range(steps+1)] for _ in range(arrLen)] def onestep(stepnum, curidx): if stepnum >= steps: st[curidx][stepnum]=1 if curidx == 0 else 0 return st[curidx][stepnum] if st[curidx][stepnum]: return st[curidx][stepnum] if curidx==0: st[curidx][stepnum]=onestep(stepnum + 1, (curidx + 1))+onestep(stepnum + 1, (curidx)) return st[curidx][stepnum] elif curidx==arrLen-1: st[curidx][stepnum]=onestep(stepnum + 1, (curidx)) + onestep(stepnum + 1, (curidx - 1)) return st[curidx][stepnum] else: st[curidx][stepnum] =onestep(stepnum + 1, (curidx + 1)) + onestep(stepnum + 1, (curidx)) + onestep(stepnum + 1,(curidx - 1))%(10e9+7) return st[curidx][stepnum] return onestep(0, 0)%int(1e9+7)
然后发现了别人的备忘录递归:
感觉这么复杂还不如直接递归dp。。。。。
class Solution: def __init__(self): self.result_dict = dict() def numWays(self, steps: int, arrLen: int) -> int: cnt = self.helper(steps, 0, arrLen, 0) return cnt % (10**9 + 7) def helper(self, step_sy, point_add, arrlen, count): if step_sy == 0 and point_add == 0: count += 1 self.result_dict.update({(step_sy, point_add): count}) return count if step_sy >= point_add and point_add < arrlen and point_add >= 0: if (step_sy-1, point_add) in self.result_dict: c1 = self.result_dict[(step_sy-1, point_add)] else: c1 = self.helper(step_sy-1, point_add, arrlen, count) if (step_sy-1, point_add+1) in self.result_dict: c2 = self.result_dict[(step_sy-1, point_add+1)] else: c2 = self.helper(step_sy-1, point_add+1, arrlen, count) if (step_sy-1, point_add-1) in self.result_dict: c3 = self.result_dict[(step_sy-1, point_add-1)] else: c3 = self.helper(step_sy-1, point_add-1, arrlen, count) self.result_dict.update({(step_sy, point_add): c1 + c2 + c3}) return c1 + c2 + c3 else: return count
这里有 d 个一样的骰子,每个骰子上都有 f 个面,分别标号为 1, 2, …, f。
我们约定:掷骰子的得到总点数为各骰子面朝上的数字的总和。
如果需要掷出的总点数为 target,请你计算出有多少种不同的组合情况(所有的组合情况总共有 f^d 种),模 10^9 + 7 后返回。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-dice-rolls-with-target-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例 5:
输入:d = 30, f = 30, target = 500
输出:222616187
题目分析:
最开始想的就是递归实现,自顶向下的写法最容易想到,已知d个骰子,目标数为target,那最后一个数就有f种可能,假设为k,减去最后一个数,剩下的target-k为d-1个骰子的和,依次自顶向下递归,
由于每次递归,骰子数目会减一,最后到终止条件d=1时,若剩下的target>f,那么这种情况不可能,返回0,若target<=f那个最后一个骰子值为target,为一种情况,返回1;
再就是加一些边界条件,不可能的情况。
class Solution: def numRollsToTarget(self, d: int, f: int, target: int) -> int: def fun(d,f,tar): if d==1 : if f>=tar: return 1 else: return 0 elif d>tar or d*f<tar: return 0 else: s=0 for i in range(1,f+1): #边界条件 if tar-i>0: s+=fun(d-1,f,tar-i) return s%(10**9+7) return fun(d,f,target)
但是很不幸,一看这个复杂度,每次递归会重新调用f次自身,o(d^f)复杂度,leetcode过了4/65case当然不行,该如何优化呢--------第一个方法:加备忘录
class Solution: def numRollsToTarget(self, d: int, f: int, target: int) -> int: #备忘录 lst=[[0]*1001 for _ in range(31)] def fun(d, f, tar): # 从备忘录中取 if lst[d][tar] != 0: return lst[d][tar] #终止条件 if d == 1: if f >= tar: lst[d][tar] = 1 return 1 else: lst[d][tar] = 0 return 0 elif d>tar or d*f<tar: lst[d][tar]=0 return 0 else: s = 0 for i in range(1, f + 1): if tar-i>0: s += fun(d - 1, f, tar - i) lst[d][tar] = s % (10 ** 9 + 7) return lst[d][tar] return fun(d, f, target)
这样就过了,再来优化,就是自底向上的迭代的方法,该方法比较麻烦的一点就是,需要判断d和target的迭代范围:
看起来非常简洁,但是刚开始不容易想到
class Solution: def numRollsToTarget(self, d: int, f: int, target: int) -> int: a=[[0]*1001 for _ in range(31)] # 初始化数组 for i in range(1,f+1): a[1][i]=1 #迭代求解 #骰子数:从2----d for i in range(2,d+1): #目标数:从最小为i个骰子都为1,到i个骰子都为f for j in range(i,i*f+1): #重复加f次 for k in range(1,f+1): if j-k>0: a[i][j]+=a[i-1][j-k] a[i][j]=a[i][j]%(10**9+7) return a[d][target]
格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。格雷编码序列必须以 0 开头。
示例 1:
输入: 2
输出: [0,1,3,2]
解释:
00 - 0
01 - 1
11 - 3
10 - 2
对于给定的 n,其格雷编码序列并不唯一。
例如,[0,2,3,1] 也是一个有效的格雷编码序列。
00 - 0
10 - 2
11 - 3
01 - 1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/gray-code
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
来自题解:https://leetcode-cn.com/problems/gray-code/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by–12/
(1)n=0 :解为0
(2)n = 1 的解:0 、1
(3)n = 2 的解
00 - 0
10 - 2
11 - 3
01 - 1
(4)n = 3 的解
000 - 0
010 - 2
011 - 3
001 - 1
------------
101 - 5
111 - 7
110 - 6
100 - 4
class Solution:
def grayCode(self, n: int) -> List[int]:
gray=[0]
for i in range(n):
add=1<<i
l=len(gray)
for j in range(l-1,-1,-1):
gray.append(gray[j]+add)
return gray
从空字符串开始,上面的(绿色)分别加0、1,下面的(红色)分别加1、0,直到长度达n
class Solution: def grayCode(self, n: int) -> List[int]: if n==0: return [0] gray=[] def back(now,op): if len(now)==n: gray.append(int(now,2)) elif op==0: back(now+'0',0) back(now+'1',1) else: back(now+'1',0) back(now+'0',1) back("",0) return gray
给定正整数数组 A,A[i] 表示第 i 个观光景点的评分,并且两个景点 i 和 j 之间的距离为 j - i。
一对景点(i < j)组成的观光组合的得分为(A[i] + A[j] + i - j):景点的评分之和减去它们两者之间的距离。
返回一对观光景点能取得的最高分。
示例:
输入:[8,1,5,2,6]
输出:11
解释:i = 0, j = 2, A[i] + A[j] + i - j = 8 + 5 + 0 - 2 = 11
思路:
维持A[i] + i最大的同时,维持A[i] + i +A[j] - j 最大,由于j>i,因此,初始化left=A[0],迭代从1开始,维持A[i] + i +A[j] - j最大在前
class Solution:
def maxScoreSightseeingPair(self, A: list) -> int:
left, res = A[0], -1
for j in range(1, len(A)):
res = max(res, left + A[j] - j)
left = max(left, A[j] + j)
return res
链接:https://www.nowcoder.com/questionTerminal/9d216205fbb44e538f725d9637239523
来源:牛客网
给定无序整数序列,求连续子串最大和,例如{-23 17 -7 11 -2 1 -34},子串为{17,-7,11},最大和为21
输入描述:
输入为整数序列,数字用空格分隔,如:-23 17 -7 11 -2 1 -34
输出描述:
输出为子序列的最大和:21
while 1:
try:
nums=list(map(int,input().split()))
maxs=float('-inf')
curs=0
for i in range(len(nums)):
if curs>=0:
curs+=nums[i]
else:
curs=nums[i]
maxs=max(maxs,curs)
print(maxs)
except:
break
拓展1:
增加条件,子串中连续的0不超过3个:(字节跳动2018暑期营算法笔试)
给出一个长度为 n 的数组a1、a2、…、an
输入描述:
第一行一个正整数 n, 表示数组长度,1 <= n <= 1000000。
第二行 n 个正整数,a1a2…an
输出描述:
一个整数
示例1:
输入
6
15 0 0 0 0 20
输出
20
拓展2:
给定一个整数数组,找出两个 不重叠 子数组使得它们的和最大。(lintcode42)
每个子数组的数字在数组中的位置应该是连续的。
返回最大的和。
给出数组 [1, 3, -1, 2, -1, 2]
这两个子数组分别为 [1, 3] 和 [2, -1, 2] 或者 [1, 3, -1, 2] 和 [2],它们的最大和都是 7
class Solution { public: /** * @param nums: A list of integers * @return: An integer denotes the sum of max two non-overlapping subarrays */ int maxTwoSubArrays(vector<int> nums) { // write your code here int result = nums[0],sum = 0; vector<int> left(nums.size(),0),right(nums.size(),0); for (int i = 0;i < nums.size();i++) { sum += nums[i]; result = max(result,sum); sum = max(sum,0); left[i] = result; } sum = 0,result = nums[nums.size()-1]; for (int i = nums.size()-1;i > 0;i--) { sum += nums[i]; result = max(result,sum); sum = max(sum,0); right[i] = result; } result = INT_MIN; for (int i = 0;i < nums.size()-1;i++) { result = max(result,left[i] + right[i+1]); } return result; } };
扩展3:
给定一个整数数组,找出两个不重叠的子数组A和B,使两个子数组和的差的绝对值|SUM(A) - SUM(B)|最大。
更好的总结:https://blog.csdn.net/hellozhxy/article/details/79911867
掌握:
快排:
归并排序:分治思想
堆排序:
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。
若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
解题思路:
解法一:
class Solution: def permute(self, nums): def backtrack(first = 0): # 递归终止条件 if first == n: output.append(nums[:]) for i in range(first, n): # place i-th integer first # 交换当前和第一个数字 nums[first], nums[i] = nums[i], nums[first] # use next integers to complete the permutations backtrack(first + 1) # backtrack,回溯,还原上一步 nums[first], nums[i] = nums[i], nums[first] n = len(nums) output = [] backtrack() return output #类似的解法 class Solution: def permute(self, nums: List[int]) -> List[List[int]]: l=len(nums) v=[True]*l ans=[] q=[] def dfs(x): if x>=l: ans.append(q.copy()) for i in range(l): if v[i]: q.append(nums[i]) v[i]=False dfs(x+1) # 当x+1>l, 即x=l时,ans才会重新添加 v[i]=True q.pop() dfs(0) return ans
解法二:使用库函数
def permute(self, nums: List[int]) -> List[List[int]]:
return list(itertools.permutations(nums))
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
#解法一:回溯 class Solution: def subsets(self, nums: List[int]) -> List[List[int]]: res = [] n = len(nums) def helper(i, tmp): res.append(tmp) for j in range(i, n): helper(j + 1,tmp + [nums[j]] ) helper(0, []) return res #解法2:库函数: class Solution: def subsets(self, nums: List[int]) -> List[List[int]]: res = [] for i in range(len(nums)+1): for tmp in itertools.combinations(nums, i): res.append(tmp) return res #解法3,迭代 class Solution: def subsets(self, nums: List[int]) -> List[List[int]]: res = [[]] for i in nums: res = res + [[i] + num for num in res] return res
给定一个字符串数组 arr,字符串 s 是将 arr 某一子序列字符串连接所得的字符串,如果 s 中的每一个字符都只出现过一次,那么它就是一个可行解。
请返回所有可行解 s 中最长长度。
输入:arr = [“cha”,“r”,“act”,“ers”]
输出:6
解释:可能的解答有 “chaers” 和 “acters”。
class Solution():
def maxLength(self, arr):
arr=list(filter(lambda x: len(set(x)) == len(x),arr))
def dfs(i, tmp):
if i >= len(arr):
return len(tmp)
else:
if not (set(tmp) & set(arr[i])):
return max(dfs(i+1,tmp+arr[i]),dfs(i+1,tmp))
else:
return dfs(i + 1, tmp)
l=dfs(0,'')
return l
栈这种数据结构,是计算机为实现子程序调用,实现的数据结构,天然的可以想到栈可以用于替代实现递归算法。
如:二叉树的DFS的实现
给定一个字符串描述的算术表达式,计算出结果值。
牛客连接https://www.nowcoder.com/profile/216430209/myFollowings/detail/7783335
输入字符串长度不超过100,合法的字符包括”+, -, *, /, (, )”,”0-9”,字符串内容的合法性及表达式语法的合法性由做题者检查。本题目只涉及整型计算
""" 中缀表达式转换为后缀表达式(去括号) """ # exp = "5*(9+(3-1)*3+10/2)" exp =input() # exp='(7+5*4*3+6)' def campare(op1,op2): return op1 in ["*", "/"] and op2 in ["+", "-"] houzhui=[] opt=[] i=0 while i<len(exp): # 若是数字就输出至后缀表达式 if exp[i].isdigit(): start=i # 一定是用i+1来判断后一个字符是否为数字,否则指针位置不对 # 判断长度的条件在前 while i+1<len(exp) and exp[i+1].isdigit() : i+=1 end=i+1 num=int(exp[start:end]) # print(num) houzhui.append(num) #左括号则入操作符栈 elif exp[i]=='(': opt.append('(') # 右括号,将操作符栈元素依次弹栈至后缀表达式,直到栈顶为“(”,并将“(”弹栈 elif exp[i]==')': while opt[-1]!='(': houzhui.append(opt.pop()) opt.pop() # 为运算符, elif exp[i] in ['+','-','*','/']: # opt有三种情况,都将运算符入opt栈 # 栈空、栈顶为(,符号优先级比栈顶符号高 if opt==[] or opt[-1]=='(' or campare(exp[i],opt[-1]): opt.append(exp[i]) else: # 否则,将opt弹栈输出,直到opt为空,左括号出现,或者操作符栈顶元素优先级更高, # 再将该运算符入opt栈 while not(opt==[] or opt[-1]=='(' or campare(exp[i],opt[-1])): houzhui.append(opt.pop()) opt.append(exp[i]) # print(houzhui) # print(opt) i+=1 #指针加一 # 最后将opt剩余运算符依次弹出输出 for i in range(len(opt)): houzhui.append(opt.pop()) # print(houzhui) # print(opt) # print('-----------------') """ 后缀表达式运算 """ def getvalue(num1,num2,op): if op == "+": return num1 + num2 elif op == "-": return num1 - num2 elif op == "*": return num1 * num2 else: # / return num1 / num2 # print(houzhui) value=[] i=0 while len(houzhui): if houzhui[i] in ['+','-','*','/']: op=houzhui.pop(0) num2=value.pop() num1=value.pop() value.append(getvalue(num1, num2, op)) else: value.append(houzhui.pop(0)) # print(houzhui) # print(value) # print(houzhui) print(value.pop())
也可以简化程序,把中缀转后缀表达式 与 表达式计算一起做
https://leetcode-cn.com/contest/weekly-contest-142/problems/brace-expansion-ii/
如果你熟悉 Shell 编程,那么一定了解过花括号展开,它可以用来生成任意字符串。
花括号展开的表达式可以看作一个由 花括号、逗号 和 小写英文字母 组成的字符串,定义下面几条语法规则:
如果只给出单一的元素 x,那么表达式表示的字符串就只有 “x”。
例如,表达式 {a} 表示字符串 “a”。
而表达式 {ab} 就表示字符串 “ab”。
当两个或多个表达式并列,以逗号分隔时,我们取这些表达式中元素的并集。
例如,表达式 {a,b,c} 表示字符串 “a”,“b”,“c”。
而表达式 {a,b},{b,c} 也可以表示字符串 “a”,“b”,“c”。
要是两个或多个表达式相接,中间没有隔开时,我们从这些表达式中各取一个元素依次连接形成字符串。
例如,表达式 {a,b}{c,d} 表示字符串 “ac”,“ad”,“bc”,“bd”。
表达式之间允许嵌套,单一元素与表达式的连接也是允许的。
例如,表达式 a{b,c,d} 表示字符串 “ab”,“ac”,"ad"。
例如,表达式 {a{b,c}}{{d,e}f{g,h}} 可以代换为 {ab,ac}{dfg,dfh,efg,efh},表示字符串 “abdfg”, “abdfh”, “abefg”, “abefh”, “acdfg”, “acdfh”, “acefg”, “acefh”。
class Solution2: def braceExpansionII(self, expression: str): expression = "{" + expression + "}" stack = [] i = 0 while i < len(expression): c = expression[i] if c == '{': stack.append(c) i += 1 elif c == ',': stack.append(c) i += 1 elif c == '}': ans = stack.pop() while stack[-1] != '{': if stack[-1] == ',': stack.pop() #采用集合存储字母,集合并集做运算 ans = ans | stack.pop() stack.pop() while stack and stack[-1] != ',' and stack[-1] != '{': #串连字符的判定条件:在做完一个{}运算后,栈顶有集合元素,如ans={‘s’,‘e’},栈顶={‘s’} temp = stack.pop() res = set() for pre in temp: for suf in ans: res.add(pre+suf) ans = res stack.append(ans) i += 1 else: start = i while i < len(expression) and expression[i] not in {'{', '}', ','}: i += 1 string = expression[start:i] ans = {string} while stack and stack[-1] != ',' and stack[-1] != '{': #串连字符的判定条件:{}直接连接字符,如{s,c}a temp = stack.pop() res = set() for pre in temp: for suf in ans: res.add(pre+suf) ans = res stack.append(ans) return sorted(list(stack[0]))
给你一个由 ‘(’、’)’ 和小写字母组成的字符串 s。
你需要从字符串中删除最少数目的 ‘(’ 或者 ‘)’ (可以删除任意位置的括号),使得剩下的「括号字符串」有效。
请返回任意一个合法字符串。
有效「括号字符串」应当符合以下 任意一条 要求:
空字符串或只包含小写字母的字符串
可以被写作 AB(A 连接 B)的字符串,其中 A 和 B 都是有效「括号字符串」
可以被写作 (A) 的字符串,其中 A 是一个有效的「括号字符串」
示例 1:
输入:s = “lee(t©o)de)”
输出:“lee(t©o)de”
解释:“lee(t(co)de)” , “lee(t©ode)” 也是一个可行答案。
示例 2:
输入:s = “a)b©d”
输出:“ab©d”
示例 3:
输入:s = “))((”
输出:""
解释:空字符串也是有效的
示例 4:
输入:s = “(a(b©d)”
输出:“a(b©d)”
class Solution: def minRemoveToMakeValid(self, s: str) -> str: l=len(s) stack=[] flag=[False for _ in range(l)] for i in range(l): if s[i].isalpha(): flag[i]=True elif s[i]=="(": stack.append(('(',i)) continue elif s[i]==")" and stack and stack[-1][0]=='(': _,idx=stack.pop() flag[i]=True flag[idx]=True ans='' for i in range(l): if flag[i]: ans+=s[i] return ans
解题步骤:
(1)查看表达式的类型:
前缀和后缀都能很方便的进行判断和求解(利用()和,等分隔符,可以很有效的提取出运算的数据和参与的运算类型);
中缀稍微麻烦一些,需要做一些判定
对于以上两类表达式,应将运算符和非运算符分开存放,方便判断
其余类别不清晰的表达式如花括号展开,其实是集合的运算,运算符有些还可以省略,则需要根据情况处理
(2)查看数据类型:确定数据存储形式
对于数字类,参与数值运算,可以直接存
对于字符类,参与布尔运算,判定后存储为布尔变量
对于字符型,参与集合运算,可以以集合set()形式存储
(3)什么时候开始运算?
一般是反括号,},],)等;
注意:
需不需要外层括号
一元和多元运算
弹栈和入栈(无用数据出栈,计算结果入栈)
返回字符串 text 中按字典序排列最小的子序列,该子序列包含 text 中所有不同字符一次。
示例 1:
输入:“cdadabcc”
输出:“adbc”
示例 2:
输入:“abcd”
输出:“abcd”
示例 3:
输入:“ecbacba”
输出:“eacb”
示例 4:
输入:“leetcode”
输出:“letcod”
https://leetcode-cn.com/problems/smallest-subsequence-of-distinct-characters/solution/tan-xin-suan-fa-zhan-wei-yan-ma-python-dai-ma-java/
采用栈的结构,实现对历史最小字符的记录,
class Solution:
def smallestSubsequence(self, text: str) -> str:
size = len(text)
stack = []
for i in range(size):
if text[i] in stack:
#已经在stack中,不作处理,继续循环
continue
while stack and ord(text[i]) < ord(stack[-1]) and \
text.find(stack[-1], i) != -1:#这一句用于判断索引i以后还有该字符,该字符才可被弹出
stack.pop()
stack.append(text[i])
return ''.join(stack)
堆:又叫二叉堆,利用完全二叉树的结构来维护一组数据
按照父亲节点和孩子节点的特性,又分为最大堆和最小堆
堆排序,直接调用
https://www.nowcoder.com/profile/216430209/test/24071106/141059#summary
产品经理(PM)有很多好的idea,而这些idea需要程序员实现。现在有N个PM,在某个时间会想出一个 idea,每个 idea 有提出时间、所需时间和优先等级。对于一个PM来说,最想实现的idea首先考虑优先等级高的,相同的情况下优先所需时间最小的,还相同的情况下选择最早想出的,没有 PM 会在同一时刻提出两个 idea。
同时有M个程序员,每个程序员空闲的时候就会查看每个PM尚未执行并且最想完成的一个idea,然后从中挑选出所需时间最小的一个idea独立实现,如果所需时间相同则选择PM序号最小的。直到完成了idea才会重复上述操作。如果有多个同时处于空闲状态的程序员,那么他们会依次进行查看idea的操作。
求每个idea实现的时间。
输入第一行三个数N、M、P,分别表示有N个PM,M个程序员,P个idea。随后有P行,每行有4个数字,分别是PM序号、提出时间、优先等级和所需时间。输出P行,分别表示每个idea实现的时间点。
class Solution: def pm_ideas(self): N, M, P = input().strip().split() N, M, P = int(N), int(M), int(P) ideas = [] for i in range(P): pm_id, proposal_t, priority, duration = input().strip().split() pm_id, proposal_t, priority, duration = int(pm_id), int(proposal_t), int(priority), int(duration) ideas.append((proposal_t, pm_id, priority, duration, i)) ideas.sort(reverse=True) #ideas按照提出时间降序排列 pm = defaultdict(list) # 生成默认值为空列表的字典,键为pm——id,值为idea参数元祖构成的列表 res = [0] * P # 返回值 coders = [0] * M # 程序员时间空闲时刻 heapq.heapify(coders) clock = 0 # 当前时间 available = 0 # 候选队列中的idea数目 while ideas or available > 0: coder_clock = heapq.heappop(coders) clock = max(clock, coder_clock) if available == 0: clock = max(clock, ideas[-1][0]) # 当还有没处理的idea,最先提出的idea的提出时间小于等于当前clock,则把该idea加入pm对应地id的列表中 while ideas and ideas[-1][0] <= clock: proposal_t, pm_id, priority, duration, idea_id = ideas.pop() heapq.heappush(pm[pm_id], (-priority, duration, pm_id, proposal_t, idea_id)) # -priority排序,越小优先级越高 available += 1 candidates = [] # 候选idea初始化 for pm_id, tasks in pm.items(): if tasks: _, duration, pm_id, proposal_t, idea_id = tasks[0] heapq.heappush(candidates, (duration, pm_id, proposal_t, idea_id)) # duration排序,越小,越优先处理 if not candidates: break duration, pm_id, proposal_t, idea_id = heapq.heappop(candidates) heapq.heappop(pm[pm_id]) available -= 1 # 每次仅处理一个idea res[idea_id] = clock + duration heapq.heappush(coders, clock + duration) for i in range(P): print(res[i])
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。
构建双指针第一次相遇:
构建双指针第二次相遇:
作者:jyd
链接:https://leetcode-cn.com/problems/linked-list-cycle-ii/solution/linked-list-cycle-ii-kuai-man-zhi-zhen-shuang-zhi-/
第一轮相遇后,重置快指针在head处,以慢指针相同速度前进a步,此时可在入口处相遇
class Solution(object):
def detectCycle(self, head):
fast, slow = head, head
while True:
if not (fast and fast.next):
return
fast, slow = fast.next.next, slow.next
if fast == slow:
break
fast = head
while fast != slow:
fast, slow = fast.next, slow.next
return fast
程序要点:移动终止条件??(r>l,r<len)移动条件???左右指针如何移动?
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
思路:盛水的容量等于左右指针距离*最小高度;首先我们初始化左右指针距离最大,在缩小左右指针距离的时候,想要找到比之前盛水更多的方案,只有提高最小高度,那么这种情况下就要选择高度最小的指针内移。
o(n)时间负责度,比暴力法o(n2)的时间复杂度快
class Solution:
def maxArea(self, height: List[int]) -> int:
le,ri=0,len(height)-1
val=(ri-le)*min(height[ri],height[le])
while le<ri:
if height[le]<height[ri]:
le+=1
else:
ri-=1
val=max(val,(ri-le)*min(height[ri],height[le]))
return val
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
暴力解法时间复杂度为on3,使用**排序+左右指针+**判定条件优化到on2
class Solution: def threeSum(self, nums: list) -> list: nums.sort() ans = [] if not nums or len(nums)<3: return ans for i in range(len(nums)): if nums[i] > 0: return ans if i>0 and nums[i]==nums[i-1]: continue le = i + 1 ri = len(nums) - 1 while le < ri: sums = nums[i] + nums[le] + nums[ri] if sums == 0: ans.append([nums[i], nums[le], nums[ri]]) while ri > le and nums[ri] == nums[ri - 1]: ri -= 1 while ri > le and nums[le] == nums[le + 1]: le += 1 le+=1 ri-=1 elif sums > 0: ri -= 1 else: le += 1 return ans
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
class Solution: def lengthOfLongestSubstring(self, s: str) -> int: if not s: return 0 sl=len(set(s)) if sl==1 or sl==len(s): return sl l,r=0,1 ans=1 while r<len(s): if s[r] in s[l:r]: ans=max(r-l,ans) while r>l and s[r] in s[l:r]: l+=1 elif len(s)-1==r: ans=max(r-l+1,ans) r+=1 return ans
(1)判断奇偶:取x的最后一个二进制位:x & 1
拓展:取x的倒数第k位:x&(1<<k)
(2)判断有多少2进制1:消去末尾的1:x=x&(x-1)
(3)与0xffff相与,为自身
(1)与0相或,为自身
性质: 两个相同的数异或为0,与0异或为自身
有一个正整数数组 arr,现给你一个对应的查询数组 queries,其中 queries[i] = [Li, Ri]。
对于每个查询 i,请你计算从 Li 到 Ri 的 XOR 值(即 arr[Li] xor arr[Li+1] xor … xor arr[Ri])作为本次查询的结果。
并返回一个包含给定查询 queries 所有结果的数组。
示例 1:
输入:arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]]
输出:[2,7,14,8]
解释:
数组中元素的二进制表示形式是:
1 = 0001
3 = 0011
4 = 0100
8 = 1000
查询的 XOR 值为:
[0,1] = 1 xor 3 = 2
[1,2] = 3 xor 4 = 7
[0,3] = 1 xor 3 xor 4 xor 8 = 14
[3,3] = 8
解析:
刚开始只想到异或的交换律(a ^ b) ^ c=a ^ (b ^ c);写了个递归,报内存不足,可能是递归深度太大;后年看解题,才知道利用递归的性质可以构造递归前缀和,来求部分异或 c ^ d= (a ^ b) ^ (a ^ b ^ c ^ d)
原始代码
class Solution: def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]: def getxor(st,end): if dp[st][end]!=-1: return dp[st][end] else: dp[st][end-1]=getxor(st,end-1) return dp[st][end-1]^arr[end] n=len(arr) dp=[[-1]*n for _ in range(n)] for i in range(n): dp[i][i]=arr[i] ans=[] for st,end in queries: ans.append(getxor(st,end)) return ans
修改后
class Solution:
def xorQueries(self, arr: List[int], queries: List[List[int]]) -> List[int]:
xorlst=[0]
for a in arr:
xorlst.append(xorlst[-1]^a)
# print(xorlst)
ans=[]
for st,end in queries:
ans.append(xorlst[st]^xorlst[end+1])
return ans
给你一个整数数组 arr 和一个整数 difference,请你找出 arr 中所有相邻元素之间的差等于给定 difference 的等差子序列,并返回其中最长的等差子序列的长度。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-arithmetic-subsequence-of-given-difference
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
输入:arr = [1,5,7,8,5,3,4,2,1], difference = -2
输出:4
解释:最长的等差子序列是 [7,5,3,1]。
思路:
依次遍历每个数组元素,使用map存储到当前元素为止的最长等差数列长度;如果如果上一个元素(下一个元素—差)存在,则长度在其基础上加一;不存在则将键值置为1。输出map最大值
class Solution:
def longestSubsequence(self, arr: List[int], difference: int) -> int:
for a in arr:
d[a] = d.get(a - difference, 0) + 1
return max(d.values())
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
https://leetcode-cn.com/problems/lru-cache/solution/lru-ce-lue-xiang-jie-he-shi-xian-by-labuladong/
代码参考
https://leetcode-cn.com/problems/lru-cache/solution/shu-ju-jie-gou-fen-xi-python-ha-xi-shuang-xiang-li/
class ListNode: def __init__(self, key=None, value=None): self.key = key self.value = value self.prev = None self.next = None class LRUCache: def __init__(self, capacity): self.capacity = capacity self.hashmap = {} # 新建两个节点 head 和 tail self.head = ListNode() self.tail = ListNode() # 初始化链表为 head <-> tail self.head.next = self.tail self.tail.prev = self.head # 因为get与put操作都可能需要将双向链表中的某个节点移到末尾,所以定义一个方法 def move_node_to_tail(self, key): # 先将哈希表key指向的节点拎出来,为了简洁起名node # hashmap[key] hashmap[key] # | | # V --> V # prev <-> node <-> next pre <-> next ... node node = self.hashmap[key] node.prev.next = node.next node.next.prev = node.prev # 之后将node插入到尾节点前 # hashmap[key] hashmap[key] # | | # V --> V # prev <-> tail ... node prev <-> node <-> tail node.prev = self.tail.prev node.next = self.tail self.tail.prev.next = node self.tail.prev = node def get(self, key): if key in self.hashmap: # 如果已经在链表中了久把它移到末尾(变成最新访问的) self.move_node_to_tail(key) res = self.hashmap.get(key, -1) if res == -1: return res else: return res.value def put(self, key, value): if key in self.hashmap: # 如果key本身已经在哈希表中了就不需要在链表中加入新的节点 # 但是需要更新字典该值对应节点的value self.hashmap[key].value = value # 之后将该节点移到末尾 self.move_node_to_tail(key) else: if len(self.hashmap) == self.capacity: # 去掉哈希表对应项 self.hashmap.pop(self.head.next.key) # 去掉最久没有被访问过的节点,即头节点之后的节点 self.head.next = self.head.next.next self.head.next.prev = self.head # 如果不在的话就插入到尾节点前 new = ListNode(key, value) self.hashmap[key] = new new.prev = self.tail.prev new.next = self.tail self.tail.prev.next = new self.tail.prev = new
给你一个整数数组 nums 和一个整数 k。
如果某个子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。
请返回这个数组中「优美子数组」的数目。
思路1:前缀和+hashmap
作者:aleix
链接:https://leetcode-cn.com/problems/count-number-of-nice-subarrays/solution/qian-zhui-he-hash-jian-dan-yi-li-jie-by-aleix/
from collections import defaultdict
class Solution:
def numberOfSubarrays(self, nums: list, k: int) -> int:
qz_sum=[0]
for i in range(len(nums)):
qz_sum.append(qz_sum[-1]+(nums[i]&1))
hashmap=defaultdict(int)
ret=0
for i in range(len(qz_sum)):
ret+=hashmap[qz_sum[i]-k]
hashmap[qz_sum[i]]+=1
return ret
思路2:直接计算
class Solution: def numberOfSubarrays(self, nums: List[int], k: int) -> int: jishu=[0] # nums=[1]+nums[:]+[1] for i in range(len(nums)): if nums[i]&1: jishu.append(i+1) jishu.append(i+2) lt,rt=1,1 idx=0 l=len(jishu) cnt=[] while idx+k<=l: if idx!=0: lt=jishu[idx]-jishu[idx-1] else: lt=1 if idx+k!=l: rt=jishu[idx+k]-jishu[idx+k-1] else: rt=1 cnt.append(rt*lt) idx+=1 return sum(cnt[1:-1])
函数设计:
题目只要求实现 加入队尾appendTail() 和 删除队首deleteHead() 两个函数的正常工作,因此我们可以设计栈 A 用于加入队尾操作,栈 B 用于将元素倒序,从而实现删除队首元素。
加入队尾 appendTail()函数: 将数字 val 加入栈 A 即可。
删除队首deleteHead()函数: 有以下三种情况。
class CQueue: def __init__(self): self.stack1 = [] self.stack2 = [] def appendTail(self, value: int) -> None: self.stack1.append(value) def deleteHead(self) -> int: if not self.stack1 and not self.stack2: return -1 if self.stack2: return self.stack2.pop() while self.stack1: self.stack2.append(self.stack1.pop()) return self.stack2.pop()
设计一个最大栈,支持 push、pop、top、peekMax 和 popMax 操作。
push(x) – 将元素 x 压入栈中。
pop() – 移除栈顶元素并返回这个值。
top() – 返回栈顶元素。
peekMax() – 返回栈中最大元素。
popMax() – 返回栈中最大的元素,并将其删除。如果有多个最大元素,只要删除最靠近栈顶的那个。
思路
class MaxStack { Stack<Integer> stack; Stack<Integer> maxStack; public MaxStack() { stack = new Stack(); maxStack = new Stack(); } public void push(int x) { int max = maxStack.isEmpty() ? x : maxStack.peek(); maxStack.push(max > x ? max : x); stack.push(x); } public int pop() { maxStack.pop(); return stack.pop(); } public int top() { return stack.peek(); } public int peekMax() { return maxStack.peek(); } public int popMax() { int max = peekMax(); Stack<Integer> buffer = new Stack(); while (top() != max) buffer.push(pop()); pop(); while (!buffer.isEmpty()) push(buffer.pop()); return max; } }
给你一个链表的头节点 head,请你编写代码,反复删去链表中由 总和 值为 0 的连续节点组成的序列,直到不存在这样的序列为止。
删除完毕后,请你返回最终结果链表的头节点。
输入:head = [1,2,-3,3,1]
输出:[3,1]
提示:答案 [1,2,1] 也是正确的。
思路:
# Definition for singly-linked list. # class ListNode(object): # def __init__(self, x): # self.val = x # self.next = None def to_list(h): ans = [] while h: ans.append(h.val) h = h.next return ans def to_link(l): n = ListNode(None) h = n for node in l: n.next = ListNode(node) n = n.next return h.next class Solution(object): def removeZeroSumSublists(self, head): """ :type head: ListNode :rtype: ListNode """ l = to_list(head) ans = list() for n in l: ans.append(n) s = 0 for k in reversed(xrange(len(ans))): s += ans[k] if s == 0: ans = ans[:k] break return to_link(ans)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。