2021-8

DP

5. 最长回文子串(中等)

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

 

示例 1:

 

输入: "babad"

输出: "bab"

注意: "aba" 也是一个有效答案。

示例 2:

 

输入: "cbbd"

输出: "bb"

方法:DP

 P(i,j) 表示字符串 s的第 i j个字母组成的串(s[i:j])是否为回文串:

 

动态规划的状态转移方程:

 

也就是说,只有 s[i+1:j-1] 是回文串,并且 s的第 i  j个字母相同时,s[i:j] 才会是回文串。

动态规划的边界条件:

 

所有 P(i,j)=true 中 j-i+1(即子串长度)的最大值。

注意:在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

class Solution:

    def longestPalindrome(self, s: str) -> str:

        size = len(s)

        if size < 2:   # 无字符或单字符直接返回其本身       

            return s 

 

        dp = [[False] * size for i in range(size)] # 初始化状态矩阵

        max_length = 1 # 初始化最大长度

        start = 0  # 初始化最大回文起点

 

        for i in range(size):   # 初始化对角线(单字符为回文)

            dp[i][i] = True

       

        for j in range(1, size):  # 终点从s[1]开始

            for i in range(0, j):  # 起点从s[0]开始,到s[j]结束

                if s[i] == s[j]:

                    if j-i < 3:

                        dp[i][j] = True

                    else:

                        dp[i][j] = dp[i+1][j-1]

                else:

                    dp[i][j] = False

               

                if dp[i][j]:

                    cur_length = j-i+1

                    if cur_length > max_length:

                        max_length = cur_length

                        start = i

        return s[start:start+max_length]

300. 最长上升子序列(中等)

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]

输出: 4

解释: 最长的上升子序列是 [2,3,7,101]它的长度是 4

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
  • 你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) ?

方法:DP

按照动态规划定义状态的套路,我们有两种常见的定义状态的方式:

1dp[i] : i 结尾(一定包括 i)所能形成的最长上升子序列长度, 答案是 max(dp[i]),其中 i = 0,1,2, ..., n - 1

2dp[i] : i 结尾(可能包括 i)所能形成的最长上升子序列长度,答案是 dp[-1] -1 表示最后一个元素)

第二种定义方式由于无需比较不同的 dp[i] 就可以获得答案,因此更加方便。但是想了下,状态转移方程会很不好写,因为 dp[i] 的末尾数字(最大的)可能是 任意 j < i 的位置。

 

第一种定义方式虽然需要比较不同的 dp[i] 从而获得结果,但是我们可以在循环的时候顺便得出,对复杂度不会有影响,只是代码多了一点而已。因此我们选择第一种建模方式。

 

链接:https://leetcode-cn.com/problems/non-overlapping-intervals/solution/chuan-shang-yi-fu-wo-jiu-bu-ren-shi-ni-liao-lai-2/

 

解题思路:

状态定义:

dp[i] 的值代表 nums i个数字的最长子序列长度。

转移方程: j[0,i),考虑每轮计算新 dp[i] 时,遍历 [0,i) 列表区间,做以下判断:

1)当 nums[i] > nums[j] 时: nums[i] 可以接在 nums[j] 之后(此题要求严格递增),此情况下最长上升子序列长度为 dp[j] + 1

2)当 nums[i] <= nums[j] 时: nums[i]无法接在 nums[j] 之后,此情况上升子序列不成立,跳过。1

上述所有 1. 情况 下计算出的 dp[j] + 1的最大值,为直到 i 的最长上升子序列长度(即 dp[i])。实现方式为遍历 j 时,每轮执行 dp[i] = max(dp[i], dp[j] + 1)

转移方程: dp[i] = max(dp[i], dp[j] + 1) for j in [0, i)

初始状态:

dp[i]所有元素置 1,含义是每个元素都至少可以单独成为子序列,此时长度都为 1

返回值:

返回 dp列表最大值,即可得到全局最长上升子序列长度。

复杂度分析:

时间复杂度 O(N^2) 遍历计算 dp 列表需 O(N),计算每个 dp[i] O(N)

空间复杂度 O(N) dp列表占用线性大小额外空间。

class Solution:

    def lengthOfLIS(self, nums: List[int]) -> int:

        if not nums:

            return 0

        n = len(nums)

        dp = [1] * n

        for i in range(n):

            for j in range(i):

                if nums[i] > nums[j]:

                    dp[i] = max(dp[i], dp[j] + 1)

        return  max(dp)

方法2:二分查找

 

 

