动态规划

方法1:暴力递归

  • helper(String s, int start, int end)

    • 表示sstartend位置,是否有回文子串

  • base case:

    • start == end : 相等时,说明只有一个字符了,返回T

    • start +1== end :两个字符的时候,比较两个字符是否相等


        public String longestPalindrome(String s) {
            String ans = "";
            for (int i = 0; i < s.length(); i++) {
                for (int j = i; j < s.length(); j++) {
                    if (helper(s, i, j) && j - i + 1 > ans.length()) {
                        ans = s.substring(i, j + 1);
                    }
                }
            }
            return ans;
        }


        private boolean helper(String s, int start, int end) {
            if (start == end) return true;
            if (start + 1 == end) return s.charAt(start) == s.charAt(end);
            boolean ans = false;
            if (s.charAt(start) == s.charAt(end)) {
                ans = helper(s, start + 1, end - 1);
            }
            return ans;
        }

方法2:自顶向下记忆化递归(Top-down)

脱胎与方法1,添加记忆化

方法3:自底向上填表递归(Bottom-up)

  • 其中f[i][j]表示s中,从ij是否有回文子串

  • k为遍历的字符长度,可以为n

    • 此时i=0,j=i+k-1=0+n-1=n-1

  • 条件为当前字符[i]==[j]的时候,要么只有两个字符,要么砍头去尾,有回文子串

另外一种写法

  • k为遍历的字符长度,可以为n 即当i=0的时候

另外一种写法

方法4:中心扩展法

方法5:Manacher算法

本动态规划的文章着重讲动态规划,涉及马拉车算法的内容不详细展开,下面的代码取自weiwei大佬的题解

分析

只能买卖一次

方法1:朴素版DP

  • 股票只能买卖一次

  • dp[i][0|1]

    • i表示第i

    • 0表示第i天没有股票的状态[无股票]

    • 1表示第i天持有股票的状态[有股票]

  • 规定卖出的时候(sell)获利,+prices[i],买入的时候,暂时算负债(buy),-prices[i]

  • dp[i][0]表示第i天无股票,可能是昨天也没有股票,状态持续到今天,为dp[i-1][0],也可能是昨天持有股票,但是卖出了(sell),为dp[i-1][1]+prices[i]

    • 这时为 dp[i][0] = max{dp[i-1][0],dp[i-1][1]+prices[i]}

  • dp[i][1]表示第i天有股票,可能是昨天就有股票,状态持续到今天,为dp[i-1][1],也可能是昨天没有持有股票,但是买入了(buy), 为dp[i-1][0]-prices[i] ,因为只有一次买入卖出的机会, dp[i-1][0]表示i-1天的时候,是无股票的,在i天的时候买入了股票,也就是说要必须在i天之后的某一天卖出股票,所以很显然dp[i-1][0]=0

    • 这时为dp[i][1] = max{dp[i-1][1],-prices[i]}

方法2:空间压缩DP

  • 由于值依赖前一天的收益情况(有无股票的状态),f只需要来回滚动即可,去掉一维

分析

可以买卖多次

方法1:朴素版DP

  • 股票可以多次买卖

方法2:空间压缩DP

去掉一维

  • f_i_0 是昨天无股票f_i_0 或者是昨天持有股票,今天卖出了sell,卖出相当于盈利为f_i_1+prices(i)

    • 这时为 f_i_0 = max(f_i_0, f_i_1 + prices[i]) - f_i_1是昨天持有股票f_i_1,或者是昨天无股票状态,今天买入了股票buy,买入相当于负债,但是需要提前记录下tmp =f_i_0,因为上面的转移方程已经改变了f_i_0的值

    • 这时为f_i_1 = max(f_i_1, tmp - prices[i])

  • 另外一种写法

分析

最多只能买卖两次

方法1:朴素版DP

分析

基于上一种的最多买卖2次,这里允许2变成一般的次数k,最多买卖k次

方法1:朴素版DP

  • 最多有K笔交易的限制

方法2:压缩空间DP

分析

