• [刷题] Leetcode算法 (2020-2-26)


    1.搜索插入位置

    题目:

    给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

    你可以假设数组中无重复元素。

    示例:

    输入: [1,3,5,6], 5
    输出: 2
    
    输入: [1,3,5,6], 2
    输出: 1
    
    输入: [1,3,5,6], 7
    输出: 4
    
    输入: [1,3,5,6], 0
    输出: 0

    代码:

    class Solution:
        def searchInsert(self, nums: List[int], target: int) -> int:
            # lo表示开始指针
            lo = 0
            # hi表示结束指针
            hi = len(nums)
            # 如果列表还有值
            while lo < hi:
                # 找到中心元素的索引
                mid = (lo+hi)//2
                # 比较中心元素与target的大小,如果target更大,则target属于右边部分,这是应该移动lo到mid+1
                if nums[mid] < target: lo = mid+1
                # 反之,如果target小于中心元素,则属于左边部分,应该移动hi到mid位置
                else: hi = mid
            # 最终返回lo即是target存在或应该插入的位置
            return lo

    这是使用二分查找法来寻找应该插入的位置( 时间复杂度O(logn) )。这个源码很有学习的价值。如果如要真正插入元素,则只需要在return之前使用nums.insert(lo, target)即可。

    总结:

    # 这是非常典型的二分查找问题,使用前后指针来逐步缩小列表范围。最后得到插入的索引
    # 二分查找只能应用于有序列表(反序的话需要修改以下代码中的逻辑)。
    # 二分查找法的时间复杂度为O(logn),空间复杂度为O(1)

    2.外观数列

    题目:

    「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:

    1. 1
    2. 11
    3. 21
    4. 1211
    5. 111221
    6. 312211

    1 被读作  "one 1"  ("一个一") , 即 11。
    11 被读作 "two 1s" ("两个一"), 即 21。
    21 被读作 "one 2",  "one 1" ("一个二" ,  "一个一") , 即 1211。

    给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。

    注意:整数序列中的每一项将表示为一个字符串。

    示例:

    输入: 1
    输出: "1"
    解释:这是一个基本样例。
    
    输入: 4
    输出: "1211"
    解释:当 n = 3 时,序列是 "21",其中我们有 "2""1" 两组,"2" 可以读作 "12",也就是出现频次 = 1 而 值 = 2;类似 "1" 可以读作 "11"。所以答案是 "12""11" 组合在一起,也就是 "1211"

    代码1:

    class Solution:
        def countAndSay(self, n: int) -> str:
            # 定义内部函数,输入前一个的结果,输出下一个结果,例如输入"11"输出"21"
            def say(strs):
                count = 1  # 记录相同数字的个数
                cur = 0  # 记录每轮要对比的数字
                res = ""  # 用于记录结果
                # 记录第一个字符
                cur = strs[0]
                # 遍历后面的所有数
                for i in range(1, len(strs)):
                    # 如果和cur相同
                    if strs[i] == cur:
                        # count加1
                        count += 1
                        
                    # 如果不和cur相等
                    else:
                        # 将count和cur
                        res += str(count) + cur
                        # 重新将新的数赋给cur
                        cur = strs[i]
                        # count回到1
                        count = 1
                        # 如果是最后一个数
                res += str(count) + cur
    
                return res
            # n=1时,直接返回结果
            if n <= 1:
                return "1"
            # 从n=2开始,迭代调用say()
            last = "1"
            for i in range(2,n+1):
                last = say(last)
            return last

    代码比较冗余,思想比较简单。就是逐个数相同字符的个数,然后写入结果。

    代码2:

    def countAndSay(self, n: int) -> str:
        # 导入itertools的groupby
        from itertools import groupby
        # 如果n=1,则直接返回"1"
        result = '1'
        # 如果n从2开始
        for i in range(1, n):
            # groupby将"1121"变成{1:['1','1'],2:['2'],1:['1']}
            result = ''.join([str(len(list(g))) + k for k, g in groupby(result)])
        return result

    这种方式利用了python的itertools.groupby函数。不建议在写算法题的时候使用内库。但可以学习一下groupby的用法。

    3.最大子序和

    题目:

    给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

    示例:

    输入: [-2,1,-3,4,-1,2,1,-5,4],
    输出: 6
    解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

    代码1:(暴力)

    class Solution:
        def maxSubArray(self, nums: List[int]) -> int:
            if not nums:
                return 0
            # _max初始化为nums[0]
            _max = nums[0]
            # 从0开始循环,每一个元素都和后面的所有元素累加,找出其中最大值即可
            for i in range(len(nums)):
                _sum = 0
                # 开始累加
                for k in range(i,len(nums)):
                    # 每累加一个数,就和_max比较一下,如果更大,则替换_max
                    _sum += nums[k]
                    _max = _sum if _sum > _max else _max
    
            return _max

    暴力解法,时间复杂度O(n^2)。就是遍历所有的可能性,然后用一个变量几率最大值即可。空间复杂度O(1)。

    代码2:(动态规划)

    class Solution:
        def maxSubArray(self, nums: List[int]) -> int:
            # 开始的时候,temp记录第一个数
            temp = nums[0]
            # 第一个数是最大的数
            _max = temp
            # 从index=1的数开始循环
            for i in range(1,len(nums)):
                # 判断 [当前最大值_max、累计最大列表和temp、下一个数] 之中最大的数,并从新赋值给_max
                _max = max(temp+nums[i],nums[i],_max)
                # 如果将下一个数加进事先累积的最大长度列表,发现还更小了,则上次的累积结束,下一个数更大,从新开始累积
                if temp+nums[i] < nums[i]:
                    # temp重新开始累积和
                    temp = nums[i]
                # 如果累积了下一个数,更大,则继续累积
                else:
                    temp = temp+nums[i]
                
            # 最后返回保存最大和的_max
            return _max

    这种解法是动态规划解法,时间复杂度O(n),空间复杂度O(1)。

    更巧妙的写法:

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

    使用一个列表来存储某个index下的最大值(这个最大值来自于,nums[i]、前面累积的最大值+nums[i]),然后再取所有index下的最大值。

    直接在原列表上保存最大序列和:

    class Solution:
        def maxSubArray(self, nums: List[int]) -> int:
            # 从1开始循环
            for i in range(1, len(nums)):
                # nums[i]用来保存i之前的最大序列和,如果nums[i-1]为负,则说明nums[i-1]+nums[i]<nums[i],所以这种情况下nums[i] += 0.也就是i之前的最大序列和就是nums[i]
                nums[i] += nums[i-1] if nums[i-1] > 0 else 0
            # 全部计算完后,nums列表已经全部是保留的每个index对应的最大序列和,取最大值即可
            return max(nums)

    代码3:(分治法)

    class Solution:
        def maxSubArray(self, nums: List[int]) -> int:
            n = len(nums)
            #递归终止条件
            if n == 1:
                return nums[0]
            else:
                #递归计算左半边最大子序和
                max_left = self.maxSubArray(nums[0:len(nums) // 2])
                #递归计算右半边最大子序和
                max_right = self.maxSubArray(nums[len(nums) // 2:len(nums)])
            
            #计算中间的最大子序和,从右到左计算左边的最大子序和,从左到右计算右边的最大子序和,再相加
            max_l = nums[len(nums) // 2 - 1]
            tmp = 0
            for i in range(len(nums) // 2 - 1, -1, -1):
                tmp += nums[i]
                max_l = max(tmp, max_l)
            max_r = nums[len(nums) // 2]
            tmp = 0
            for i in range(len(nums) // 2, len(nums)):
                tmp += nums[i]
                max_r = max(tmp, max_r)
            #返回三个中的最大值
            return max(max_right,max_left,max_l+max_r)

    使用DC(分治法),最大序列可能出现在左、右或中间。如下图所示:

    我们将列表分成左右两部分,最大序列只可能出现在3个部分,即左边部分、右边部分、和中间跨中心的部分。

    左右部分我们分别单独找出最大值。

    中间部分,我们从中心开始往左右方向各找一个最大序列,既然都是最大序列,那加起来也是最大序列(注意,这两个序列实际上是连续的,因为都是从中心开始)。这样就可以得到跨中心部分的最大序列和。

    最后,返回max(左边、中间、右边)。然后使用DC思想进行递归。

    ##

  • 相关阅读:
    巧用加密方法保障电子邮件系统安全 狼人:
    网管员注意:保障邮件安全的七条措施 狼人:
    全面剖析DNS 0DAY攻击、威胁以及防治 狼人:
    简述:电子邮件安全发展 狼人:
    IE漏洞致数百万用户中招 快用瑞星卡卡打补丁 狼人:
    警惕可执行文件:三类危险TXT类型文件 狼人:
    安全使用电子邮件十三法 狼人:
    提高IE和Email的安全性的四步骤 狼人:
    了解电子邮件加密 保证隐私内容安全 狼人:
    防不胜防 了解DNS缓存中毒攻击原理 狼人:
  • 原文地址:https://www.cnblogs.com/leokale-zz/p/12366974.html
Copyright © 2020-2023  润新知