《算法通关之路》-chapter18融会贯通

《算法通关之路》学习笔记,记录一下自己的刷题过程,详细的内容请大家购买作者的书籍查阅。

循环移位问题

轮转数组

力扣第189题
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

'''
方法一:复制相同的数组

时间复杂度:O(n)
空间复杂度:O(n)
'''

class Solution:
    def rotate(self, nums: list[int], k: int) -> None:

        copy = nums.copy()
        n = len(nums)

        for i in range(n):
            nums[(k+i)%n] = copy[i]

nums, k = [1,2,3,4,5,6,7], 3
print(nums)
solu = Solution()
solu.rotate(nums, k)
print(nums)
[1, 2, 3, 4, 5, 6, 7]
[5, 6, 7, 1, 2, 3, 4]
'''
方法二:时间换空间(超时)

时间复杂度:O(nk)
空间复杂度:O(1)
'''

class Solution:
    def rotate(self, nums: list[int], k: int) -> None:

        n = len(nums)
        t = None
        offset = n - k % n # 右移变左移
        if offset == 0:
            return
        while offset:
            t = nums[0]
            offset -= 1
            for i in range(n-1):
                nums[i] = nums[i+1]
            nums[n-1] = t

nums, k = [1,2,3,4,5,6,7], 3
print(nums)
solu = Solution()
solu.rotate(nums, k)
print(nums)
[1, 2, 3, 4, 5, 6, 7]
[5, 6, 7, 1, 2, 3, 4]
'''
方法三:空间换时间(不符合题意)

时间复杂度:O(n)
空间复杂度:O(n)
'''

class Solution:
    def rotate(self, nums: list[int], k: int) -> None:

        n = len(nums)
        offset = k % n
        nums = nums + nums.copy()
        return nums[n - offset : n * 2 - offset]

nums, k = [1,2,3,4,5,6,7], 3
print(nums)
solu = Solution()
solu.rotate(nums, k)
[1, 2, 3, 4, 5, 6, 7]
[5, 6, 7, 1, 2, 3, 4]
'''
方法四:利用python迭代器

时间复杂度:O(k)
空间复杂度:O(1)
'''

class Solution:
    def rotate(self, nums: list[int], k: int) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        for i in range(0, -k, -1):
            nums.insert(0,nums.pop())

nums, k = [1,2,3,4,5,6,7], 3
print(nums)
solu = Solution()
solu.rotate(nums, k)
print(nums)
[1, 2, 3, 4, 5, 6, 7]
[5, 6, 7, 1, 2, 3, 4]
'''
方法五:三次翻转法

时间复杂度:O(k)
空间复杂度:O(1)
'''

class Solution:
    def rotate(self, nums: list[int], k: int) -> None:

        def reverse(list: list[int], start: int, end: int) -> None:
            
            while start < end:
                t = list[start]
                list[start] = list[end]
                list[end] = t
                start += 1
                end -= 1
        
        n = len(nums)
        offset = k % n
        if offset == 0:
            return

        reverse(nums, 0, n - offset - 1)
        reverse(nums, n - offset, n - 1)
        reverse(nums, 0, n - 1)

nums, k = [1,2,3,4,5,6,7], 3
print(nums)
solu = Solution()
solu.rotate(nums, k)
print(nums)
[1, 2, 3, 4, 5, 6, 7]
[5, 6, 7, 1, 2, 3, 4]

旋转链表

力扣第61题
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

'''
方法一:右移变左移

时间复杂度:O(n)
空间复杂度:O(1)
'''

from typing import Optional

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
        
class Solution:
    def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:

        if not head or k == 0 or not head.next:
            return head

        p1 = head
        res = None
        n = 1

        while p1 and p1.next:
            p1 = p1.next
            n += 1
        
        cur  = 1 # 通过cur计数来寻找断点
        p2 = head
        while cur < n - k % n:
            p2 = p2.next
            cur += 1
        
        p1.next = head
        res = p2.next
        p2.next = None

        return res

编辑距离

编辑距离

力扣第72题
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符

'''
方法一:记忆化递归

空间复杂度:O(mn)
'''

class Solution:
    def minDistance(self, s1: str, s2: str) -> int:

        def find_min(s1, i, s2, j, memo):
            if i == -1:
                return j + 1
            if j == -1:
                return i + 1

            if memo[i][j] != -1:
                return memo[i][j]

            if s1[i] == s2[j]:
                memo[i][j] = find_min(s1, i - 1, s2, j - 1, memo)
            else:
                memo[i][j] = min(
                    find_min(s1, i, s2, j - 1, memo) + 1,  # 插入
                    find_min(s1, i - 1, s2, j, memo) + 1,  # 删除
                    find_min(s1, i - 1, s2, j - 1, memo) + 1  # 替换
                )
            return memo[i][j]

        m, n = len(s1), len(s2)
        memo = [[-1] * n for _ in range(m)]
        res = find_min(s1, m - 1, s2, n - 1, memo)
        return res

word1, word2 = "horse", "ros"
solu = Solution()
solu.minDistance(word1, word2)
3
'''
方法二:动态规划

时间复杂度:O(mn)
空间复杂度:O(mn)
'''

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:

        m, n = len(word1), len(word2)

        # dp[i][j]表示字符串word[:i]和word[:j]的最小编辑距离
        dp = [[0 for j in range(n + 1)] for i in range(m + 1)]

        for i in range(1, m + 1):
            dp[i][0] = i
        for j in range(1, n + 1):
            dp[0][j] = j
        
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if word1[i - 1] == word2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1]
                else:
                    dp[i][j] = min(dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]) + 1
        
        return dp[m][n]

