2024.8.27 Python,广度优先搜索找最短的桥,最大连续1的个数(二分法和滑动窗口),日期之间隔几天,用栈写计算器,二叉树染色

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!=1continue
				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 的整个树中染色节点的最大价值。

  • 18
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java数组是一种用于存储多个相同类型元素的数据结构。它可以存储基本数据类型(如int、float等)或者引用类型(如String、对象等)。数组在内存中是连续存储的,通过索引可以访问和修改数组中的元素。 二分法是一种高效的查算法,适用于已排序的数组。它通过将数组分成两部分,然后判断目标元素在哪一部分中,从而缩小查范围。具体步骤如下: 1. 确定数组的起始索引start和结束索引end。 2. 计算中间索引mid,即mid = (start + end) / 2。 3. 比较中间索引对应的元素与目标元素的大小关系: - 如果中间元素等于目标元素,则到了目标元素,返回中间索引。 - 如果中间元素大于目标元素,则目标元素在左半部分,更新结束索引为mid - 1。 - 如果中间元素小于目标元素,则目标元素在右半部分,更新起始索引为mid + 1。 4. 重复步骤2和步骤3,直到到目标元素或者起始索引大于结束索引。 对于给定的数组[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],我们可以使用二分法第一个1的索引。具体步骤如下: 1. 起始索引start为0,结束索引end为13。 2. 计算中间索引mid,即mid = (0 + 13) / 2 = 6。 3. 比较中间索引对应的元素与目标元素1的大小关系: - 中间元素1等于目标元素1,但我们要的是第一个1,所以更新结束索引为mid。 4. 重复步骤2和步骤3,直到到目标元素或者起始索引大于结束索引。 5. 继续二分查,此时起始索引start为0,结束索引end为6。 6. 计算中间索引mid,即mid = (0 + 6) / 2 = 3。 7. 比较中间索引对应的元素与目标元素1的大小关系: - 中间元素0小于目标元素1,更新起始索引为mid + 1。 8. 继续二分查,此时起始索引start为4,结束索引end为6。 9. 计算中间索引mid,即mid = (4 + 6) / 2 = 5。 10. 比较中间索引对应的元素与目标元素1的大小关系: - 中间元素1等于目标元素1,但我们要的是第一个1,所以更新结束索引为mid。 11. 继续二分查,此时起始索引start为4,结束索引end为5。 12. 计算中间索引mid,即mid = (4 + 5) / 2 = 4。 13. 比较中间索引对应的元素与目标元素1的大小关系: - 中间元素1等于目标元素1,但我们要的是第一个1,所以更新结束索引为mid。 14. 继续二分查,此时起始索引start为4,结束索引end为4。 15. 起始索引等于结束索引,查结束。返回起始索引4。 所以,在给定的数组中,使用二分法第一个1的索引为4。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值