可以买卖多次,但是卖出有一天冷冻期

方法1:朴素版DP

分析

可以买卖多次,但是每次卖出收取手续费fee

方法1:朴素版DP

  • 不过也可以,在处理fee的时候,上面的做法是卖出(sell)的时候,算扣除的手续费-fee,也可以在买入(buy)的时候算利润-fee,有如下代码

方法2:空间压缩DP

  • 基于上面方法的第一段代码,去掉一维,改成如下的形式:

  • 或者基于变量的方式有下面的改写方式:

方法1:DP

  • 也可以不判断,硬比较

follow up:返回最大子数组和的起始索引

方法1:记忆化递归

方法2:记忆化递归

  • Indexenum来记录某个坐标是否可以到达最末尾的位置,有三类值:

    • GOOD:可以跳到末尾位置

    • BAD:不可以跳到末尾位置

    • UNKNOWN:不知道是否可以跳到末尾位置

  • 一开始的时候都是UNKNOWN状态,最末尾的位置是GOOD状态,因为可以由自己跳到自己的位置

  • 出口时判断,是否是GOOD状态,计算memo,返回true的时候记录GOOD状态,返回false时记录BAD状态

方法3:贪心

方法1:记忆化递归

方法2:DP

方法1:记忆化递归

  • 准备一个函数:dfs(int[] arr, int curPos, boolean[] visited)

    • 其中curPos表示当前访问的位置

    • visited表示当前的curPos位置有无被访问过

  • 出口条件:

    • 当前curPos越界了,也就是不在[0,len-1]范围内时,返回false

    • 当前curPos的访问过,返回false

    • arr[curPos]==0时,表示找到了,返回true

  • 探索左边和右边位置

方法2:BFS

  • 准备一个bool类型的数组visited表示当前的下标有无被访问过

  • 准备一个queue,转这个queue

    • 取到这一轮的总的size大小,进行for loop

    • 弹出当前的curPos,如果arr[curPos]== 0说明找到了,返回true

    • 分别渠道左右两边去找,curPos的位置不越界并且leftPosrightPos未被访问过

    • 访问后要设置下visited的属性,并且将位置放置于queue

方法1:记忆化递归

方法2:DP

Follow Up:打印路径

方法1:DP

方法2:记忆化递归

一些名词

  • 最长上升子序列($LIS$):Longest Increasing Subsequence

  • 最长连续序列($LCS$):Longest Consecutive Sequence

  • 最长连续递增序列($LCIS$):Longest Continuous Increasing Subsequence

  • 最长公共子序列($LCS$):Longest Common Subsequence

子串与子序列区别:子串不可跳跃,子序列可以跳跃,如 “AC”是“ABCDEFG”的子序列,而不是子串。 而“ABC”则是其子串

1589761403470.png

定义状态

  • $dp[i]$表示在区间$nums[0....i]$区间范围内的最长上升子序列的长度

2020-05-18_082043.png
  • 比较索引$i$与其前面出现的某个位置$j$的大小

    • 当$nums[i]<=nums[j]$,说明$j$处的值要比$i$处的值的,要是形成子序列则是$nums[0...j,i]$这样的结果,这时候$j$到$i$不能形成递增,以$i$结尾的子序列所形成的最长子序列的长度等价于以$j$结尾的子序列所形成的最长子序列的长度,即$dp[i]=dp[j]$

    • 当$nums[i]>nums[j]$,说明$j$处的值小于$i$处值,形成的子序列是$nums[0...j,i]$这样的结果,这时候从$j$到$i$是递增的,这时候需要在长度上+1,即$dp[i]=dp[j+1]$

    • 取上述两种情况的$max$,动态转移方程为: $dp[i] = Math.max(dp[i], dp[j] + 1)|0<=j<i<n$

    • 举例:如果遍历到$i$位置,在$[0-i]$ 区间内有$[0-j] j<i$ 当$nums[i]<=nums[j]$时,表示以$j$结束的子序列和$i$结束的子序列不能形成上升子序列,举 例:$[1,4,5,7,6,8]$,当$i$在$index$为$4$的位置,也就是$nums[i] =6$ ,$j $为$index$ 为$3$时,$nums[j] =7$ ,以$nums[j] $和$nums[i]$ 不能形成一个上升子序列

