1.接8.26的文章
最短的桥:给你一个大小为 n x n 的二元矩阵 grid ,其中 1 表示陆地,0 表示水域。
岛 是由四面相连的 1 形成的一个最大组,即不会与非组内的任何其他 1 相连。grid 中 恰好存在两座岛 。
你可以将任意数量的 0 变为 1 ,以使两座岛连接起来,变成 一座岛 。
返回必须翻转的 0 的最小数目。
示例 1:
输入:grid = [[0,1],[1,0]]
输出:1
示例 2:
输入:grid = [[0,1,0],[0,0,0],[0,0,1]]
输出:2
示例 3:
输入:grid = [[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
输出:1
class Solution:
def shortestBridge(self,grid:List[List[int]])->int:
n=len(grid)
for i,row in enumerate(grid):
for j,v in enumerate(row):
if v!=1:
continue
island=[]
grid[i][j]=-1
q=deque([(i,j)])
while q:
x,y=q.popleft()
island.append((x,y))
for nx,ny in (x+1,y)(x-1,y)(x,y+1)(x,y-1):
if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] == 1:
grid[nx][ny]=-1
q.append((nx,ny))
代码逻辑:
我也不知道要加什么奇奇怪怪的中间变量,我就知道,我需要干这么几件事情:
1.我需要判断是不是岛屿,也就是说找到1了我再进行操作
2.找到岛屿以后,目的是把岛屿信息存进一个列表里,方便之后广度优先搜索找下一个岛屿,同时也要建立一个双端队列去找岛屿的其他部分
3.那么这个时候就要初始化两个列表了,一个island=[],一个q.deque([])其中,q我直接就可以存第一个元组进去,那就是(i,j)
4.进q循环前,我需要给当前的岛屿置-1,表示我来过了
5.上下左右遍历并判断,先判断范围再判断值,符合条件就加进island同时加进q,待会pop,别忘了置-1表示这里我来过了
接下来的代码是在两个for循环下的,也就是和while同等级,虽然for循环两层嵌套,但是这个嵌套的目的只是为了找到第一个1,所以这个for循环的作用已经结束了,在这一层循环里就可以干完所有的事情最后直接return了,直接就不理for了,不然我还得在这层循环里加一个break然后再出去做剩下的事没必要
step=0
q=island
while True:
tmp=q
q=[]
for x,y in tmp:
for nx,ny in (x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1):
if 0 <= nx < n and 0 <= ny < n:
if grid[nx][ny]==1:
return step
if grid[nx][ny]==0:
grid[nx][ny]==-1
q.append(nx,ny)
step+=1
这循环套循环看得人头皮发麻,我来捋一捋
现在的目标是,在这个岛的基础之上,均匀的往外扩展一层,那么现在就需要遍历这个岛的所有的-1,然后在旁边置-1,只一层,那么就是从一个队列里遍历现有的岛屿,然后新置-1的部分不遍历,这样就能稳定的只扩一层
代码逻辑:
1.tmp现在就是island的所有坐标对,第一层循环for x,y in tmp就是遍历现在岛上的所有值
2.for nx,ny是给每个点都往外找一层,符合条件的再考虑,当然,首先还是要考虑是否在合理范围内,这很重要。
3.如果还在地图内,那么就分两种情况,如果是-1,那就不管,说明回到岛上了,如果不是-1,那么就要么是0要么是1,如果是1,那就直接输出step,如果是0,那就置-1,同时把这些0加入队列里。
4.等于是第一次的队列是全岛,第二次的队列就是单纯的岛外围。
2.最大连续1的个数 III
给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。
示例 1:
输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
方法一:二分法:
class Solution:
def longestOnes(self,nums:List[int],k:int)->int:
n=len(nums)
P=[0]
for num in nums:
P.append(P[-1]+(1-num))
ans=0
for right in range(n):
left = bisect.bisect_left(P, P[right + 1] - k)
ans = max(ans, right - left + 1)
return ans
这个代码看懂要了半天,接下来我会详细讲解这个代码
首先要去找最少的翻0的数,不如去全部翻转,找有多少个1,原式就有多少个0,所以代码的第一部分就是在干这个事情。代码的第二段是个二分查找,使用了库函数在干这个事,我待会会写一个手动实现来满足这个事情,在这里要强调的是:
要找的下标=bisect.bisect_left(列表,找谁)
这个函数返回的是第一个大于或等于指定值的位置。如果是bisect_right返回的是第一个大于指定值的位置。也就是说left函数能找到这个值,right肯定找不到这个值,对了,还要求数列必须是单调增的,所以使用条件非常的局限,那我还不如直接自己写二分法。
def bisect_left(arr,x):
if x<=arr[0]:
return 0
left,right=0,len(arr)
while left<right:
mid=(left+right)//2
if arr[mid]<x:
left=mid+1
else:
right=mid
return left
#或者
def bisect_left(arr, x):
if x <= arr[0]:
return 0
left, right = 0, len(arr) - 1 # 调整 right 为 len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] < x:
left = mid + 1
else:
right = mid - 1 # 更新方式改为 right = mid - 1
return left
方法二:滑动窗口
class Solution:
def longestOnes(self, nums: List[int], k: int) -> int:
n = len(nums)
left = lsum = rsum = 0
ans = 0
for right in range(n):
rsum += 1 - nums[right]
while lsum < rsum - k:
lsum += 1 - nums[left]
left += 1
ans = max(ans, right - left + 1)
return ans
n: 数组 nums 的长度。
left: 滑动窗口的左边界。
lsum: 当前窗口内 0 的总数。
rsum: 当前窗口内 0 的总数(包括窗口的右边界)。
ans: 记录最大连续 1 的长度。
逻辑说明
1.初始化:left, lsum, 和 rsum 都初始化为 0。ans 初始化为 0,用于存储结果。
2.遍历数组:使用 right 指针从 0 遍历到 n-1,即遍历整个数组。
3.更新 rsum:rsum += 1 - nums[right] 用于更新窗口内的 0 的数量。如果 nums[right] 是 0,那么 1 - nums[right] 是 1,增加窗口内 0 的计数;如果 nums[right] 是 1,那么 1 - nums[right] 是 0,不改变 0 的计数。
4.调整左边界 left: while lsum < rsum - k 这个条件确保窗口内的 0 数量不超过 k。如果窗口内 0 的数量 lsum 小于 rsum - k,说明当前窗口内的 0 的数量已经超过了 k,需要调整左边界。
在 while 循环中,lsum += 1 - nums[left] 更新窗口内的 0 的数量,left += 1 移动左边界来缩小窗口。
5.更新答案:ans = max(ans, right - left + 1) 更新 ans 为当前窗口长度(right - left + 1)和之前的最大值的较大者。
6.返回结果:return ans 返回最大连续 1 的长度。
3.日期之间隔几天
class Solution:
def daysBetweenDates(self, date1: str, date2: str) -> int:
# 平年的 12 个月的天数
month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
# 解析日期字符串,确保 d1 为较早的日期,d2 为较晚的日期
d1, d2 = min(date1, date2), max(date1, date2)
year1, month1, day1 = (int(i) for i in d1.split('-'))
year2, month2, day2 = (int(i) for i in d2.split('-'))
# 判断是否为闰年的辅助函数
def is_leap(year):
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
# 计算两个年份之间的总天数
def days_between_two_years(year1, year2):
total_days = 0
for year in range(year1, year2):
if is_leap(year):
total_days += 366
else:
total_days += 365
return total_days
# 计算从当年的1月1号到某一天的天数
def days_from_year_start(year, month, day):
# 计算本年从1月1日到指定日期的天数
days_count = sum(month_days[:(month-1)]) + day
# 如果是闰年且已经过了2月,补偿一天
if is_leap(year) and month > 2:
days_count += 1
return days_count
# 计算两个日期之间的总天数
days_between_years = days_between_two_years(year1, year2)
days_from_start_to_d2 = days_from_year_start(year2, month2, day2)
days_from_start_to_d1 = days_from_year_start(year1, month1, day1)
# 总天数 = 年份之间的天数 + d2 当年的天数 - d1 当年的天数
total_days = days_between_years + days_from_start_to_d2 - days_from_start_to_d1
return total_days
逻辑很简单,但是很长,考虑的情况很多,记录一下
4.计算器(栈)
class Solution:
def calculate(self,s:str)->int:
n=len(s)
stack=[]
preSign='+'
num=0
for i in range(n):
if s[i]=' ' and s[i].isdigit():
num=num*10+(ord(s[i])-ord('0'))
if i=n-1 or s[i] in '+-*/'
if preSign=='+':
stack.append(num)
elif preSign=='*':
stack.append(stack.pop()*num)
elif preSign=='/':
stack.append(int(stack.pop()/num))
else:
stack.append(-num)
preSign=s[i]
num=0
return sum(stack)
1.这个题的逻辑也没什么好说的,值得注意的是,中间的ord,是转换成阿斯科码进行加减计算的,有点脱裤子放屁的感觉,
2.要注意的就是,他默认的preSign是加号,被减数进stack的时候是以+num进去的,然后减号才被写进preSign=s[i]中。然后还有就是比如乘号吧,因为preSign是+,所以同样的道理,第一个乘数进去是加进去的。
3.读到符号变化以后,一定要给num清空,不影响下一次。
4.in-1的时候,理论上应该操作了,因为是先写进去的num,所以此时进入第二个if也就是in-1的时候num已经完整了,再进行的preSign的具体操作。
5.二叉树染色
小扣有一个根结点为 root 的二叉树模型,初始所有结点均为白色,可以用蓝色染料给模型结点染色,模型的每个结点有一个 val 价值。小扣出于美观考虑,希望最后二叉树上每个蓝色相连部分的结点个数不能超过 k 个,求所有染成蓝色的结点价值总和最大是多少?
class Solution:
def maxValue(self, root: TreeNode, k: int) -> int:
def dfs(root):
res=[0]*(k+1)
if not root:
return res
left=dfs(root.left)
right=dfs(root.right)
res[0]=max(left)+max(right)
for i in range (k):
for j in range(k-1):
if left[i]+right[j]+root.val>res[i+j+1]:
res[i+j+1]=left[i]+right[j]+root.val
return res
return max(dfs(root))
再看:
1.定义 dfs 函数:
这个函数用于计算以当前节点为根的子树中,最多染色 k 个节点的蓝色的最大价值总和。它返回一个长度为 k+1 的数组 res,其中 res[i] 表示最多染 i 个节点的最大价值总和。
2.处理空节点:
如果当前节点为空,直接返回一个全为 0 的数组 res。
3.递归计算左右子树:
递归调用 dfs 计算左子树和右子树中最多染 k 个节点的最大价值。结果分别存储在 left 和 right 中。
4.计算当前节点的最大价值:
res[0] = max(left) + max(right):这是不染色当前节点的情况,即左子树和右子树的最大值之和。
for i in range(k) 和 for j in range(k - i):这些循环用于计算染色当前节点的情况。i 和 j 分别表示在左子树和右子树中染色的节点数。
if left[i] + right[j] + root.val > res[i + j + 1]:检查染色当前节点后,最大价值是否更新。
res[i + j + 1] = left[i] + right[j] + root.val:更新结果数组 res。
5.返回结果:
return max(dfs(root)):返回根节点为 root 的整个树中染色节点的最大价值。