最长连续序列 python

2022.09.19

1.128. 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

解法1 暴力求解:去重+排序

nums = [0,3,7,2,5,8,4,6,0,1]
t = 1
ans = 0
nums = sorted(set(nums))# 先去重再排序
if not nums: # 列表为空返回0
	print(0)
for i in range(len(nums)-1):
    if nums[i+1]-nums[i] == 1:# 列表为空返回0
        t+=1
    else:
        ans = max(ans,t)# 最长连续递增序列,后一个元素比前一个元素大1。
        t = 1
print(max(ans,t)) # 有可能去重排序后整个都是连续的,所以要输出ans,t中较大的一个

解法2 动态规划+哈希表

题目要求python求最长递减子序列 最长连续序列 python_python求最长递减子序列复杂度,但是哈希表暴力求解
借鉴【动态规划】Python 题解及讨论区

class Solution(object):
    def longestConsecutive(self, nums):
        hash_dict = dict() # 用哈希表存储每个端点值对应连续区间的长度
        max_length = 0
        for num in nums:
            if num not in hash_dict: # 若是新数加入;若数已在哈希表中,跳过不做处理
                left = hash_dict.get(num - 1, 0) # 取出其左右相邻数已有的连续区间长度 left 和 right
                right = hash_dict.get(num + 1, 0)
                cur_length = 1 + left + right # 计算当前数的区间长度为:cur_length = left + right + 1
                if cur_length > max_length: # 根据 cur_length 更新最大长度 max_length 的值
                    max_length = cur_length
                hash_dict[num] = cur_length # 更新区间两端点的长度值
                hash_dict[num - left] = cur_length
                hash_dict[num + right] = cur_length
        return max_length

2.300. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1

参考链接:动态规划 + 二分查找,清晰图解

解法1 动态规划

执行用时3080ms

class Solution(object):
    def lengthOfLIS(self, nums):
        if not nums: # 列表为空返回0
            return 0
        dp = [1] * len(nums)
        for i in range(len(nums)):
            for j in range(i):
                if nums[j] < nums[i]: # 如果要求非严格递增,将此行 '<' 改为 '<=' 即可。
                    dp[i] = max(dp[i], dp[j] + 1) #  dp[i]的值代表以nums[i]结尾的最长子序列长度
        return max(dp)

解法2 动态规划+二分查找

采用了二分查找后,执行用时只有24ms

class Solution(object):
    def lengthOfLIS(self, nums):
        tails, res = [0] * len(nums), 0 # 新建数组tails,用于保存最长上升子序列。
        for num in nums: # 对原序列进行遍历,将每位元素二分插入tails中。
            i, j = 0, res
            while i < j:
                m = (i + j) // 2
                if tails[m] < num: # 如果要求非严格递增,将此行 '<' 改为 '<=' 即可。
                    i = m + 1 
                else: 
                    j = m
            tails[i] = num
            if j == res: 
                res += 1
        return res

3.354. 俄罗斯套娃信封问题

给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。
当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。

注意:不允许旋转信封。

示例 1:
输入:envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出:3
解释:最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。
示例 2:
输入:envelopes = [[1,1],[1,1],[1,1]]
输出:1

Python中bisect的使用方法

Python 二分查找与 bisect 模块8.6. bisect — 数组二分查找算法

import bisect
#使用bisect函数前需要对列表进行排序,否则虽然可以输出数值,但没有意义
a = [1, 5, 6, 10, 9]
a.sort()
print("最初的列表:", a)
-----------------------
输出>>>
最初的列表: [1, 5, 6, 9, 10]
1.bisect.bisect 返回某个数在列表中可以插入的位置,但不会插入该数。
#如果这个数与列表中的元素相同,则返回元素后面的位置
print("6在列表中可以插入的位置:", bisect.bisect(a, 6))
-----------------------
输出>>>
6在列表中可以插入的位置: 3
2.bisect.insort 将某个数插入列表
bisect.insort(a, 8)
print("在列表中插入8:", a)
-----------------------
输出>>>
在列表中插入8: [1, 5, 6, 8, 9, 10]
3.处理插入数值与列表元素相同的情况,插入该数
#bisect.insort_left 插入元素左侧位置;bisect.insort_right 插入元素右侧位置
bisect.insort_left(a, 5)
print("在列表中插入5:", a)
bisect.insort_right(a, 10)
print("在列表中插入10:", a)
-----------------------
输出>>>
在列表中插入5: [1, 5, 5, 6, 8, 9, 10]
在列表中插入10: [1, 5, 5, 6, 8, 9, 10, 10]
4.处理插入数值与列表元素相同的情况,返回位置,但不会插入该数
#bisect.bisect_left 插入元素左侧位置;bisect.bisect_right 插入元素右侧位置
print("5在列表中可以插入的位置:", bisect.bisect_left(a, 5))
print("5在列表中可以插入的位置:", bisect.bisect_right(a, 5))
-----------------------
输出>>>
5在列表中可以插入的位置: 1
5在列表中可以插入的位置: 3
5. bisect() 还可以用于数字表查询

