垃圾小肥羊leetcode刷题记录5

垃圾小肥羊leetcode刷题记录5_第1张图片
我的解法:

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)。


垃圾小肥羊leetcode刷题记录5_第2张图片

大佬解法:

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,这样新括号组合可以由左右两子部分的不同括号组合组合而成。


垃圾小肥羊leetcode刷题记录5_第3张图片
我的解法:

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),为递归时的堆栈空间。


垃圾小肥羊leetcode刷题记录5_第4张图片

大佬解法:

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]右侧的数列依然为递减数列,我们希望该数列取值最小,因此我们将该数列倒转,即为最小的升序数列。


垃圾小肥羊leetcode刷题记录5_第5张图片

大佬解法:

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)。


垃圾小肥羊leetcode刷题记录5_第6张图片
我的解法:

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)。


垃圾小肥羊leetcode刷题记录5_第7张图片

我的解法:

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。


垃圾小肥羊leetcode刷题记录5_第8张图片

我的解法:

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

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


垃圾小肥羊leetcode刷题记录5_第9张图片
我的解法:

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函数外完成第一次排序。为了防止出现重复结果,在遍历选择第一个元素时,必须保证选择的元素与上一个元素不同,即当发现选择的元素与上一个元素相同时,直接跳过以该元素为第一个元素的搜索,直接搜索下一个元素。


垃圾小肥羊leetcode刷题记录5_第10张图片

我的解法:

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),之后再将互换复原。


垃圾小肥羊leetcode刷题记录5_第11张图片
我的解法:

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

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


垃圾小肥羊leetcode刷题记录5_第12张图片

我的解法:

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个元素。我们在做行旋转成列时,需要将行末尾已经丢失的信息替换为字典中记录的正确信息,再进行旋转。


垃圾小肥羊leetcode刷题记录5_第13张图片

我的解法:

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位的字母数量编码,或者将字符串转为排序数组都能解决这个问题。


垃圾小肥羊leetcode刷题记录5_第14张图片
我的解法:

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)。

你可能感兴趣的:(小白羊不太会编程,算法,leetcode,数据结构)