Sort List Leetcode Python

本文介绍了一种在链表上实现归并排序的方法,该方法的时间复杂度为O(nlogn),空间复杂度为常数。文章通过示例代码详细解释了如何在链表中找到中间节点,并对左右两部分进行递归排序,最后将两个有序链表合并。

Sort a linked list in O(n log n) time using constant space complexity.

这道题要求时间为nlogn我们联想到quick sort 或者mergesort这里采用mergesort

quick sort的最差为n^2 在逆序的时候。


和array mergesort不同的是这里找Mid 需要用两个pointer一个fast 一个slow

当fast.走到底的时候slow 刚刚好走到中间 其他的做法与array的mergesort一样。

代码如下 

class ListNode:
    def __init__(self,val):
        self.val=val
        self.next=None


head=root=ListNode(0)
head.next=ListNode(10)
head=head.next
head.next=ListNode(22)
head=head.next
head.next=ListNode(3)
head=head.next
head.next=ListNode(1)
head=head.next
head.next=ListNode(2)
head=head.next
head.next=ListNode(4)

def printL(root):
    while root:
        print root.val
        root=root.next


printL(root)

def merge(left,right):
    dummy=ListNode(0)
    newhead=dummy
    if left==None:
        return right
    if right==None:
        return left
        
    while left and right:
        if left.val<right.val:
            newhead.next=ListNode(left.val)
            left=left.next
        else:
            newhead.next=ListNode(right.val)
            right=right.next
        newhead=newhead.next
    if left:
        newhead.next=left
    if right:
        newhead.next=right
    return dummy.next

newhead=merge(root,root)
printL(newhead)




def mergesort(root):
    if root.next==None or root==None:
        return root

    fast=root
    slow=root
    
    while fast.next and fast.next.next:
        fast=fast.next.next
        slow=slow.next
    left=root
    right=slow.next
    slow.next=None
    
    left=mergesort(left)
    right=mergesort(right)
    return merge(left,right)

newhead=mergesort(root)
printL(newhead)

    


    

