2024.8.24 Python,链表异常断裂问题,双链表的建立问题,全排列中的引用机制与copy的使用,最大子数组和

1.浏览器系统设计

你有一个只支持单个标签页的 浏览器 ,最开始你浏览的网页是 homepage ,你可以访问其他的网站 url ,也可以在浏览历史中后退 steps 步或前进 steps 步。

请你实现 BrowserHistory 类:

BrowserHistory(string homepage) ,用 homepage 初始化浏览器类。

void visit(string url) 从当前页跳转访问 url 对应的页面 。执行此操作会把浏览历史前进的记录全部删除。

string back(int steps) 在浏览历史中后退 steps 步。如果你只能在浏览历史中后退至多 x 步且 steps > x ,那么你只后退 x 步。请返回后退 至多 steps 步以后的 url 。

string forward(int steps) 在浏览历史中前进 steps 步。如果你只能在浏览历史中前进至多 x 步且 steps > x ,那么你只前进 x 步。请返回前进 至多 steps步以后的 url 。

***方法一:***我的办法,单链表:

py 复制代码
class ListNode:
    def __init__(self, val: str, next=None):
        self.val = val
        self.next = next

class BrowserHistory:
    def __init__(self, homepage: str):
        self.head = ListNode(homepage)
        self.cur = self.head

    def visit(self, url: str) -> None:
        # 创建新节点
        new_node = ListNode(url)
        # 当前节点的 next 指向新节点
        self.cur.next = new_node
        # 将当前节点移到新节点
        self.cur = new_node
        # 确保新节点之后没有其他节点
        self.cur.next = None

    def back(self, steps: int) -> str:
        temp = self.head
        total_step = 0
        
        # 计算当前节点的位置距离头的位置,n个节点走n-1步
        while temp != self.cur:
            temp = temp.next
            total_step += 1

        # 如果步数超过了实际步数,只回退到起始点,这步很重要
        forward_step = max(0, total_step - steps)
        
        # 找到目标节点,从头开始走forward步
        temp = self.head
        for _ in range(forward_step):
            temp = temp.next
        
        # 更新当前节点为目标节点
        self.cur = temp
        return self.cur.val 

    def forward(self, steps: int) -> str:
        for _ in range(steps):
            if self.cur.next is None:  # 如果已经到达最后一个页面,无法再前进
                break
            self.cur = self.cur.next
        return self.cur.val

从早上开始到现在我一直没搞懂为什么我的代码有问题,我以为是back的问题,其实back也有问题,back和forward我都没有加限制条件,在测试用例中,他含有超过范围的特殊值,所以要给他加限定条件,我的代码的最大问题在于我的visit是这样写的:

py 复制代码
def visit(self, url: str) -> None:
        self.cur=self.cur.next        
        self.cur=ListNode(url)
###上面的代码是错误的,下面的是对的
def visit(self, url: str) -> None:
        self.cur.next=ListNode(url)
        self.cur=self.cur.next        

chat说我的代码这样写链表会导致前面的链表断了。所以他一直在back中报错,说我的temp=temp.next中temp已经是None了,那么temp的next毫无意义。所以我一直在找back的问题。现在来看chat对于我的链表断裂问题是怎么说的:

1.在这段代码中,首先你将 self.cur 移动到 self.cur.next,然后将 self.cur 指向一个新的 ListNode(url) 节点。这会导致你丢失原本的链表结构。

2.具体来说,当你执行 self.cur = self.cur.next 后,self.cur 指向了原链表中的下一个节点。然而,你马上用 self.cur = ListNode(url) 创建了一个新的节点,并且 self.cur 再也不会指向原来的链表中的那个节点。也就是说,你原来链表的前一个节点的 next 指针仍然指向这个旧节点,而不是新的节点。这就导致前进操作不可行,因为你丢失了历史记录。

啊啊啊啊,也就是说,我应该先给next赋值,再移动指针,我的错误在于先移动了指针,再才建立了联系。
方法二:双链表

py 复制代码
class ListNode:
    def __init__(self, val: str):
        self.val = val
        self.prev = None
        self.next = None

