《算法通关之路》-数学之美

数学之美

例题1:两数之和

两数之和:leetcode链接

思路一:暴力破解,两个for循环。遍历每一种可能。

def twoSum(nums: List[int], target: int) -> List[int]:
    ans = []
    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            if nums[i] + nums[j] == target:
                ans.append(i)
                ans.append(j)
                return ans
    return ans

执行用时:3692 ms, 在所有 Python3 提交中击败了7.98%的用户

内存消耗:15.5 MB, 在所有 Python3 提交中击败了68.43%的用户

时间复杂度: O ( n 2 ) O(n^2) O(n2)

空间复杂度: O ( 1 ) O(1) O(1)


思路二:排序加双指针

def twoSum(nums: List[int], target: int) -> List[int]:
    nums = sorted(nums)  # 排序
    left_ptr = 0  # 左指针指向第一个元素
    right_ptr = len(nums) - 1  # 右指针指向第最后一个元素
    ans = []
    while left_ptr < right_ptr: # 结束条件
        temp_num = nums[left_ptr] + nums[right_ptr]
        if temp_num == target:
            ans = [left_ptr, right_ptr]
            break
        elif temp_num > target:# 单调性
            right_ptr -= 1
        else:
            left_ptr += 1
    return ans

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

不过解法不适用于该题,因为排序会改变下标。


思路3:空间换时间

使用hash表,每遍历一个元素,就判断是否有target-nums[i]存在。如果存在,返回下标,否则存入hash表。

def twoSum(nums: List[int], target: int) -> List[int]:
    ans = []
    hashmap = {}
    for i in range(len(nums)):
        if (target - nums[i]) in hashmap:
            ans = [i, hashmap[target - nums[i]]]
        else:
            hashmap[nums[i]] = i
    return ans

执行用时:36 ms, 在所有 Python3 提交中击败了84.01%的用户

内存消耗:16.1 MB, 在所有 Python3 提交中击败了13.49%的用户

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

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

例题2:三数之和

三数之和:leetcode链接

此题与上题的差异:

  • 3数之和等于0
  • 返回数据本身,不是索引值
  • 存在多个答案,全都需要返回
  • 需要去重

思路:外层循环访问每个元素,内层循环使用双指针找到满足条件的数。内层去重判断左界和右界是否和下一位置重复,去除重复解,并同时将 左右指针移到下一位置,寻找新的解。

def threeSum(nums: List[int]) -> List[List[int]]:
    nums.sort()
    n = len(nums)
    ans = []
    #外层循环遍历每个元素,对于最后一组情况应该到n-3。
    for i in range(n - 2):  # 找到第n-3个数
        # 去重
        if i > 0 and nums[i] == nums[i - 1]:# 需要和上次枚举的数不同
            continue
        left_ptr = i + 1
        right_ptr = n - 1
        # 内层循环使用双指针找符合条件的数。
        while left_ptr < right_ptr:
            temp_num = nums[left_ptr] + nums[right_ptr] + nums[i]
            if temp_num == 0:
                ans.append([nums[i], nums[left_ptr], nums[right_ptr]])
                # 去重
                while left_ptr < right_ptr and nums[left_ptr] == nums[left_ptr + 1]:
                    left_ptr += 1
                while left_ptr < right_ptr and nums[right_ptr] == nums[right_ptr - 1]:
                    right_ptr -= 1
                left_ptr += 1
                right_ptr -= 1
            elif temp_num > 0:#单调性
                right_ptr -= 1
            else:#单调性
                left_ptr += 1
    return ans

执行用时:628 ms, 在所有 Python3 提交中击败了74.97%的用户

内存消耗:17.8 MB, 在所有 Python3 提交中击败了58.05%的用户

时间复杂度: O ( n 2 ) O(n^2) O(n2)

例题3:四数之和

四数之和:leetcode链接

要求四个数字之和与目标值相等。

思路一:回溯法,遍历所有可能。此思路本题会超时。

def backtrack(res: List[List[int]], nums: List[int], n: int, tempList: List[int], target: int, start: int,
              hashmap: dict):
    # 长度超过4退出
    if (len(tempList) > 4):
        return
    #判断是否符合条件
    if len(tempList) == 4 and sum(tempList) == target:
        if str(tempList) in hashmap:
            # 去重
            return
        else:
            hashmap[str(tempList)] = True
            return res.append(tempList.copy())
    #回溯。。
    for i in range(start, n):
        # 剪枝
        if len(tempList) == 3 and sum(tempList) + nums[i] != target:
            continue
        tempList.append(nums[i])
        backtrack(res, nums, n, tempList, target, i + 1, hashmap)
        tempList.pop()