边界条件

  • 当$nums[0...i]$只有一个元素时,即以这一个元素结尾的子序列,最长上升子序列是其自身,为$1$

核心代码

方法1:DP

方法2:贪心+二分

  • 准备一个辅助数组$tails$,其中$tails[i]$表示长度为$i+1$即$nums[0...i]$的序列尾部元素的值

  • 辅助数组$tails$一定是严格单调递增的,即在$nums[0...j..i]$区间上,$tails[j]<tails[i]$,下面使用 反证法证明这个结论

    • 假设$nums[0...j..i]$这个区间上,$tails[j]>=tails[i]$,从$i$这个子序列向前删除$i-j$个元素,这时候长度变为$j$的子序列,这时候的尾部元素的值为$x$

    • 根据$tails$数组的定义,$x<tails[i]$

    • 而$x$又是$tails[j]$的值,即$x=tails[j]$

    • 得出$tails[i]>tails[j]$,这与一开始假设的条件矛盾,假设条件不成立

方法1:朴素版DP

  • dp[N][T+1],其中N是子集数组的大小,T是目标和,多放一个,从0开始的

  • dp[i][j]表示在子集数组的区间范围内[0...i]之间选择若干个数,可以组成j

    • j=0的时候,dp[i][0]true,当不选任何子集数组的数的时候,可以形成一直方案

    • i=0的时候,dp[0][j] 指的是当选第0个数的时候,能否等于j,显然在nums[0]=j的时候满足这种条件,其他都为false

  • 一般情况,dp[i][j]对于,第i个数,有这两种情况:

    • j>=nums[i]的时候,说明j还可以拆解,可以选或者不选i这个数,只要有一种方案是true即可

      • 不选:dp[i][j]= dp[i-1][j]

      • 选:dp[i][j]=dp[i-1][j-nums[i]]

    • j<nums[i]的时候,说明j不可以拆解,我们肯定选不到i这个数了,也就是dp[i][j]=dp[i-1][j]

总结就是:

  • dp[N-1][T]即答案

打印

dp[3][11]往上走到dp[2][11] 这一行用掉11 得到11-11 =0 结束

dp[3][11]往左走到dp[3][6],这一行用掉5 得11-5 = 6 dp[3][6]一直往上走到dp[1][6]减去这一行的5 得到 6 -5 =1 到dp[1][1] 一直往上走到dp[0][1] 减去这一行的1 1-1 =0 结束 5 5 1 是形成11的一个组合

方法2:空间压缩O(1)DP

关于倒序的遍历的问题:参考这篇:背包问题之 01 背包问题(科普文,基础,背包九讲)

方法0:朴素版回溯

TLE,这种回溯的思路是值得借鉴的

方法1:回溯+状态压缩

  • 1 <= maxChoosableInteger <= 20数据范围,想到可能需要状态压缩

方法2:回溯+HashMap

国际站的高赞的解法,链接

  • 另,使用数组做key

TLE

方法1:暴力递归

  • 经典的种子题

方法2:自顶向下记忆化递归(Top-down)

方法3:自底向上填表DP(Bottom-up)

方法4:矩阵相乘+快速幂

方法1:状态压缩+BFS

方法2:记忆化DFS

方法3:位运算+DP

方法1:三维数组+DP

方法2:二维数组+DP

  • 动态规划的解法,类似题目有970

  • sum[i]表示s[0...i]区间的字符产生的引力值

  • pos[c]表示字符c最近一次出现的位置

  • 优化

  • 优化

    • 记录每个字母上一次的下标,累加贡献,设定贡献为一个字母在子字符串第一次出现时产生贡献,后续重复出现不贡献;

    • 则每次贡献为 左边有 i - lastpos 种选法,右边有 n - i 种选法;

    • 初始位置设为-1;

  • dp的思路

Last updated