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思想进行递归。
##