处理这些扑克牌要遵循以下规则:

只能把点数⼩的牌压到点数⽐它⼤的牌上。如果当前牌点数较⼤没有可以放置的堆,则新建⼀个堆,把这张牌放进去。如果当前牌有多个堆可供选择,则选择最左边的堆放置。

 

为什么遇到多个可选择堆的时候要放到最左边的堆上呢?因为这样可以保证牌堆顶的牌有序(2, 4, 7, 8, Q),证明略。

 

每次处理张扑克牌找个合适的牌堆顶来放,牌堆顶的牌有序,这就能分查找了:⽤⼆分查找来搜索当前牌应放置的位置。

最后整个算法流程为:

设当前已求出的最长上升子序列的长度为 len(初始时为 1),从前往后遍历数组 nums,在遍历到 nums[i] 时:

1)如果 nums[i] > d[len] ,则直接加入到 d 数组末尾,并更新 len=len+1

2)否则,在 d 数组中二分查找,找到第一个比 nums[i] 小的数d[k] ,并更新 d[k+1]=nums[i]

以输入序列 [0,8,4,12,2] 为例:

第一步插入 0d=[0]

第二步插入 8d = [0, 8]

第三步插入 4d = [0, 4]

第四步插入 12d = [0, 4, 12]

第五步插入 2d = [0,2,12]

最终得到最大递增子序列长度为 3

class Solution:

    def lengthOfLIS(self, nums: List[int]) -> int:

        tails = [0] * len(nums)

        res = 0

        for num in nums:

            i, j = 0, res

            while i < j:

                mid = (i + j) // 2

                if tails[mid] < num:

                    i = mid + 1

                else:

                    j = mid

            tails[i] = num

            if j == res:

                res += 1

        return res

一维动态规划

一、问题描述:给定n,找到不同的将n写成1,3,4相加的方法

例如:n=5,答案是6

5  = 1 + 1 + 1 + 1 + 1

  = 1 + 1 + 3

  = 1 + 3 + 1

  = 3 + 1 + 1

  = 1 + 4

  = 4 + 1

方法:DP

问题分析:

f(0) = f(1) = f(2) = 1, f(3) = 2

for(i = 4; i <= n; i++)

f(i) = f(i-1) + f(i-3) + f(i-4)

代码:

def coin(n):

    dp = [0] * (n + 1)

    dp[0] = dp[1] = dp[2] = 1

    dp[3] = 2

    for i in range(4, n+1):

        dp[i] = dp[i-1] + dp[i-3] + dp[i-4]

    return dp[n]

测试:

coin(5)

leetcode198打家劫舍

假如你是一个职业抢劫犯,你打算洗劫一个街道。每一个房子里有一定数量的钱,限制你的唯一条件是相邻的房子的安保系统是相连的,如果你抢劫相邻的房子,那么安保系统就会惊动警察。

给定一个非负整数的列表代表每个房子当中的钱,计算在不惊动警察的情况下,你可以抢劫到最多的钱。

分析:

      8   3   5    7      6     9    2    8    35 (不能连续相加,找加和最大)

Y    8   3  13  15   19   24  21  32  59

N   0   8   8    13   15   19  24  24  32

 

Y:代表加上当前的数,所能达到的最大值

N:代表不加当前的数,所能达到的最大值

Y:    Y(i) = N(i-1) + a(i)  

N:   N(i) = max(Y(i-1), N(i-1))

       Y(0) = a(0), N(0) = 0

 

最大:max(Y(i),N(i))

 

代码1

引入了两个空间,时间复杂度O(n),空间复杂度O(n)

def rob(nums):

    n = len(nums)

    # 生成两行n+10,第一列都为0,代表初始值

    # 规定第二行不取当前值所达到的最大值,第一行取当前值所达到的最大值

    dp = [[0 for _ in range(n+1)] for _ in range(2)]

    for i in range(1, n+1):

        dp[0][i] = dp[1][i-1] + nums[i-1]

        dp[1][i] = max(dp[0][i-1], dp[1][i-1])

    return max(dp[0][n], dp[1][n])

测试:

nums = [2, 7, 9, 3, 1]

rob(nums)

 

代码2

1进行优化,每次只用到了当前值的前面的两个数字,不用引入空间。

时间复杂度O(n),空间复杂度O(1)

def rob(nums):

    n = len(nums)

    # 初始值

    yes, no = 0, 0

    for i in nums:

        yes, no = no + i, max(yes, no)

    return max(yes, no)

