每日一题
- 1765.地图中的最高点1.29
- 884.两句话中的不常见单词(简)1.30
- 1342.将数字变成0的操作次数(简)1.31
- 1763.最长的美好子字符串2.1
- 2000.反转单词前缀(简)2.2
- 1414.和为k的最少斐波那契数字数目2.3
- 1725.可以形成最大的矩形数目(简)2.4
- 1219.黄金矿工2.5
- 1748.唯一元素的和(简)2.6
- 1405.最长快乐字符串2.7
- 1001.网格照明(难)2.8
- 2006.差的绝对值为K的数对数目(简)2.9
- 1447.最简分数2.10
- 1984.学生分数的最小差值(简)2.11
- 1020.飞地的数量2.12
- 1189."气球"的最大数量(简)2.13
- 540.有序数组中的单一元素2.14
- 1380.矩阵中的幸运数字(简)2.15
- 1719.重构一棵树的方案数(超难)2.16
- 688.骑士在棋盘上的概率2.17
- 1791.找出星型图的中心节点(简)2.18
1765.地图中的最高点1.29
算法思路:多源广度优先搜索
- 首先,计算与水域相邻的格子的高度。对于这些格子来说,其相邻格子中的最小高度即为水域的高度0,因此这些格子的高度为1。
- 然后,计算与高度为1的格子相邻的、尚未被计算过的格子的高度。对于这些格子来说,其相邻格子中的最小高度为1,因此这些格子的高度为2。
- 以此类推,计算出所有格子的高度。
对于每一轮,我们考虑的是与上一轮格子相邻的,未被计算过的格子x,其高度必然比上一轮的格子高度多1。
可以发现,上述过程就是从水域出发,指向广度优先搜索的过程。因此,记录下(多个)所有水域的位置,然后执行广度优先搜索,计算出所有陆地格子的高度,即为答案。这就是多源BFS。可用原矩阵进行访问过。
class Solution(object):
def highestPeak(self, isWater):
"""
:type isWater: List[List[int]]
:rtype: List[List[int]]
"""
queue, m, n, cost = [], len(isWater), len(isWater[0]), 0
for i, row in enumerate(isWater):
for j, val in enumerate(row):
# 水域,作为起点入队,并更新为答案需要返回的0
if val:
isWater[i][j] = 0
queue.append((i, j))
# 陆地:先更新为无限大的高度,等BFS时更新它
else:
isWater[i][j] = float("inf")
while queue:
nxt = []
cost += 1
for i, j in queue:
for dx, dy in (0, 1), (1, 0), (-1, 0), (0, -1):
# 只有没被更新过的陆地才能被更新,否则已经有更近的水域访问过它了
nx=i+dx
ny=j+dy
if 0 <= nx < m and 0 <= ny < n and isWater[nx][ny] > cost:
#在python3中可以直接用海象运算符写为0<=(nx:=i+dx)<m and 0<=(ny:=j+dy)<n and isWater[nx][ny]>cost
isWater[nx][ny] = cost
nxt.append((nx, ny))
queue = nxt
return isWater
884.两句话中的不常见单词(简)1.30
算法思想:哈希表
题目要求
- 在句子s1中恰好出现一次,但在句子s2中没有出现的单词
- 或在句子s2中恰好出现一次,但在句子s1中没有出现的单词
其实等价于找出:
在两个句子中一共只出现一次的单词。
因此我们可以使用一个哈希映射统计两个句子中单词出现的次数。哈希映射中的每个键值对,键表示一个单词,值表示该单词出现的次数。在统计完成后,我们再对哈希映射进行一次遍历,把所有值为1的键放入答案中即可。
class Solution(object):
def uncommonFromSentences(self, s1, s2):
"""
:type s1: str
:type s2: str
:rtype: List[str]
"""
freq=Counter(s1.split( ))+Counter(s2.split( ))#用法非常妙!
ans=list()
for word,count in freq.items():
if count==1:
ans.append(word)
return ans
1342.将数字变成0的操作次数(简)1.31
class Solution(object):
def numberOfSteps(self, num):
"""
:type num: int
:rtype: int
"""
count=0
while num!=0:
if num%2==0:#偶数
num=num//2
else:#奇数
num-=1
count+=1
return count
1763.最长的美好子字符串2.1
算法思想:
用比较暴力的方法:枚举遍历子串temp,统计该子串的字典键值对,添加到集合中,如果这是小/大写字母,判断字典中有没有大/小写,没有就false, 找到了就true,返回temp。
from collections import Counter
class Solution(object):
def longestNiceSubstring(self, s):
"""
:type s: str
:rtype: str
"""
for i in range(len(s)-1,0,-1):#i字符串长度,从大到小
for j in range(len(s)-i):#长度为i时,开始的位置有0到len()-i-1
temp=s[j:j+i+1]#截取这个子串
temp_dict=Counter(temp)#字典统计出现这个子串字符次数
temp_set=set()
flage=True
for key in temp_dict:
if key not in temp_set:
temp_set.add(key.lower())
if key.islower():
key_=key.upper()
else:
key_=key.lower()
if key_ not in temp_dict:
flage=False
break
if flage:
return temp
return ""
2000.反转单词前缀(简)2.2
算法思路:首先查找ch在字符串word的位置,如果找到,则将字符串从下标0开始,到查找到的ch所在位置为止的那段字符串进行反转,否则直接返回原字符串。
注:找字符ch在字符串word里出现的下标位置,这题我们应该用find()
- index(ch)返回字符最后出现的位置
- find(ch)返回字符第一次出现的位置
class Solution(object):
def reversePrefix(self, word, ch):
"""
:type word: str
:type ch: str
:rtype: str
"""
i=word.find(ch)+1
return word[:i][::-1]+word[i:]
1414.和为k的最少斐波那契数字数目2.3
算法思路:递归
我们最终选取的数列中的数必然是没有相邻的,因为相邻的话我们可以直接取下一个斐波那契数(他们的和),毕竟取一个比取两个肯定取了更少的数。最接近k的斐波那契数必须被选。取完后后面依然是一个递归求解。
大白话:1,1,2,3,5,8,13这个数列中,你会发现13比8+3+1大1。8等于5+2+1。
class Solution(object):
def findMinFibonacciNumbers(self, k):
"""
:type k: int
:rtype: int
"""
if k==0:
return 0
f,f1=1,1
while f1<=k:
f,f1=f1,f+f1
return 1+self.findMinFibonacciNumbers(k-f)
1725.可以形成最大的矩形数目(简)2.4
算法思路:一次遍历
记l和w为某个矩形的长度和宽度,设k为可以从这个矩形中切出的最大正方形的边长,则有k=min(l,w)。我们遍历输入数组,维护两个变量,maxLen表示遍历到当前矩形时的所有可以切出的最大正方形的边长的最大值,res表示可以切出边长为maxLen的正方形的个数。计算当前的k,当k=maxLen时,对res进行加1操作;当k>maxLen时,则更新maxLen为k,并把res重置为1。
- 时间复杂度:O(n),其中n 为输入数组rectangles 的长度。我们仅需一次遍历即可得到答案。
- 空间复杂度:O(1)。我们仅需要常数空间来存储变量。
class Solution(object):
def countGoodRectangles(self, rectangles):
"""
:type rectangles: List[List[int]]
:rtype: int
"""
res,maxLen=0,0
for l,w in rectangles:
k=min(l,w)
if k==maxLen:
res+=1
elif k>maxLen:
res=1
maxLen=k
return res
1219.黄金矿工2.5
算法思路:回溯算法
首先在mXn个网格内枚举起点。只要格子内的数大于0,它就可以作为起点进行开采。记枚举的起点为(i,j),我们就可以从(i,j)开始进行递归+回溯,枚举所有可行的开采路径。我们用递归函数dfs(x,y,gold)进行枚举,其中(x,y)表示所在的位置,gold表示开采位置(x,y)之前,已拥有的黄金数量。根据题目的要求,我们需要进行如下步骤:
- 我们需要将gold更新为gold+grid[x][y],表示对位置(x,y)进行开采。由于我们的目标是最大化收益,因此我们还要维护一个最大的收益值ans,并在这一步使用gold更新ans;
- 我们需要枚举矿工下一步的方向。由于矿工每次可以从当前位置向上下左右四个方向走,因此我们需要依次枚举每一个方向。如果往某一方向不会走出网格,并且走到的位置的值不为0,我们就可以进行递归搜索;
- 在搜索完所有方向后,我们进行回溯。
需要注意的是,题目规定了“每个单元格只能被开采一次”,因此当前我们到达位置(x,y)时,我们可以将grid[x][y]暂时置为0;在进行回溯之前,再将grid[x][y]的值恢复。
class Solution(object):
def getMaximumGold(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
m=len(grid)
n=len(grid[0])
global ans
ans=0
def dfs(x,y,gold):
gold=gold+grid[x][y]
global ans
ans=max(ans,gold)
res=grid[x][y]
grid[x][y]=0#遍历过,标记
for nx,ny in ((x-1,y),(x+1,y),(x,y-1),(x,y+1)):
if 0<=nx<m and 0<=ny<n and grid[nx][ny]>0:#符合条件遍历
dfs(nx,ny,gold)
grid[x][y]=res#遍历完恢复原来值
for i in range(m):
for j in range(n):
if grid[i][j]!=0:#依次遍历
dfs(i,j,0)
return ans
1748.唯一元素的和(简)2.6
算法思路:用哈希表,字典记录元素出现次数,次数为1即为唯一元素,累加。
- defaultdict的作用是在于,当字典里的key不存在但被查找时,返回的不是keyError而是一个默认值。
- 作用是当key不存在时,返回的是工厂函数的默认值,比如list对应[ ],str对应的是空字符串,set对应set( ),int对应0,如下举例:
from collections import defaultdict
dict1 = defaultdict(int)
dict2 = defaultdict(set)
dict3 = defaultdict(str)
dict4 = defaultdict(list)
#输出默认值
print(dict1[1])# 0
print(dict2[1])# set()
print(dict3[1])#
print(dict4[1])# []
class Solution(object):
def sumOfUnique(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
dic=defaultdict(int)
res=0
for i in nums:
dic[i]+=1
for m,n in dic.items():
if n==1:
res+=m
return res
1405.最长快乐字符串2.7
算法思路:贪心策略
- 尽可能优先使用当前数量最多的字母,因为最后同一种字母剩余的越多,越容易出现字母连续相同的情况。如果构建完成最长的快乐字符串后还存在剩余未选择的字母,则剩余的字母一定为同一种字母且该字母的总数量最多。
- 依次从当前数量最多的字母开始尝试,如果发现加入当前字母会导致出现三个连续相同字母,则跳过当前字母,直到我们找到可以添加的字母为止。实际上每次只会在数量最多和次多的字母中选择一个。
- 如果尝试所有的字母都无法添加,则直接退出,此时构成的字符串即为最长的快乐字符串。
class Solution(object):
def longestDiverseString(self, a, b, c):
"""
:type a: int
:type b: int
:type c: int
:rtype: str
"""
ans=[]
cnt=[[a,'a'],[b,'b'],[c,'c']]
while True:
cnt.sort(key=lambda x:-x[0])#从大到小排序,先排数量最多的
isNext=False
for i,(c,ch) in enumerate(cnt):
if c<=0:#没有该字母
break
if len(ans)>=2 and ans[-2]==ch and ans[-1]==ch:#会成为三个连续字母,跳过
continue
isNext=True
ans.append(ch)
cnt[i][0]-=1
break
if not isNext:#所有都遍历过了,才输出
return "".join(ans)
1001.网格照明(难)2.8
解题思路:哈希表,数组
维护四个计数和点的集合。
四个计数分别为:行计数、列计数、左对角线计数、右对角线计数。这样我们只需要知道查询点在任意计数上是否大于0,就知道它是不是被照亮了。再根据点的集合删除点,删除点的时候同样更新各个计数情况即可。
class Solution(object):
def gridIllumination(self, n, lamps, queries):
"""
:type n: int
:type lamps: List[List[int]]
:type queries: List[List[int]]
:rtype: List[int]
"""
row_cnts,col_cnts,lr_cnts,rl_cnts,points=defaultdict(int),defaultdict(int),defaultdict(int),defaultdict(int),set()
#行计数,列计数,左对角计数,右对角计数(用字典,键值对次数),亮灯计数(用集合)
for r,c in lamps:#遍历开灯的地方
if (r,c) not in points:#统计各方向开灯数,不是灯亮数!
points.add((r,c))#(r,c)=(x,y)
row_cnts[r]+=1
col_cnts[c]+=1
#r*(-1)+b=c
lr_cnts[r+c]+=1
#r+b=c
rl_cnts[r-c]+=1
ans=[0]*len(queries)
for i in range(len(queries)):
r,c=queries[i]#灭灯坐标
if row_cnts[r] or col_cnts[c] or lr_cnts[r+c] or rl_cnts[r-c]:#只有有一个方向开灯,就说明这是亮的
ans[i]=1
for dx,dy in (0,1),(1,0),(0,-1),(-1,0),(0,0),(1,1),(-1,1),(1,-1),(-1,-1):#去判断是哪个方向上的灯,并关掉
nx=r+dx
ny=c+dy
if (nx,ny) in points:#是开灯,各方向计数-1
points.remove((nx,ny))
row_cnts[nx]-=1
col_cnts[ny]-=1
lr_cnts[nx+ny]-=1
rl_cnts[nx-ny]-=1
return ans
2006.差的绝对值为K的数对数目(简)2.9
算法思路:哈希表+一次遍历
我们进行一次遍历,遍历时下标代表j。对每一个j,我们需要知道在这个j之前的符合条件的i的个数,即满足|nums[i]-nums[j]|=k的i的个数,亦即满足nums[i]=nums[j]+k或nums[j]-k的i的个数。使用哈希表可以在O(1)的时间内统计出这样的个数,因此在遍历时我们可以使用一个哈希表来维护不同数值的频率,并统计符合条件的数对数。
class Solution(object):
def countKDifference(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
res=0
cnt=defaultdict(int)#哈希字典用于统计
for num in nums:
res+=cnt[num+k]+cnt[num-k]#和前面的匹配
cnt[num]+=1#统计已出现次数
return res
1447.最简分数2.10
解题思路:暴力模拟
由于要保证分数在(0,1)范围内,我们可以枚举分母denominator∈[2,n]和分子numerator∈[1,denominator),若分子分母的最大公约数为1,则我们找到了一个最简分数。
class Solution(object):
def gcd(x,y):
while x%y!=0:
x,y=y,x%y
return y
def simplifiedFractions(self, n):
"""
:type n: int
:rtype: List[str]
"""
def gcd(x,y):
while x%y!=0:
x,y=y,x%y
return y
ls=[]
for i in range(2,n+1):
for j in range(1,i):#分子j是小于分母i的
if gcd(i,j)==1:
f=str(j)+"/"+str(i)
ls.append(f)
return ls
1984.学生分数的最小差值(简)2.11
算法思路:排序
首先对数组nums进行升序排序,随后使用一个大小固定为k的滑动窗口在nums上进行遍历。记滑动窗口的左边界为i,那么右边界即为i+k-1,窗口中的k名学生最高分和最低分的差值即为nums[i+k-1]-nums[i]。最终的答案即为所有nums[i+k-1]-nums[i]中的最小值。
class Solution(object):
def minimumDifference(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
nums.sort()
minnum=("float")
for i in range(len(nums)-k+1):
a=nums[i+k-1]-nums[i]
if minnum>a:
minnum=a
return minnum
1020.飞地的数量2.12
解题思路:多源BFS
从四条外边的陆地出发的BFS,先把边界的陆地加入要遍历点的队列中,把1变为0。四个方向遍历,把能走到的陆地1变为0,再加入要遍历点的队列中。最后计算不能到达的陆地格子数量(地图中还是1的数量/值总和)。
- deque()双向链表
- pop(1)可带参数、popleft()不可带参数
class Solution(object):
def numEnclaves(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
m=len(grid)
n=len(grid[0])
queue=deque()#存放满足条件的,作为bfs源点
for i in range(m):
if grid[i][0]:#第一列
queue.append((i,0))
grid[i][0]=0
if grid[i][n-1]:#最后一列
queue.append((i,n-1))
grid[i][n-1]=0
for j in range(n):
if grid[0][j]:#第一行
queue.append((0,j))
grid[0][j]=0
if grid[m-1][j]:#最后一行
queue.append((m-1,j))
grid[m-1][j]=0
while queue:
x,y=queue.popleft()
for dx,dy in ((0,-1),(-1,0),(0,1),(1,0)):
nx=x+dx
ny=y+dy
if 0<=nx<m and 0<=ny<n and grid[nx][ny]:#满足条件,要继续遍历
queue.append((nx,ny))
grid[nx][ny]=0
return sum(sum(g) for g in grid)
1189."气球"的最大数量(简)2.13
算法思路:统计
统计字符串中字母‘a’,‘b’,‘l’,‘o’,‘n’ 的数量即可。其中每个字母 “balloon” 需要两个‘l’,‘o’,可以将字母‘l’,‘o’ 的数量除以 22,返回字母 ‘a’,‘b’,‘l’,‘o’,‘n’ 中数量最小值即为可以构成的单词数量。
class Solution(object):
def maxNumberOfBalloons(self, text):
"""
:type text: str
:rtype: int
"""
cnt=Counter(ch for ch in text if ch in 'balon')
cnt['l']=cnt['l']//2
cnt['o']=cnt['o']//2
# print(cnt)
return min(cnt.values()) if len(cnt)==5 else 0 #若不这样写,字母不足时,会返回已有的最小数值不为0,错误
540.有序数组中的单一元素2.14
算法思路:偶数下标的二分查找
考虑没有单个元素的情况:偶数下标的值与右边的值相等
考虑有一个单个元素的情况:则该元素一定在偶数下标,且他不与右边的值相等。
在偶数下标范围内,进行二分查找。找到满足nums[x]/nums[x+1]的最小的偶数下标x,则下标x处的元素是只出现一次的元素。
- 初始时,左边界为最小偶数0,右边界为len(nums)-1,因为长度为奇数。
- 每次取左右边界的平均值mid作为判断的下标,如果mid是奇数则将mid减1,确保mid是偶数,
- 比较nums[mid]和nums[mid+1]是否相等,如果相等则mid<x,调整左边界,否则mid>=x,调整右边界。
- 调整边界后继续二分查找,直到确定x的值。
注意
- mid是偶数时,mid&1=0
- mid是奇数时,mid&1=1
class Solution(object):
def singleNonDuplicate(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
low=0
high=len(nums)-1
while low<high:
mid=(high+low)//2
mid-=mid&1
if nums[mid]==nums[mid+1]:
low=mid+2
else:
high=mid
return nums[low]
1380.矩阵中的幸运数字(简)2.15
算法思路:数组、矩阵、数学规律
这样的点至多有一个,我们只需要确定某行最小值的最大值与某列最大值的最小值一致,若存在即找到这样的两个数,才存在答案。
自己写的,有点冗长
class Solution(object):
def luckyNumbers (self, matrix):
"""
:type matrix: List[List[int]]
:rtype: List[int]
"""
m=len(matrix)
n=len(matrix[0])
max_x=-1
min_y=("float")
# max_ls=[]#存每列的最大值
min_ls=[]#存每行的最小值
for i in range(m):
for j in range(n):
min_y=min(min_y,matrix[i][j])
min_ls.append(min_y)
min_y=("float")
for j in range(n):
for i in range(m):
max_x=max(max_x,matrix[i][j])
if max_x==max(min_ls):
return [max_x]
max_x=-1
return []
1719.重构一棵树的方案数(超难)2.16
官方题解
说是hard里面的hard
class Solution:
def checkWays(self, pairs: List[List[int]]) -> int:
adj = defaultdict(set)
for x, y in pairs:
adj[x].add(y)
adj[y].add(x)
# 检测是否存在根节点
root = next((node for node, neighbours in adj.items() if len(neighbours) == len(adj) - 1), -1)
if root == -1:
return 0
ans = 1
for node, neighbours in adj.items():
if node == root:
continue
currDegree = len(neighbours)
parent = -1
parentDegree = maxsize
# 根据 degree 的大小找到 node 的父节点 parent
for neighbour in neighbours:
if currDegree <= len(adj[neighbour]) < parentDegree:
parent = neighbour
parentDegree = len(adj[neighbour])
# 检测 neighbours 是否为 adj[parent] 的子集
if parent == -1 or any(neighbour != parent and neighbour not in adj[parent] for neighbour in neighbours):
return 0
if parentDegree == currDegree:
ans = 2
return ans
688.骑士在棋盘上的概率2.17
算法思路:DFS
- 结束条件,先判断是否出界,出界的话return 0,再判断已经走了k步还在界内的 return 1
- res记录 周围8步 累加一共有多少在界内,最后,除以8算概率
- 最后再加上 记忆优化
class Solution:
def knightProbability(self, n: int, k: int, row: int, column: int) -> float:
@functools.lru_cache(None)#缓存,防止超时
def dfs(r,c,counts):
if r<0 or r>n-1 or c<0 or c>n-1:
return 0
if counts==k:
return 1
step=[(-2,1),(-1,2),(1,2),(2,1),(2,-1),(1,-2),(-1,-2),(-2,-1)]
res=0
for i,j in step:
res+=dfs(r+i,c+j,counts+1)
return res/8
return dfs(row,column,0)
1791.找出星型图的中心节点(简)2.18
解题思路:超级简单的简单题
实际上只需要比较前两组,相同元素便为中心节点
class Solution(object):
def findCenter(self, edges):
"""
:type edges: List[List[int]]
:rtype: int
"""
a,b=edges[0]
i,j=edges[1]
if a==i or a==j:
return a
else:
return b