def fourSum(nums: List[int], target: int) -> List[List[int]]:
    res = []
    hashmap = {}
    nums.sort()
    backtrack(res, nums, len(nums), [], target, 0, hashmap)
    return res

时间复杂度: O ( n 4 ) O(n^4) O(n4)

空间复杂度: O ( n 4 ) O(n^4) O(n4)


思路二:排序 + 双指针

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        quadruplets = list()
        if not nums or len(nums) < 4:
            return quadruplets

        nums.sort()
        length = len(nums)
        for i in range(length - 3):
            # 去重
            if i > 0 and nums[i] == nums[i - 1]:
                continue
            # 不满足条件
            if nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target:
                break
            # 剪枝
            if nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target:
                continue
            # 第二重循环
            for j in range(i + 1, length - 2):
                # 去重
                if j > i + 1 and nums[j] == nums[j - 1]:
                    continue
                # 不满足条件
                if nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target:
                    break
                # 剪枝
                if nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target:
                    continue
                left, right = j + 1, length - 1
                # 双指针
                while left < right:
                    total = nums[i] + nums[j] + nums[left] + nums[right]
                    if total == target:
                        quadruplets.append([nums[i], nums[j], nums[left], nums[right]])
                        while left < right and nums[left] == nums[left + 1]:
                            left += 1
                        left += 1
                        while left < right and nums[right] == nums[right - 1]:
                            right -= 1
                        right -= 1
                    elif total < target:
                        left += 1
                    else:
                        right -= 1

        return quadruplets

执行用时:72 ms, 在所有 Python3 提交中击败了91.05%的用户

内存消耗:15.1 MB, 在所有 Python3 提交中击败了40.67%的用户

时间复杂度: O ( n 3 ) O(n^3) O(n3)

空间复杂度: O ( 1 ) O(1) O(1)

例题4:最接近的三数之和

最接近的三数之和:leetcode链接

思路:和三数之和类似,只是找的是与target只差绝对值最小的数。

class Solution:
    def threeSumClosest(self,nums: List[int], target: int) -> int:
        nums.sort()
        n = len(nums)
        res = nums[0] + nums[1] + nums[2]  # 最小三个数之和
        # 外层循环遍历每个元素,对于最后一组情况应该到n-3。
        for i in range(n - 2):  # 找到第n-3个数
            # 去重
            if i > 0 and nums[i] == nums[i - 1]:  # 需要和上次枚举的数不同
                continue
            left_ptr = i + 1
            right_ptr = n - 1
            # 内层循环使用双指针找符合条件的数。
            while left_ptr < right_ptr:
                temp_num = nums[left_ptr] + nums[right_ptr] + nums[i]
                if temp_num == target:
                    return temp_num
                if abs(temp_num - target) < abs(res - target):
                    res = temp_num
                if temp_num > target:  # 单调性
                    right_ptr -= 1
                elif temp_num < target:  # 单调性
                    left_ptr += 1
        return res

执行用时:264 ms, 在所有 Python3 提交中击败了23.96%的用户

内存消耗:15.1 MB, 在所有 Python3 提交中击败了38.14%的用户

时间复杂度: O ( n 2 ) O(n^2) O(n2)

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

例题5:最大子序列和

最大子序列和:leetcode链接

思路一:暴力破解,遍历每一种子序列

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        ans = -math.inf
        n = len(nums)
        total = 0
        for i in range(n):
            total = 0
            for j in range(i, n):
                total += nums[j]
                ans = max(total * 1.0, ans)
        return int(ans)

超时

时间复杂度: O ( n 2 ) O(n^2) O(n2)

空间复杂度: O ( 1 ) O(1) O(1)


思路二:前缀和

若有 s [ i ] = s [ 0 ] + s [ 1 ] + . . . + s [ i ] s[i] = s[0] + s[1] + ...+s[i] s[i]=s[0]+s[1]+...+s[i],则 s [ j ] − s [ i ] s[j] - s[i] s[j]s[i]就是i到j的序列和。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        maxSum = nums[0]
        minSum = sum = 0
        for i in range(n):
            sum += nums[i]
            maxSum = max(maxSum, sum - minSum)
            minSum = min(minSum, sum)
        return maxSum

思路二:动态规划,

dp[i] 表示在位置i的最长的子序列

d p [ i ] = m a x ( d p [ i − 1 ] , n u m s [ i ] ) dp[i] = max(dp[i-1],nums[i]) dp[i]=max(dp[i1],nums[i])

用一个变量记录最大值。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        dp = [0 for i in range(n + 1)]
        dp[0] = nums[0]
        ans = dp[0]
        for i in range(1, n):
            dp[i] = max(dp[i - 1] + nums[i], nums[i])
            ans = max(dp[i], ans)
        return ans