Python导入模块的方法有两种:import module 和 from module import,区别是前者所有导入的东西使用时需加上模块名的限定,而后者不要。

# import bisect # 报错,TypeError: 'module' object is not callable
from bisect import * 
def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
    i = bisect(breakpoints, score)
    return grades[i]
[grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
-----------------------
输出>>>
['F', 'A', 'C', 'C', 'B', 'A', 'A']

解法1 动态规划

超出时间限制

[[2, 3], [5, 4], [6, 5], [6, 7]]

用第一个维度递增,第二个维度递减的顺序排序,会得到下面的结果:

[[2, 3], [5, 4], [6, 7], [6, 5]]

这个时候,只看第二个维度
[3, 4, 7, 5],就会得到最长递增子序列的长度是 3 的正确结果

class Solution(object):
    def maxEnvelopes(self, envelopes):
		if not envelopes: # 列表为空返回0
            return 0
        N = len(envelopes)
        envelopes.sort(key=lambda x: (x[0], -x[1])) # 按第一维升序,第二位降序对信封进行快排
        res = 0
        dp = [1] * N
        for i in range(N):
            for j in range(i):
                if envelopes[j][1] < envelopes[i][1]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)

解法2 动态规划+二分查找

class Solution(object):
    def maxEnvelopes(self, envelopes):
        envelopes.sort(key=lambda x: [x[0], -x[1]]) # 按第一维升序,第二位降序对信封进行快排
        dp = []
        for i, env in enumerate(envelopes):
            i = bisect.bisect_left(dp, env[1]) # bisect.bisect_left 插入元素左侧位置
            if i < len(dp):
                dp[i] = env[1]
            else:
                dp.append(env[1])
        return len(dp)

4.329. 矩阵中的最长递增路径

给定一个 python求最长递减子序列 最长连续序列 python_数组_02 x python求最长递减子序列 最长连续序列 python_散列表_03整数矩阵 matrix ,找出其中最长递增路径的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。

示例 1:
输入:matrix = [[9,9,4],[6,6,8],[2,1,1]]
输出:4 
解释:最长递增路径为 [1, 2, 6, 9]。
示例 2:
输入:matrix = [[3,4,5],[3,2,6],[2,2,1]]
输出:4 
解释:最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。
示例 3:
输入:matrix = [[1]]
输出:1

解法1 记忆化搜索+DFS

functools.lru_cache装饰器详解 在functools这个模块中,有lru_cache这个一个神奇的装饰器存在。functools.lru_cache的作用主要是用来做缓存,他能把相对耗时的函数结果进行保存,避免传入相同的参数重复计算。同时,缓存并不会无限增长,不用的缓存会被释放。

# import functools
class Solution:
    def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
        @lru_cache(None)
        def dfs(x, y):
            res = 1
            for a, b in [[x - 1, y], [x + 1, y], [x, y - 1], [x, y + 1]]:
                if 0 <= a < m and 0 <= b < n and matrix[a][b] > matrix[x][y] and dfs(a, b) + 1 > res:
                    res = dfs(a, b) + 1
            return res
        m, n = len(matrix), len(matrix[0])
        ans = 0
        for i in range(m):
            for j in range(n):
                if dfs(i, j) > ans:
                    ans = dfs(i, j)
        return ans

5.334. 递增的三元子序列

给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。
如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。

示例 1:
输入:nums = [1,2,3,4,5]
输出:true
解释:任何 i < j < k 的三元组都满足题意

示例 2:
输入:nums = [5,4,3,2,1]
输出:false
解释:不存在满足题意的三元组

示例 3:
输入:nums = [2,1,5,0,4,6]
输出:true
解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6

解题思路

用两个变量 r1, r2 分别记录第一小和第二小的数。然后遍历 nums。只要碰到比 r1 小的数我们就替换掉 r1,碰到比 r1 大但比 r2 小的数就替换 r2。
只要碰到比 r2 大的数就已经满足题意了。

class Solution:
    def increasingTriplet(self, nums):
        r1, r2 = sys.maxsize, sys.maxsize
        for n in nums :
            if n <= r1 : r1 = n
            elif n <= r2 : r2 = n
            else : return True
        return False

参考内容

最长递增子序列问题五连击