class BrowserHistory:
    def __init__(self, homepage: str):
        self.cur = ListNode(homepage)

    def visit(self, url: str) -> None:
        new_node = ListNode(url)
        self.cur.next = new_node
        new_node.prev = self.cur
        self.cur = new_node  # 将当前指针更新为新页面
        new_node.next = None  # 断开当前节点的前进记录

    def back(self, steps: int) -> str:
        while steps > 0 and self.cur.prev is not None:
            self.cur = self.cur.prev
            steps -= 1
        return self.cur.val

    def forward(self, steps: int) -> str:
        while steps > 0 and self.cur.next is not None:
            self.cur = self.cur.next
            steps -= 1
        return self.cur.val
# Your BrowserHistory object will be instantiated and called as such:
# obj = BrowserHistory(homepage)
# obj.visit(url)
# param_2 = obj.back(steps)
# param_3 = obj.forward(steps)

双链表的定义和上面单链表的类似,用next定义下一个节点,然后next的上一个是cur,cur的下一个是next,这样一个新的结点就定义完了。只有这样才能让链表把数据串起来,

py 复制代码
def visit(self, url: str) -> None:
        self.cur.next = ListNode(url)         
        self.cur.next.prev = self.cur
        self.cur = self.cur.next  # 将当前指针更新为新页面
        
def visit(self, url: str) -> None:
        new_node = ListNode(url)
        self.cur.next = new_node
        new_node.prev = self.cur
        self.cur = new_node  # 将当前指针更新为新页面
        new_node.next = None  # 断开当前节点的前进记录

这两个是一样的,所以一定要细致的去思考这个关系
方法三:动态数组

py 复制代码
class BrowserHistory:

    def __init__(self, homepage: str):
        # 初始化浏览器历史记录,`history` 存储所有访问过的页面
        self.history = [homepage]
        # `current_index` 表示当前页面在历史记录中的位置
        self.current_index = 0

    def visit(self, url: str) -> None:
        # 删除当前页面之后的所有页面,因为新访问的页面会替代它们
        self.history = self.history[:self.current_index + 1]
        # 将新访问的页面添加到历史记录中
        self.history.append(url)
        # 更新当前页面的位置
        self.current_index += 1

    def back(self, steps: int) -> str:
        # 计算需要回退的步数,但不超过历史记录的开始位置
        self.current_index = max(0, self.current_index - steps)
        # 返回回退后的页面
        return self.history[self.current_index]

    def forward(self, steps: int) -> str:
        # 计算需要前进的步数,但不超过历史记录的末尾
        self.current_index = min(self.current_index + steps, len(self.history) - 1)
        # 返回前进后的页面
        return self.history[self.current_index]


# Your BrowserHistory object will be instantiated and called as such:
# obj = BrowserHistory(homepage)
# obj.visit(url)
# param_2 = obj.back(steps)
# param_3 = obj.forward(steps)

这个代码没什么好说的,这是一个对于数组的很好的操作,我觉得这些代码中的max(0,index-step)还有min(index+steps,len(self.history)-1)这两个操作来去限制back和forward,是值得我去学习的。不一定非得要用if来加限制条件,有些限制条件是可以去想办法简化的

2.全排列,接8.16号的全排列

今天自行的做了一遍全排列的题,出现了一些问题,我的代码如下:

py 复制代码
#这个代码是错的
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        def backtrack(letter,remaining):
            if remaining==[]:
                res.append(letter)
                return 
            n=len(remaining)
            for i in range(n):
                letter+=[remaining[i]]
                backtrack(letter,remaining[0:i]+remaining[i+1:n])
        res=[]
        backtrack([],nums)
        return res     

看起来逻辑是没什么问题的,但是实际的输出极其糟糕,大概是这样的:

[[1,2,3,3,2,2,1,3,3,1,3,1,2,2,1],[1,2,3,3,2,2,1,3,3,1,3,1,2,2,1],[1,2,3,3,2,2,1,3,3,1,3,1,2,2,1],[1,2,3,3,2,2,1,3,3,1,3,1,2,2,1],[1,2,3,3,2,2,1,3,3,1,3,1,2,2,1],[1,2,3,3,2,2,1,3,3,1,3,1,2,2,1]]