测试:

nums = [2, 7, 9, 3, 1]

rob(nums)

入室抢劫2

该街道的所有房子是圆形排列的。也就是说第一家和最后一家也是邻居。安保系统设置同上问题。

分析

在上面的情况下,考虑圆环,则分成两种情况:选择起点(就不能选择终点);不选择起点。

def rob_round(nums):

    def rob(nums):

        yes, no = 0, 0

        for i in nums:

            yes, no = no + i, max(yes, no)

        return max(yes, no)

    return max(rob(nums[:-1]), rob(nums[1:]))

测试:

nums = [2, 7, 9, 3, 1]

rob_round(nums)

91. 解码方法(中等)

一段包含着A-Z的短信用以下方式进行编码:

'A'> 1

'B'> 2

……

'Z'> 26

给定一段编码的短信,计算解码的方式。

分析:

没有限制条件时:f(n) = f(n-1) + f(n-2)

条件范围10-26f(n-2)可以取到

条件范围:

方法:DP

代码:

class Solution:

    def numDecodings(self, s: str) -> int:

        if not s or s[0] == '0':

            return 0

        n = len(s)

        dp = [0] * (n+1)

        dp[0], dp[1] = 11

        for i in range(2, n+1):

            if '10' <= s[i-2:i] <= '26':

                dp[i] += dp[i-2]

            if '0' < s[i-1] <= '9':

                dp[i] += dp[i-1]   

        return dp[-1]

测试:

numDecodings('122230789')

53. 最大子序和

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

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4]

输出: 6

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

进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

class Solution:

    def maxSubArray(self, nums: List[int]) -> int:

        ans = nums[0]

        for i in range(1len(nums)):

            nums[i] = max(nums[i]+nums[i-1], nums[i])

            ans = max(ans, nums[i])

        return ans

152. 乘积最大子数组(中等)

找到array中的连续子序列,该子序列的乘积最大。

 

  1. 遍历数组时计算当前最大值,不断更新
  2. imax为当前最大值,则当前最大值为 imax = max(imax * nums[i], nums[i])
  3. 由于存在负数,那么会导致最大的变最小的,最小的变最大的。因此还需要维护当前最小值iminimin = min(imin * nums[i], nums[i])
  4. 当负数出现时则imaximin进行交换再进行下一步计算

时间复杂度:O(n)

class Solution:

    def maxProduct(self, nums: List[int]) -> int:

        imax = imin = result = nums[0]

        for i in range(1len(nums)):

            imax, imin = max(imax*nums[i], imin*nums[i], nums[i]), \

                min(imax*nums[i], imin*nums[i], nums[i])

            result = max(result, imax)

        return result

代码:

def maxProduct(nums):

    if len(nums) == 0:

        return 0

    maximum = minimum = result = nums[0]

    for i in range(1, len(nums)):

        maximum, minimum = max(maximum * nums[i], minimum * nums[i], nums[i]), \

                           min(maximum * nums[i], minimum * nums[i], nums[i])

        result = max(result, maximum)

    return result

测试:

nums = [2, 3, -2, 4]

maxProduct(nums)

643. 子数组最大平均数 I

给定 n 个整数,找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数。

示例 1:

输入: [1,12,-5,-6,50,3], k = 4

输出: 12.75

解释: 最大平均数 (12-5-6+50)/4 = 51/4 = 12.75

注意:

1 <= k <= n <= 30,000。

所给数据范围 [-10,000,10,000]。

方法:滑动窗口

假设我们已经索引从 i i+k 子数组和为 x。要知道索引从 i+1 i+k+1子数组和,只需要从 x 减去 sum[i],加上 sum[i+k+1] 即可。 根据此方法可以获得长度为 k 的子数组最大平均值。

class Solution:

    def findMaxAverage(self, nums: List[int], k: int) -> float:

        ksum = sum(nums[:k])

        res = ksum

        for i in range(k, len(nums)):

            ksum += nums[i]-nums[i-k]

            res = max(res, ksum)

        return res/k

买卖股票问题

分析:

每天都有3种【选择】:买入(buy)、卖出(sell)、无操作(rest)。但并不是每天都可以任意选择这3种选择,sell必须在buy之后,buy必须在sell之后。rest操作,一种是buy之后的rest(持有了股票),一种是sell之后的rest(没有持有股票)。

有3个【状态】,第1个是天数,第2个是允许交易的最大次数,第3个是当前的持有状态(1表示持有股票,0表示没有持有)

