动态规划
方法1:暴力递归
helper(String s, int start, int end)表示
s从start到end位置,是否有回文子串
base case:start == end: 相等时,说明只有一个字符了,返回Tstart +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中,从i到j是否有回文子串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:记忆化递归
用
Index的enum来记录某个坐标是否可以到达最末尾的位置,有三类值: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的位置不越界并且leftPos和rightPos未被访问过访问后要设置下
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”则是其子串

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

比较索引$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
国际站的高赞的解法,链接
format的技巧有点类似: 将矩阵当成二进制转化成十进制
另,使用数组做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