执行用时:264 ms, 在所有 Python3 提交中击败了6.93%的用户

内存消耗:25.7 MB, 在所有 Python3 提交中击败了52.24%的用户

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

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

实际上不需要所有的值,所以可以不需要dp数组

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        dp = nums[0]
        ans = dp
        for i in range(1, n):
            dp = max(dp + nums[i], nums[i])
            ans = max(dp, ans)
        return ans

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

空间复杂度: O ( 1 ) O(1) O(1)


思路3:分治

每次将区间平均分成两部分,最大序列可能有3中情况。

情况1:左序列。

情况2:右序列。

情况3:保护中间元素的序列。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        return self.helper(nums, 0, len(nums) - 1)

    def helper(self, nums: List[int], start: int, end: int) -> int:
        if start > end:
            return float("-inf")
        mid = start + (end - start) // 2
        ## 左序列的值
        left_value = self.helper(nums, start, mid - 1)
        ## 右序列的值
        right_value = self.helper(nums, mid + 1, end)
        left_max_value = right_max_value = 0
        ## 跨越中间的情况
        total = 0
        # 在区间[start,mid-1]中的最大值,倒叙检查正序都可以。
        for i in reversed(range(start, mid)):
            total += nums[i]
            left_max_value = max(left_max_value, total)
        total = 0
        # 在区间[mid+1,end]中的最大值。
        for i in range(mid + 1, end + 1):
            total += nums[i]
            right_max_value = max(right_max_value, total)
        # 跨越中间情况下的值
        cross_value = left_max_value + right_max_value + nums[mid]
        # 返回三种情况的最大值
        return max(left_value, right_value, cross_value)

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

空间复杂度: O ( l o g n ) O(logn) O(logn)

例题6:最大数

最大数:leetcode链接

思路:将数转为字符串,自定义比较函数。

class Solution:
    def largestNumber(self, nums: List[int]) -> str:
        num_list = [str(e) for e in nums]

        def compare(a, b):
            if a + b > b + a:
                return 1
            elif a + b < b + a:
                return -1
            else:
                return 0

        num_list.sort(key=functools.cmp_to_key(compare), reverse=True)
        return "".join(num_list) if num_list[0] != '0' else '0'

执行用时:44 ms, 在所有 Python3 提交中击败了54.94%的用户

内存消耗:15 MB, 在所有 Python3 提交中击败了30.39%的用户

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

空间复杂度: O ( l o g n ) O(logn) O(logn)

例题7:分到小数

分到小数:leetcode链接

模拟手算乘法,当余数没有出现循环时,则余数*10继续运算,否则结束运算。

class Solution:
    def fractionToDecimal(self, numerator: int, denominator: int) -> str:
        sign = ""
        # 符号
        if numerator // denominator < 0:
            sign = "-"
        # 处理整数部分
        n, remainder = divmod(abs(numerator), abs(denominator))
        hashtable = []
        ans = [str(n), "."]
        # 如果remainder 不在hash表里说明没有循环。
        while remainder not in hashtable:
            # 将remainder 加入hashtable,数组实现,下标表示出现循环的起始位置。
            hashtable.append(remainder)
            # remainder 乘 10
            n, remainder = divmod(remainder * 10, abs(denominator))
            # 整数部分放入结果。
            ans.append(str(n))
        # 获取循环开始的位置
        index = hashtable.index(remainder)
        # 拼接(
        ans.insert(index + 2, "(")
        ans.append(")")
        # 如果循环是(0)则去掉,如果右侧是.则去掉。
        return sign + "".join(ans).replace("(0)", '').rstrip(".")

执行用时:44 ms, 在所有 Python3 提交中击败了15.12%的用户

内存消耗:15 MB, 在所有 Python3 提交中击败了81.03%的用户

时间复杂度: O ( d e m o n i a t o r ) O(demoniator) O(demoniator)

空间复杂度: O ( d e m o n i a t o r ) O(demoniator) O(demoniator)

例题8:最大整除子集

最大整除子集:leetcode链接

子集个数有 2 n 2^n 2n个,暴力不行。

  • 如果要满足题意,即集合S中任意一对数 ( s j , s i ) (s_j,s_i) (sj,si)都有, s j % s i = 0 s_j\%s_i =0 sj%si=0,或者 s i % s j = 0 s_i\%s_j = 0 si%sj=0

  • 如果是排好序的。则只需要有最大元素与前一个能整除即可。

  • 排序后,如果用map存储每个元素最大的子集情况,key表示子集中最大的值,value表示子集。

    • 如果某元素e能够e%key == 0,则存入map[key],选择每种满足情况的集合中长度最大的,保存为map[e]
    • 否则不动