word1, word2 = "horse", "ros"
solu = Solution()
solu.minDistance(word1, word2)
3
'''
方法三:动态规划(空间优化)

时间复杂度:O(mn)
空间复杂度:O(n)
'''

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:

        m, n = len(word1), len(word2)
        pre, cur = [0] * (n + 1), [0] * (n + 1)

        for i in range(1, n + 1):
            pre[i] = i
        for i in range(1, m + 1):
            cur[0] = i
            for j in range(1, n + 1):
                if word1[i - 1] == word2[j - 1]:
                    cur[j] = pre[j - 1]
                else:
                    cur[j] = min(pre[j], pre[j - 1], cur[j - 1]) + 1
            pre = cur.copy()
        
        return pre[n] # 解决没有进入循环的情况

word1, word2 = "horse", "ros"
solu = Solution()
solu.minDistance(word1, word2)
3
'''
方法四:动态规划(继续优化空间)

时间复杂度:O(mn)
空间复杂度:O(n)
'''

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:

        m, n = len(word1), len(word2)
        cur = [0] * (n + 1)
        pre = None

        for i in range(1, n + 1):
            cur[i] = i
        for i in range(1, m + 1):
            pre = cur[0]
            cur[0] = i
            for j in range(1, n + 1):
                tmp = cur[j]
                if word1[i - 1] == word2[j - 1]:
                    cur[j] = pre
                else:
                    cur[j] = min(cur[j], cur[j - 1], pre) + 1
                pre = tmp
        
        return cur[n]

word1, word2 = "horse", "ros"
solu = Solution()
solu.minDistance(word1, word2)
3

第k问题

数组中的第K个最大元素

力扣第215题
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

'''
方法一:堆排序(调包)

时间复杂度:O(nlogk)
空间复杂度:O(k)
'''

from heapq import nlargest

class Solution:
    def findKthLargest(self, nums: list[int], k: int) -> int:

        # [3,2,1,5,6,4]
        # [6, 5]
        
        return nlargest(k, nums)[-1] 

nums, k = [3,2,1,5,6,4], 2
solu = Solution()
solu.findKthLargest(nums, k)
5

其他解法请参考:
《算法通关之路》-chapter13分治法

数据流中的第 K 大元素

力扣第703题
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

'''
方法一:堆排序(调包)

时间复杂度:O(nlogk)
空间复杂度:O(k)
'''

import heapq

class KthLargest:

    def __init__(self, k: int, nums: list[int]):

        # len(nums) >= k-1,通过加一个元素保证查找k大元素时,数组中至少有k个元素
        self.nums = heapq.nlargest(k, nums + [float('-inf')])
        self.k = k
        heapq.heapify(self.nums)

    def add(self, val: int) -> int:
        heapq.heappushpop(self.nums, val)
        return self.nums[0]

kth_largest =  KthLargest(3, [4, 5, 8, 2])
kth_largest.add(3)   # return 4
kth_largest.add(5)   # return 5
kth_largest.add(10)  # return 5
kth_largest.add(9)   # return 8
kth_largest.add(4)   # return 8
8

二叉搜索树中第K小的元素

力扣第230题
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。

'''
方法一:二分查找

时间复杂度:O(n),最坏O(n2)
空间复杂度:O(n),最坏O(n2)
'''

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
        
        # 返回该节点及所有子节点的个数
        def countNodes(node) -> int:
            if node == None:
                return 0
            l = countNodes(node.left)
            r = countNodes(node.right)
            return l + r + 1

        cnt = countNodes(root.left)
        if cnt == k - 1:
            return root.val
        elif cnt > k - 1:
            return self.kthSmallest(root.left, k)
        
        return self.kthSmallest(root.right, k - cnt - 1)

有序矩阵中第 K 小的元素

力扣第378题
给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。

你必须找到一个内存复杂度优于 O(n2) 的解决方案。

'''
方法一:二分查找

时间复杂度:O(nlog(hi-lo))
空间复杂度:O(1)
'''

class Solution:
    def kthSmallest(self, matrix: list[list[int]], k: int) -> int:

        n = len(matrix)
        lo, hi = matrix[0][0], matrix[n - 1][n - 1]

        def countNotGreater(target: int) -> int:
            
            i, j = 0, n - 1
            cnt = 0
            while i < n and j >= 0:
                if matrix[i][j] <= target:
                    cnt += j + 1
                    i += 1
                else:
                    j -= 1
            return cnt
        
        while lo < hi:
            mid = (lo + hi) // 2
            cnt = countNotGreater(mid)

            if cnt < k:
                lo = mid + 1
            else:
                hi = mid
        
        return lo

matrix, k = [[1,5,9],[10,11,13],[12,13,15]], 8
solu = Solution()
solu.kthSmallest(matrix, k)
13

乘法表中第k小的数

力扣第668题
几乎每一个人都用 乘法表。但是你能在乘法表中快速找到第 k 小的数字吗?

'''
方法一:二分查找

时间复杂度:O(mlogmn)
空间复杂度:O(1)
'''

class Solution:
    def findKthNumber(self, m: int, n: int, k: int) -> int:
        
        lo, hi = 1, m * n

        def countNotGreater(mid: int, m: int, n: int) -> int:
            
            cnt = 0
            for i in range(1, m + 1):
                cnt += min(mid // i, n)
                
            return cnt
        
        while lo < hi:
            mid = (lo + hi) // 2
            if countNotGreater(mid, m, n) < k:
                lo = mid + 1
            else:
                hi = mid
        
        return lo

m, n, k = 3, 3, 5
solu = Solution()
solu.findKthNumber(m, n, k)
3

你可能感兴趣的:(#,《算法通关之路》学习笔记,算法,python,leetcode,数据结构,动态规划)