我以为是我对数据类型的操控有问题,检查了半天,我也没能看出问题,所以我就查了之前的代码,完全找不出和之前的代码有什么区别,之前代码如下:

py 复制代码
class Solution:
	def permute(self,nums:List[int])->List[List[int]]:
		res=[]
		def backtrack(path,remaining):
			if not remaining:
				res.append(path)
				return
			for i in range(len(remaining)):
				backtrack(path+[remaining[i]],remaining[:i]+remaining[i+1:])
		backtrack([],nums)
		return res

我无非就是用了path和letter的区别,为什么这么大呢,原因在于,正确的代码里,应该带入的是letter加remaining[i],而这一层的letter的值并没有实际的改变,但是我的代码中,letter+=后,letter变了,带入的时候就在i=0的时候就改变了,那你想,在i=1的时候,letter不为空了,就继续算进去了,然后append这个函数也怪的很,他是一个引用机制,即便是现在你把当前的letter用append加入进res了,他仍然会因为后续的改变而改变当前的值,所以只用pop是不够的,还需要给letter一个复制版

具体的代码如下:

py 复制代码
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        def backtrack(letter,remaining):
            if remaining==[]:
                res.append(letter[:])		#改的部分
                return 
            n=len(remaining)
            for i in range(n):
                letter+=[remaining[i]]
                backtrack(letter,remaining[0:i]+remaining[i+1:n])
                letter.pop()    #改的部分
        res=[]
        backtrack([],nums)
        return res            

我没有完全理解这个引用机制

方法二:仍然是做错了,交换法:

py 复制代码
#以下是错误代码
class Solution:
    def permute(self,nums:List[int])->List[List[int]]:
        if nums==[]:
            return []
        n=len(nums)
        res=[]
        def backtrack(first):
            for i in range(first,n):
                nums[i],nums[first]=nums[first],nums[i]
                backtrack(first+1)
                nums[i],nums[first]=nums[first],nums[i]
            return res.append(nums[:])
        backtrack(0)
        return res

输出是:[[1,2,3],[1,2,3],[1,3,2],[1,3,2],[1,2,3],[2,1,3],[2,1,3],[2,3,1],[2,3,1],[2,1,3],[3,2,1],[3,2,1],[3,1,2],[3,1,2],[3,2,1],[1,2,3]]

这次的问题在于,没有给backtrack设定好合理的出口,nums[:]这次注意了,但是出口不对,不是在for循环里出的,而是要加一个if。当所有的事情完成的时候,再出去。最终的代码应该是:

py 复制代码
class Solution:
    def permute(self,nums:List[int])->List[List[int]]:
        if nums==[]:
            return []
        n=len(nums)
        res=[]
        def backtrack(first):
            if first==n:
                res.append(nums[:])
                return 
            for i in range(first,n):
                nums[i],nums[first]=nums[first],nums[i]
                backtrack(first+1)
                nums[i],nums[first]=nums[first],nums[i]
            
        backtrack(0)
        return res

3.最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]

输出:6

解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入:nums = [1]

输出:1

示例 3:

输入:nums = [5,4,-1,7,8]

输出:23

**方法一:**暴力法,略,两个循环求就完事了,但是两个循环循死你时间复杂度O(N²)

**方法二:**暴力法但一遍遍历: 这个办法本来都不想看了,但是细细一看还是挺有意思的,虽然没有普适性,但是对于开阔思路有帮助:

py 复制代码
class Solution:
	def maxSubArray(self,nums:List[int])->int:
		tmp=nums[0]
		n=len(nums)
		res=tmp
		for i in range(1,n):
			if tmp+nums[i]>nums[i]:
				res=max(res,tmp+nums[i])
				tmp+=nums[i]
			else:
				res=max(res,tmp+nums[i],tmp,nums[i])
				tmp=nums[i]
		return res

代码逻辑:

1.初始化:tmp=nums[0]:tmp 用于记录当前子数组的和,初始化为第一个元素的值。