class Solution:
    def largestDivisibleSubset(self, nums: List[int]) -> List[int]:
        nums.sort()
        # 声明一个hashmap,key表示子集中最大的元素,value表示子集
        # 初始状态为{-1:set()}
        hashmap = {-1: set()}
        # 遍历数组中的每一个元素。
        for e in nums:
            # 用来保存容纳e的子集。
            temp = []
            # hashmap的key是子集中最大的元素
            for key in hashmap:
                if e % key == 0:
                    # 存入子集
                    hashmap[key].add(e)
                    # 存入temp
                    temp.append(hashmap[key])
                    # 移除子集
                    hashmap[key].remove(e)
            # 表示当前元素能够构建的最大子集
            hashmap[e] = max(temp, key=len) | {e}
        return list(max(hashmap.values(), key=len))

执行用时:248 ms, 在所有 Python3 提交中击败了91.16%的用户

内存消耗:16.1 MB, 在所有 Python3 提交中击败了7.97%的用户

时间复杂度: O ( n 2 ) O(n^2) O(n2)

空间复杂度: O ( n 2 ) O(n^2) O(n2)

思路二:动态规划

class Solution:
    def largestDivisibleSubset(self, nums: List[int]) -> List[int]:
        nums.sort()
        n = len(nums)
        f, g = [0] * n, [0] * n
        for i in range(n):
            # 至少包含自身一个数,因此起始长度为 1,由自身转移而来
            length, prev = 1, i
            for j in range(i):
                if nums[i] % nums[j] == 0:
                    # 如果能接在更长的序列后面,则更新「最大长度」&「从何转移而来」
                    if f[j] + 1 > length:
                        length = f[j] + 1
                        prev = j
            # 记录「最终长度」&「从何转移而来」
            f[i] = length
            g[i] = prev

        # 遍历所有的 f[i],取得「最大长度」和「对应下标」
        max_len = idx = -1
        for i in range(n):
            if f[i] > max_len:
                idx = i
                max_len = f[i]
        
        # 使用 g[] 数组回溯出具体方案
        ans = []
        while len(ans) < max_len:
            ans.append(nums[idx])
            idx = g[idx]
        ans.reverse()
        return ans
 

执行用时:292 ms, 在所有 Python3 提交中击败了84.40%的用户

内存消耗:15 MB, 在所有 Python3 提交中击败了94.28%的用户

时间复杂度: O ( n 2 ) O(n^2) O(n2)

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

例题9: 质数排序

质数排序:leetcode链接

n为100,可以提前算出素数范围表。

排列数,素数在素数索引全排,合数在合适索引全排。若n范围内的素数个数为m,结果为 m ! × ( n − m ) ! m!\times(n-m)! m!×(nm)!

class Solution:
    def numPrimeArrangements(self, n: int) -> int:
        dp = [0, 0, 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25]
        prime_arrange = math.factorial(dp[n])
        non_prime_arrange = math.factorial(n - dp[n])
        p = 10 ** 9 + 7
        return prime_arrange * non_prime_arrange % p

执行用时:36 ms, 在所有 Python3 提交中击败了68.12%的用户

内存消耗:14.9 MB, 在所有 Python3 提交中击败了75.11%的用户

时间复杂度: O ( 1 ) O(1) O(1)

空间复杂度: O ( 1 ) O(1) O(1)

优化空间:阶乘乘法,改为快速模乘。

class Solution:
    # n < 100
    def __is_prime(self, n: int) -> bool:
        i = 0
        if n == 1:
            return False
        prime_table = [2, 3, 5, 7]
        flag = True
        for e in prime_table:
            if n % e == 0:
                flag = False
                break
        return flag

    def __get_prime_table(self, n: int) -> List[int]:
        prime_table = [2, 3, 5, 7]
        for i in range(n + 1):
            if self.__is_prime(i):
                prime_table.append(i)
        return prime_table

    def numPrimeArrangements(self, n: int) -> int:
        # prime_table = self.__get_prime_table(n)
        # dp = [0 for i in range(n + 1)]
        # dp[0] = 0
        # for i in range(n + 1):
        #     if i in prime_table:
        #         dp[i] = dp[i - 1] + 1
        #     else:
        #         dp[i] = dp[i - 1]
        # print(dp)
        dp = [0, 0, 1, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 10, 10, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 17, 17, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25]
        prime_arrange = math.factorial(dp[n])
        non_prime_arrange = math.factorial(n - dp[n])
        p = 10 ** 9 + 7
        mul = prime_arrange * non_prime_arrange % p
        return mul

你可能感兴趣的:(算法,leetcode,算法,蓝桥杯)