垃圾小肥羊leetcode刷题记录5

    技术2025-05-18  50

    我的解法:

    class Solution: def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: dummyhead = ListNode(None) dummyhead.next = head cur = dummyhead for i in range(n+1): cur = cur.next tail = dummyhead while cur: tail = tail.next cur = cur.next tail.next = tail.next.next return dummyhead.next

    删除某一节点只需将该节点前置节点指向后置节点,为方便操作头节点,加入一个伪头节点。用一领跑指针从伪头节点出发遍历整个链表,用另一个跟随指针记录其之后的第n个节点,该节点即为自领跑指针指向节点开始的倒数第n个元素的前置节点。当前置节点遍历到链表尾,即为None时,跟随指针恰好指向需删除的倒数第n个节点的前置节点,这是即可方便的删除目标节点。时间复杂度O(n)。


    大佬解法:

    class Solution: def generateParenthesis(self, n: int) -> List[str]: ans = [] def dfs(left, right, prefix): if left == 0 and right == 0: ans.append(prefix) return if left > 0: dfs(left-1, right, prefix+'(') if right > left: dfs(left, right-1, prefix+')') dfs(n, n, '') return ans

    使用回溯算法,在保证括号集合一直有效的情况下添加左右括号,即当仍有左括号剩余时可以添加左括号,当右括号数量大于左括号数量时,可以添加右括号。在这种情况下,当剩余左右括号数量均为零时,将结果加入答案中。

    class Solution: def generateParenthesis(self, n: int) -> List[str]: ans = [] if n == 0: return [''] for i in range(n): for left in self.generateParenthesis(i): for right in self.generateParenthesis(n-i-1): ans.append('({}){}'.format(left,right)) return ans

    动态规划法,尝试建立n对括号组合与n-1对括号组合之间的关系。我们把第一个左括号和一个可以移动的右括号看成新增的括号,则右括号把括号组合分为左右两部分([左])[右]。左右两部分括号对之和为n-1,这样新括号组合可以由左右两子部分的不同括号组合组合而成。


    我的解法:

    class Solution: def swapPairs(self, head: ListNode) -> ListNode: if not head or not head.next: return head p1, p2 = head, head.next head = p2 while p2.next and p2.next.next: cur = p1 p1.next = p2.next.next p1 = p2.next p2.next = cur p2 = p1.next p1.next = p2.next p2.next = p1 return head

    用双指针,一个指针p1遍历单数节点,一个指针p2遍历双数节点,同时用指针cur来辅助节点间的链接的更替。利用此方法处理掉所有成对的节点,若最后存在落单的节点,再将结果与最后一个节点相连。时间复杂度O(n),空间复杂度O(n)。

    大佬解法:

    class Solution: def swapPairs(self, head: ListNode) -> ListNode: if not head or not head.next: return head new_head = head.next head.next = self.swapPairs(new_head.next) new_head.next = head return new_head

    迭代算法,每次将两节点调换指向,剩余节点利用迭代方法完成排序。时间复杂度度O(n),空间复杂度O(n),为递归时的堆栈空间。


    大佬解法:

    class Solution: def nextPermutation(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ n, flag = len(nums), False if n <= 1: return nums for i in range(n-1, 0, -1): if nums[i] > nums[i-1]: flag = True break if flag: for j in range(n-1, i-1, -1): if nums[j] > nums[i-1]: break nums[i-1], nums[j] = nums[j], nums[i-1] nums[i:] = nums[i:][::-1] else: nums[:] = nums[::-1]

    首先从后往前遍历,找到需要交换的元素。具体方法为从后往前遍历,找到第一对元素满足nums[i]大于nums[i-1],此时nums[i-1]右侧数列为递减数列,该递减数列已无法增大,因此需要从这个递减数列中找出最小的大于nums[i-1]的数nums[j]与nums[i-1]互换。我们再次从数列末尾往前遍历找到nums[j],将其与nums[i-1]互换后,nums[i-1]右侧的数列依然为递减数列,我们希望该数列取值最小,因此我们将该数列倒转,即为最小的升序数列。


    大佬解法:

    class Solution: def search(self, nums: List[int], target: int) -> int: if not nums: return -1 n = len(nums) left, right = 0, n-1 while left<=right: mid = (left+right)//2 if target == nums[mid]: return mid if nums[left] <= nums[mid]: if (nums[left] <= target) and (target < nums[mid]): right = mid-1 else: left = mid+1 else: if (nums[mid] < target) and (target <= nums[right]): left = mid+1 else: right = mid-1 return -1

    利用二分查找法。因为将旋转排序数组二分为[l,mid]、[mid+1,r]左右两部分时,必有一部分为有序数组。我们可以通过判断nums[l]<=nums[mid]是否成立来判断左区间是否为有序数组,若成立则左区间为有序数组,若不成立则右区间为有序数组,取等号是为了处理当只剩两个元素时l=mid的情况。我们能方便的由有序数组部分判断目标值是否在有序数组内,借此调整左右指针的指向。不断向下进行迭代,先判断哪部分为有序数组,再判断目标值的位置,直到找到目标。时间复杂度O(n)。


    我的解法:

    class Solution: def searchRange(self, nums: List[int], target: int) -> List[int]: n = len(nums) if n < 1: return [-1,-1] left, right = 0, n-1 first, last = -1, -1 while left<=right: mid = (left+right)//2 if nums[mid] > target: right = mid-1 elif nums[mid] < target: left = mid+1 else: right = mid first = right if left == right: break left, right =0, n-1 while left<=right: mid = (left+right+1)//2 if nums[mid] > target: right = mid-1 elif nums[mid] < target: left = mid+1 else: left = mid last = left if left == right: break return [first, last]

    利用二分法分别查找目标元素的第一和最后位置。对于第一个位置,我们将中间数设定为(left+right)//2,这表示当剩余列表中有双数个元素,即中间数有两个时,将选择左侧的中间数。中间数大于或小于目标值时,我们按正常的二分法进行查找,当中间数等于目标值时,不是停止搜索,而是将右指针移动到当前中间值位置继续搜索,同时记录当前第一个目标值位置(right指针位置),由于中间数倾向于选择左侧的元素,到左右指针重合为止,第一个位置将一直向左侧更新。对于最后一个位置,我们将中间数设定为(left+right+1)//2,这表明中间数倾向于选择右侧元素,类似的,我们找到相应中间位置后,将左指针移动过来,继续搜索,直到找到最右侧的目标元素。时间复杂度O(logn)。


    我的解法:

    class Solution: def isValidSudoku(self, board: List[List[str]]) -> bool: rows, columns, blocks = [[] for _ in range(9)], [[] for _ in range(9)], [[[] for _ in range(3)] for _ in range(3)] for i in range(9): for j in range(9): if board[i][j].isdigit(): if board[i][j] not in rows[i]: rows[i].append(board[i][j]) else: return False if board[i][j] not in columns[j]: columns[j].append(board[i][j]) else: return False bi, bj = i//3, j//3 if board[i][j] not in blocks[bi][bj]: blocks[bi][bj].append(board[i][j]) else: return False return True

    创建三个列表分别用来记录各行、各列以及各方块出现的数字,遍历一遍盘面,如果是数字则分别判断它们是否已经在对应的行、列或方块中出现过,如果没有则将其添加进对应列表,如果有则返回False。


    我的解法:

    class Solution: class Solution: def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: if not candidates: return [] ans, n = [], len(candidates) #candidates = sorted(candidates) for i in range(n): if candidates[i]>target: continue dif = target-candidates[i] if dif == 0: ans.append([candidates[i]]) else: for sub_ans in self.combinationSum(candidates[i:], dif): if sub_ans: ans.append(sub_ans+[candidates[i]]) return ans

    递归算法,因为题目中明确数组中无重复元素,因此我们可以不需要对数组进行排序。总体思想为将原问题分解成可迭代的子问题。我们遍历列表中的元素,若元素大于目标值则直接跳过,若小于目标值,则计算与目标值之间的差,将差作为目标值,包括自身的剩余数组作为新的查找数组带回函数递归;如果得到的子结果集不为空,则将子结果集与本元素合并后添加进本层的结果中。排序可能有助于加快搜索速度,因为对于排序数组来说,当以一个数为起始的数组无法搜索到结果时,以比它更大的数为起点的数组也不可能搜索到结果,可以直接截断搜索。


    我的解法:

    class Solution: def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: def helper(candidates, target): if not candidates: return [] n, ans = len(candidates), [] for i in range(n): if candidates[i]>target: continue if i>=1 and candidates[i]==candidates[i-1]: continue if candidates[i]==target: ans.append([candidates[i]]) else: dif = target-candidates[i] if i < n-1: for sub_ans in helper(candidates[i+1:], dif): if sub_ans: ans.append([candidates[i]]+sub_ans) else: break return ans candidates = sorted(candidates) ans = helper(candidates, target) return ans

    与上一题解法类似,不同的是由于列表中可能存在相同的元素,故需要考虑到重复结果的问题。需要首先对列表进行排序,因为之后要递归调用原函数,而排序只需对原始列表进行一次即可,递归中不需要再次排序,故定义一个helper函数实现所需的结果查找功能,而在helper函数外完成第一次排序。为了防止出现重复结果,在遍历选择第一个元素时,必须保证选择的元素与上一个元素不同,即当发现选择的元素与上一个元素相同时,直接跳过以该元素为第一个元素的搜索,直接搜索下一个元素。


    我的解法:

    class Solution: def permute(self, nums: List[int]) -> List[List[int]]: if not nums: return [] n = len(nums) if n == 1: return [nums] ans = [] for i in range(n): if i < n-1: for sub_ans in self.permute(nums[:i]+nums[i+1:]): ans.append([nums[i]]+sub_ans) else: for sub_ans in self.permute(nums[:i]): ans.append([nums[i]]+sub_ans) return ans

    运用递归算法,从列表中取出一个数字,那么列表中剩余数字仍然面临全排列的问题,因此可以迭代求解。需要注意的是如何表示除一个元素外的剩余数组。对于最后一个元素而言,可以表示为nuns[:i],对于其他元素则表示为nums[:i]+nums[i+1:]。

    大佬解法:

    class Solution: def permute(self, nums: List[int]) -> List[List[int]]: n = len(nums) def backtrack(depth=0): if depth == n: ans.append(nums[:]) for i in range(depth,n): nums[depth], nums[i] = nums[i], nums[depth] backtrack(depth+1) nums[i], nums[depth] = nums[depth], nums[i] ans = [] backtrack() return ans

    回溯搜索算法,定义backtrack函数,函数参数depth为当前正在排列的深度,若深度等于列表长度n则将结果记入结果列表中,注意因为之后需要对同一列表进行回溯操作,这里要使用拷贝nums[:],不然最后所有结果都会变回原数组的样子。对于nums和depth,depth左边的树都已经使用过,我们遍历选择depth及其右边的一个数作为depth的数,对于depth+1层,因为要确保使用过的数位于左侧,我们将num[depth]与选择的nums[i]互换,继续递归使用backtrack(depth+1),之后再将互换复原。


    我的解法:

    class Solution: def permuteUnique(self, nums: List[int]) -> List[List[int]]: n = len(nums) def backtrack(depth=0): if depth==n: ans.append(nums[:]) showed = {} for i in range(depth,n): if showed.get(nums[i]): continue else: showed[nums[i]] = 1 nums[i], nums[depth] = nums[depth], nums[i] backtrack(depth+1) nums[depth], nums[i] = nums[i], nums[depth] ans = [] backtrack() return ans

    同样是回溯算法,但是引入剪枝,对于同一层,记录已经出现过的数字,若遍历到之前出现过的数字,就直接跳过。


    我的解法:

    class Solution: def rotate(self, matrix: List[List[int]]) -> None: """ Do not return anything, modify matrix in-place instead. """ n = len(matrix) store = {i:[] for i in range(n)} for i in range(n): for k in range(i+1,n): store[k] = [matrix[k][n-1-i]]+store[k] to_be_added = matrix[i][:n-i]+store[i] for j in range(n): matrix[j][n-1-i] = to_be_added[j]

    首先根据观察,发现第i行会转换到第n-i-1列,那么我们从第一行开始转换。需要注意的是,如果我们从前往后的将行元素转换到旋转后的列时,行中靠后的元素可能会被新的列元素替换,造成信息丢失,因此我们要创建一个store字典来记录每一行丢失的信息,第i行丢失的信息为其最后i个元素。我们在做行旋转成列时,需要将行末尾已经丢失的信息替换为字典中记录的正确信息,再进行旋转。


    我的解法:

    class Solution: def groupAnagrams(self, strs: List[str]) -> List[List[str]]: ans = {} for s in strs: letter = [0]*26 for si in s: letter[ord(si)-ord('a')] += 1 letter = tuple(letter) if not ans.get(letter): ans[letter] = [s] else: ans[letter].append(s) return list(ans.values())

    将字符串转化为26位的字母数量编码,或者将字符串转为排序数组都能解决这个问题。


    我的解法:

    class Solution: def myPow(self, x: float, n: int) -> float: ans_set = {1:x} def pow_(x,n): if ans_set.get(n): return ans_set[n] else: ans = pow_(x,n//2)*pow_(x,n-n//2) ans_set[n] = ans return ans return pow(x,n)

    第一反应是这不就是x**n吗,然后想到这里面会有很多重复运算,比如24相当于小22x22,即22计算了两次。因此我们利用一个字典来储存已经计算过的次方,将原高次方逐渐分解为低次方计算。考虑到奇次方的情况,我们把原次方分解为n//2和n-n//2,递归求解即可得到答案,时间复杂度O(logn)。

    Processed: 0.012, SQL: 9