res=tmp:res 用于记录迄今为止找到的最大子数组的和,初始化为第一个元素的值。

2.遍历数组:for i in range(1,n):从数组的第二个元素开始遍历。

动态更新当前子数组和:

if tmp + nums[i] > nums[i]:判断是否将当前元素 nums[i] 加入到当前子数组(即 tmp + nums[i])中会得到一个更大的子数组和。如果是,说明当前子数组可以继续扩展。

tmp += nums[i]:如果当前子数组可以继续扩展,更新 tmp 为扩展后的和。

否则,tmp = nums[i]:如果扩展后的子数组和不如当前元素 nums[i] 本身大,说明之前的子数组和可能有负作用,应当从当前元素重新开始构建子数组。

3.更新最大子数组和:res=max(res,tmp):在每一步更新 res,使其始终保持为最大子数组的和。

4.返回结果:最终返回 res,即遍历整个数组后找到的最大子数组和。

为什么这段代码能够遍历完所有可能:

在每次遍历元素时,tmp 代表以当前元素结尾的最大子数组和。通过比较 tmp + nums[i] 和 nums[i],我们判断是否继续扩展子数组还是重新开始新的子数组。

res 始终保持遍历过程中遇到的最大子数组和,所以最终结果一定是全局最大的。

这段代码的核心思想是,既然我们需要找到最大和的子数组,那么在每一步都要判断当前子数组是否有扩展的潜力,如果没有,就从当前元素重新开始构建子数组。这样,最终遍历完成时,res 就会是最大和的子数组。

动态规划思想:这个过程实际上是在动态规划中使用了「状态转移」的思想,通过维护一个局部最优解 tmp,并逐步更新全局最优解 res。
方法三:分治法

它的最大子序和要么在左半边,要么在右半边,要么是穿过中间,对于左右边的序列,情况也是一样,因此可以用递归处理。中间部分的则可以直接计算出来,时间复杂度应该是 O(nlogn)。代码如下:

py 复制代码
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        #递归终止条件
        if n == 1:
            return nums[0]
        else:
            #递归计算左半边最大子序和
            max_left = self.maxSubArray(nums[0:len(nums) // 2])
            #递归计算右半边最大子序和
            max_right = self.maxSubArray(nums[len(nums) // 2:len(nums)])
        
        #计算中间的最大子序和,从右到左计算左边的最大子序和,从左到右计算右边的最大子序和,再相加
        max_l = nums[len(nums) // 2 - 1]
        tmp = 0
        for i in range(len(nums) // 2 - 1, -1, -1):
            tmp += nums[i]
            max_l = max(tmp, max_l)
        max_r = nums[len(nums) // 2]
        tmp = 0
        for i in range(len(nums) // 2, len(nums)):
            tmp += nums[i]
            max_r = max(tmp, max_r)
        #返回三个中的最大值
        return max(max_right,max_left,max_l+max_r)

方法四:动态规划:

py 复制代码
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        for i in range(1, len(nums)):
            nums[i] += max(nums[i - 1], 0)
        return max(nums)
相关推荐
技术猿188702783512 分钟前
阿里巴巴1688中国站商品搜索API返回值深度解析与实战应用
开发语言·python
wangsir.10 分钟前
进程间通信-进程池
linux·开发语言·算法
虾小眠052613 分钟前
python 100个算法
python·算法
是小比特14 分钟前
再谈c++模板
开发语言·c++
冯宝宝^17 分钟前
“鸿儒”——AIGC团队知识管理工作台
python·aigc
闲人编程19 分钟前
Python实现人工免疫系统算法
开发语言·python·算法·优化·ais·人工免疫
程序猿阿伟22 分钟前
《C++位域:在复杂数据结构中的精准驾驭与风险规避》
开发语言·数据结构·c++
lsnm25 分钟前
C++ 二叉树进阶
开发语言·c++·b树
Lilixy.182329 分钟前
【Java-简单练习题】
java·开发语言·python
Ty_110631 分钟前
深入理解Java虚拟机:Jvm总结-Java内存区域与内存溢出异常
java·开发语言·jvm