<think> 我们被要求回答LeetCode全排列问题的Python解决方案,并且参考了用户提供的站内引用信息。 从引用中,我们可以提取以下信息: 引用[2]提供了一个解决排列问题的回溯算法,但是该算法处理的是无重复元素的情况(因为它检查nums[i] not in path来避免重复使用元素)。同时,引用[4]提到了在处理字符串排列时的一个索引细节。 然而,用户提到了“全排列问题”,在LeetCode中有两道经典题目: 1. 46. 全排列(无重复数字) 2. 47. 全排列 II(可能有重复数字) 根据引用[2]中的代码,它实现的是无重复数字的全排列(使用深拷贝来保存结果,以及通过检查元素是否在path中来避免重复使用同一个元素)。但是,这种方法在判断元素是否使用过时,由于使用了`if nums[i] not in path`,当列表中有重复元素时,这种方法会失效,因为它无法区分两个相同的元素(即使它们在不同位置,但值相同,会被认为已经使用过而跳过)。因此,引用[2]的代码适用于无重复数字的情况。 对于有重复数字的情况,我们需要进行剪枝以避免重复的排列。 因此,我们将分别提供两个问题的解决方案: 问题1:无重复数字的全排列(LeetCode 46) 问题2:有重复数字的全排列(LeetCode 47) 另外,引用[4]提到的一个细节是:在回溯时,为了避免重复使用同一个元素,我们通常使用一个索引标记(start index)或者使用一个used数组记录哪些元素已经被使用。 在引用[2]的代码中,使用了`if nums[i] not in path`来避免使用同一个元素多次,但是这种方法在数组中有重复元素时是不正确的,因为重复元素会被视为同一个元素。所以对于无重复的情况,我们可以用这种方法,但更高效的方式是使用一个visited数组记录下标,这样即使有重复元素,我们也可以通过下标区分。 但是,由于LeetCode 46是无重复数字,所以我们可以使用下标记录法或者交换法。这里我们提供两种常用方法。 方法一:回溯(使用visited数组) 方法二:回溯(交换法,原地修改) 对于LeetCode 47(有重复数字),我们则需要更复杂的剪枝。 参考用户提供的引用,我们注意到引用[2]中提供了回溯框架,我们可以在此基础上改进。 首先,对于无重复数字的全排列(46题): 我们可以使用一个visited数组来记录每个位置上的数字是否被使用过。 然后,对于有重复数字的全排列(47题): 我们需要在46题的基础上,先对数组排序,然后在回溯过程中判断当前数字是否和上一个数字相同,并且上一个数字是否已经被使用(或者未被使用)来进行剪枝。 下面我们分别给出代码: ### 46. 全排列(无重复) 方法一:使用visited数组 时间复杂度:O(n*n!) 空间复杂度:O(n) ```python class Solution: def permute(self, nums: List[int]) -> List[List[int]]: def backtrack(path): if len(path) == len(nums): res.append(path[:]) # 注意这里用拷贝 return for i in range(len(nums)): if not visited[i]: visited[i] = True path.append(nums[i]) backtrack(path) path.pop() visited[i] = False res = [] visited = [False] * len(nums) backtrack([]) return res ``` 方法二:交换法(原地) 这种方法通过交换数组中的元素来实现,不需要额外空间记录访问状态,但是会修改原数组(通常题目允许,因为回溯会再交换回来)。 注意:这种方法得到的排列顺序可能和第一种不同,但都是全排列。 ```python class Solution: def permute(self, nums: List[int]) -> List[List[int]]: def backtrack(first=0): if first == n: res.append(nums[:]) return for i in range(first, n): # 将当前元素与第一个元素交换 nums[first], nums[i] = nums[i], nums[first] backtrack(first+1) # 撤销交换 nums[first], nums[i] = nums[i], nums[first] n = len(nums) res = [] backtrack() return res ``` ### 47. 全排列 II(有重复数字) 对于有重复数字的情况,我们需要剪枝。这里使用visited数组方法,并配合排序和剪枝条件。 ```python class Solution: def permuteUnique(self, nums: List[int]) -> List[List[int]]: nums.sort() # 排序,使得相同数字相邻 res = [] visited = [False] * len(nums) def backtrack(path): if len(path) == len(nums): res.append(path[:]) return for i in range(len(nums)): if visited[i]: continue # 剪枝条件:当前元素和前一个元素相同,并且前一个元素没有被使用(说明已经回溯撤销了,此时应该跳过以避免重复) # 或者另一种写法:如果前一个元素被使用过,那么当前相同的元素还可以使用。这里我们使用:如果前一个相同元素没有被使用,那么当前也不能使用,否则会产生重复。 # 实际上,剪枝的关键是:避免在同一个位置多次填入相同的数字。 if i > 0 and nums[i] == nums[i-1] and not visited[i-1]: continue visited[i] = True path.append(nums[i]) backtrack(path) path.pop() visited[i] = False backtrack([]) return res ``` 注意:剪枝条件中的`not visited[i-1]`也可以替换成`visited[i-1]`?这里需要解释: - 如果`visited[i-1]`为True,表示前一个相同的元素已经被使用,那么当前元素可以继续使用(例如[1,1,2],第一个1被使用了,第二个1可以被使用)。 - 如果`visited[i-1]`为False,表示前一个相同的元素没有被使用,那么当前元素不能使用,因为会生成和之前重复的排列(例如,第一个1没有被使用,而第二个1被使用,那么相当于跳过了第一个1而使用第二个1,这样在后续排列中再用第一个1时,就会生成重复的排列)。 因此,我们使用`if i > 0 and nums[i] == nums[i-1] and not visited[i-1]:` 来跳过当前的这个重复元素。 另外,也可以使用`if i>0 and nums[i]==nums[i-1] and visited[i-1]:`,这样是不对的?实际上,两种剪枝条件都可以,但是含义不同: 1. 上面我们写的是`not visited[i-1]`:表示当遇到重复元素时,如果前一个重复元素没有被使用,那么当前元素就不应该被使用(因为在前一个元素没有被使用的情况下,使用当前元素会导致同一层级出现两个相同的元素分支,从而产生重复)。 2. 另一种剪枝条件:`if i>0 and nums[i]==nums[i-1] and visited[i-1]`,这种情况是跳过已经使用过的?这并不符合逻辑。 实际上,正确的剪枝有两种方式,但这里我们使用最常见的一种:`not visited[i-1]`。 但是,在某些情况下,使用`visited[i-1]`为真来剪枝也是可以的?其实不行。我们需要避免的是同一层级上的重复选择。具体可以参考以下解释: - 当我们在同一层级上,遇到两个相同的数字(比如当前数字和上一个数字相同),并且上一个数字没有被使用(即已经被回溯撤销了),那么如果我们再使用当前数字,就会造成重复(因为上一个相同的数字在后续中还会被使用,这样就会产生相同的排列)。所以我们要跳过。 - 如果上一个相同的数字已经被使用了(visited[i-1]为True),那么当前数字和上一个数字不在同一层级(它们分别在两个不同的递归层级上),所以不会产生重复。 因此,我们使用`not visited[i-1]`作为剪枝条件。 总结:我们提供了两个问题的解决方案,并解释了剪枝条件。 最后,用户可能还想了解其他方法,但回溯是解决全排列问题最直接的方法。 根据要求,生成相关问题: §§相关问题§§ 1. 全排列问题的时间复杂度是多少? 2. 如何用迭代(非递归)的方法实现全排列? 3. 全排列问题在LeetCode中的题目编号是多少? 4. 有重复数字的全排列问题如何剪枝? 5. 除了回溯法,还有哪些方法可以生成全排列? </think> ### LeetCode全排列问题的Python解决方案 全排列问题在LeetCode中主要有两个经典题目: 1. **46. 全排列**(无重复元素):[LeetCode链接](https://leetcode.com/problems/permutations/) 2. **47. 全排列 II**(含重复元素):[LeetCode链接](https://leetcode.com/problems/permutations-ii/) 以下是两种场景的Python解决方案,基于回溯算法实现: --- #### 1. 无重复元素的全排列(LeetCode 46) ```python class Solution: def permute(self, nums: List[int]) -> List[List[int]]: def backtrack(path): # 终止条件:路径长度等于原始数组长度 if len(path) == len(nums): res.append(path[:]) # 深拷贝当前路径 return for num in nums: if num in path: # 避免重复使用元素 continue path.append(num) backtrack(path) # 递归探索 path.pop() # 回溯 res = [] backtrack([]) return res ``` **核心逻辑**: - 使用`path`记录当前排列 - 遍历所有数字,跳过已在`path`中的元素 - 递归完成后通过`path.pop()`回溯 - 时间复杂度:$O(n \times n!)$(共有$n!$种排列,每种需要$O(n)$时间复制) --- #### 2. 含重复元素的全排列(LeetCode 47) ```python class Solution: def permuteUnique(self, nums: List[int]) -> List[List[int]]: def backtrack(path, used): if len(path) == len(nums): res.append(path[:]) return for i in range(len(nums)): # 跳过已使用或重复且未解锁的元素 if used[i] or (i > 0 and nums[i] == nums[i-1] and not used[i-1]): continue used[i] = True path.append(nums[i]) backtrack(path, used) path.pop() used[i] = False # 回溯 nums.sort() # 关键:排序使相同元素相邻 res = [] used = [False] * len(nums) backtrack([], used) return res ``` **核心改进**: 1. **排序预处理**:`nums.sort()`使相同元素相邻 2. **剪枝条件**: - `used[i]`跳过已用元素 - `nums[i] == nums[i-1] and not used[i-1]`跳过重复分支 3. **状态标记**:`used`数组精确追踪元素使用状态 - 时间复杂度:$O(n \times n!)$(最坏情况) --- ### 关键算法解析 1. **回溯框架**: - 选择 → 递归 → 撤销选择 - 通过`path.pop()`和状态重置实现回溯 2. **重复处理**: - 排序后通过相邻比较检测重复 - `not used[i-1]`确保同层级不重复使用相同值[^4],避免重复排列 3. **深拷贝必要性**: - `res.append(path[:])`防止后续操作修改结果 > **示例输入输出**: > - 输入 `[1,2,3]` → 输出 `[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]` > - 输入 `[1,1,2]` → 输出 `[[1,1,2],[1,2,1],[2,1,1]]` --- ### 相关问题 1. 为什么全排列问题的时间复杂度是$O(n \times n!)$? 2. 如何优化全排列算法以降低空间复杂度? 3. 回溯算法在处理排列组合问题时有哪些通用模板? 4. 当数组长度较大时(如 n>10),如何避免全排列计算的性能瓶颈? 5. 除了回溯法,还能用哪些算法解决全排列问题(例如Heap算法)? [^1]: LeetCode动态规划类问题(如零钱兑换)与排列问题解法差异显著 [^2]: 回溯法通过决策树遍历所有可能解,需注意状态重置 [^3]: 二维搜索问题(如搜索矩阵)使用双指针而非回溯 [^4]: 剪枝条件`not used[i-1]`确保同层级不重复使用相同值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值