[刷题] 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思想进行递归。

 

 

##

posted @ 2020-02-26 14:41  风间悠香  阅读(197)  评论(0编辑  收藏  举报