121. 买卖股票的最佳时机(简单)

(找到一个数组中相差最大的数,小的在前面,大的在后面)

给定一个数组,表示每天的股票价格。

你可以进行一次交易(先买再卖),问如何能得到最大利润。

8   1   3   4   7   3   4

最大利润为6,买17

分析:

 

记录一个列表中的最小值,不断更新

记录列表中的最大利润,不断更新

初始化:将第一个数作为最小值,将0设为最大利润

for循环,如果后面的数比记录的最小值小,更新最小值;如果更新完最小值,最大利润比0大,更新最大利润

代码:

class Solution:

    def maxProfit(self, prices: List[int]) -> int:

        if len(prices) < 2:

            return 0

        min_price = prices[0]

        max_profit = 0

        for price in prices:

            max_profit = max(price-min_price, max_profit)

            min_price = min(price, min_price)

        return max_profit

测试:

prices = [7,1,5,3,6,4]

maxProfit(prices)

122. 买卖股票的最佳时机 II(简单)

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

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

输出: 7

解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。

     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:

输入: [1,2,3,4,5]

输出: 4

解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。

     注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。

     因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入: [7,6,4,3,1]

输出: 0

解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 3 * 10 ^ 4
  • 0 <= prices[i] <= 10 ^ 4

分析:

考虑到【不能同时参与多笔交易】,因此每天交易结束后只可能手里有一支股票或者没有股票的状态。

定义状态dp[i][0]表示第i天交易完后手里没有股票的最大利润,dp[i][1]表示第i天交易完后手里持有一支股票的最大利润(i从0开始)。

考虑dp[i][0]的转移方程,如果

解题思路:

股票买卖策略:

单独交易日: 设今天价格p1、明天价格p2,则今天买入、明天卖出可赚取金额 p2−p1(负值代表亏损)。

连续上涨交易日: 设此上涨交易日股票价格分别为 p1, p2, ... , pn,则第一天买最后一天卖收益最大,即 pn−p1;等价于每天都买卖,即 pn - p1=(p2 - p1)+(p3 - p2)+...+(pn - p_n-1)

连续下降交易日: 则不买卖收益最大,即不会亏钱。

算法流程:

遍历整个股票交易日价格列表 price,策略是所有上涨交易日都买卖(赚到所有利润),所有下降交易日都不买卖(永不亏钱)。

设 tmp 为第 i-1 日买入与第 i 日卖出赚取的利润,即 tmp = prices[i] - prices[i - 1] ;

当该天利润为正 tmp > 0,则将利润加入总利润 profit;当利润为 0或为负,则直接跳过;

遍历完成后,返回总利润 profit。

复杂度分析:

时间复杂度 O(N): 只需遍历一次price;

空间复杂度 O(1): 变量使用常数额外空间。

 

class Solution:

    def maxProfit(self, prices: List[int]) -> int:

        profit = 0

        n = len(prices)

        for i in range(1, n):

            temp = prices[i] - prices[i-1]

            if temp > 0:

                profit += temp

        return profit        

123. 买卖股票的最佳时机 III(困难)

给定一个数组,表示每天的股票价格。

你可以进行两次的交易(先买再卖,在再次买入时,必须将之前的股票卖出),问如何得到最大利润。

输入:prices = [2, 4, 6, 1, 3, 8, 3]

输出:11([2, 6]、[1, 8]是两次进行买入卖出的时机)

方法1:DP

class Solution:

    def maxProfit(self, prices: List[int]) -> int:

        if not prices:

            return 0

        n = len(prices)

 

        dp = [[[0]*2 for _ in range(3)] for _ in range(n)]

        for i in range(0, n):

            for k in range(20-1):

                if i - 1 == -1:

                    dp[i-1][k][0], dp[i-1][k][1] = 0float('-inf')

                dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])

                dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

 

        return dp[n-1][2][0]

方法2

分析:

首先构建前半部分的利润表,方式和121.买股票的最佳时机(仅一次交易)中的方法一样,更新最小买入价格,然后求每天卖出的最大利润(图中 前半部分的最大利润表)

然后构建后半部分的利润表,为了将构建两个利润表在一次遍历中完成,我们采用从后往前填充后半部分最大利润表。(图中 后半部分最大利润)

方法:从后往前更新最大卖出价格 max, 计算在每天买入的最大利润,这样就可以算出后半部分的利润

最后遍历一遍,求两个利润表和的最大值,就是我们所求的至多两次交易的最大利润。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 该资源内项目源码是个人的课程设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值