赞
踩
1.确定dp数组以及下标的含义
dp[i]: 爬到第i层楼梯,有dp[i]种方法
2.确定递推公式
如果可以推出dp[i]呢?
从dp[i]的定义可以看出,dp[i] 可以有两个方向推出来。
首先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种方法,那么再一步跳一个台阶不就是dp[i]了么。
还有就是dp[i - 2],上i-2层楼梯,有dp[i - 2]种方法,那么再一步跳两个台阶不就是dp[i]了么。
那么dp[i]就是 dp[i - 1]与dp[i - 2]之和。
因为到dp就两种了。dp[i-1]再走一步。dp[i-2],再走两步。
3.dp数组如何初始化
不初始化第0步,从第1和第2步开始。
4.确定遍历顺序
从递推公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,遍历顺序一定是从前向后遍历的
- class Solution {
- public int climbStairs(int n) {
- int dp[]=new int[n+1];
- if(n==1){
- return 1;
- }
- if(n==2){
- return 2;
- }
- int i=3;
- dp[1]=1;
- dp[2]=2;
- while(i<n+1){
- dp[i]=dp[i-1]+dp[i-2];
- i++;
- }
- return dp[n];
- }
-
- }
- class Solution {
- public int minCostClimbingStairs(int[] cost) {
- int[] dp=new int[cost.length+1];
- if(cost.length==1){
- return cost[0];
- }
- if(cost.length==2){
- return Math.min(cost[0],cost[1]);
- }
-
-
- dp[0]=cost[0];
- dp[1]=cost[1];
-
-
- int i=2;
- while(i<cost.length){
- dp[i]=Math.min(dp[i-1],dp[i-2])+cost[i];//dp[i],记录i之前最小的与i位置继续跳的花费。
- i++;
-
- }
- //最后一步需要考虑一下。最后两步应该跳哪个。
- return Math.min(dp[cost.length-1],dp[cost.length-2]);
-
-
- }
- }
- class Solution {
- public int uniquePaths(int m, int n) {
- //设dp路径的个数。
- int[][] dp=new int[m][n];
- //dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条。同理。到dp[1][0],的路径也只有一条。因为不可能再改道了,是一直往下走到那的。如果换方向就到不了,因为不能往左走。
- for (int i = 0; i < m; i++) dp[i][0] = 1;
- for (int j = 0; j < n; j++) dp[0][j] = 1;
-
- for(int i=1;i<m;i++){
- for(int j=1;j<n;j++){
- dp[i][j]=dp[i-1][j]+dp[i][j-1];//这俩没地方可走了。
-
- }
- }
- return dp[m-1][n-1];
-
- }
- }
- class Solution {
- //[i][j]上如果有石头了,dp[i][j]就等于0吧。
- public int uniquePathsWithObstacles(int[][] obstacleGrid) {
- int m=obstacleGrid.length;
- int n=obstacleGrid[0].length;
- int [][] dp=new int[m][n];
- if(obstacleGrid[0][0]==1){
- return 0;
- }
-
-
- for (int i = 0; i < m; i++){//如果横的碰到个障碍物,那剩下的都走不了了。
- if(obstacleGrid[i][0]!=1){
- dp[i][0] = 1;
-
- }else{
- break;
-
- }
-
-
-
- }
- for (int j = 0; j < n; j++){
- if(obstacleGrid[0][j]!=1){//如果横的碰到个障碍物,从这点往后的都走不了了。
- dp[0][j] = 1;
-
- }else{
- break;
- }
-
- }
-
- for(int i=1;i<m;i++){
- for(int j=1;j<n;j++){
- if(obstacleGrid[i][j]!=1){
- dp[i][j]=dp[i-1][j]+dp[i][j-1];//如果[i][j-1]上有石头,那这个时候的路径就是只有dp[i-1][j]的。
-
- }
-
- }
- }
- return dp[m-1][n-1];
-
-
- }
- }
- class Solution {
- //错误思路:直接开根号,然后取余直接完事。乘积最大化,肯定是从开根号那里乘。
- public int integerBreak(int n) {
- //dp[0],dp[1]怎么设置呢?不设置d[0],d[1],这俩没有什么意义。也不会输入0和1。
- int [] dp=new int[n+1];
-
- dp[2]=1;
- //i-j,最低能到2。dp[2],拆成两项以上的,所以dp[2]=1。
- //同时j的上限也是i-2。保证i-j,最低是2,最低dp[2]。
- if(n>=3)
- {
- for(int i=3;i<=n;i++){
- for(int j=1;j<i-1;j++){
- dp[i]=Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));//从前到后吧dp[i],求出来,因为dp[n],得用它推导。
- //得拆成两项以上。如果直接弄成dp[i-j]*dp[j],就要拆成4项以上了。
-
- }
-
- }
-
- }
- return dp[n];
-
- }
- }
二叉搜索树的不同,就是根的左右子树结点不同。只看结点的布局,不用看值的差异。
- class Solution {
- public int numTrees(int n) {
- // 从1到n数的先后输入顺序,也会导致建树不同。
- //大佬的思路:假设n个节点存在二叉排序树的个数是G(n),1为根节点,2为根节点,...,n为根节点,当1为根节点时,其左子树节点个数为0,右子树节点个数为n-1,同理当2为根节点时,其左子树节点个数为1,右子树节点为n-2,所以可得G(n) = G(0)*G(n-1)+G(1)*G(n-2)+...+G(n-1)*G(0)
- int [] G=new int[n+1];//得用到下标G(n)
- G[0]=1;
- G[1]=1;
- for(int i=2;i<n+1;i++){
- for(int j=1;j<=i;j++){
- G[i]+=G[j-1]*G[i-j];
- }
- }
-
- return G[n];
-
- }
- }
有n件物品和一个最多能背重量为w的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
1.设置dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
2.确定递推公式,
那么可以有两个方向推出来dp[i][j],
所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
3.确定初始值。
当背包剩余容量为0时,dp[i][0]=0;
当放0号物品的时候,如果d[0][j],j的容量大于0号物品的重量。就让d[0][j]=weight[0]。
所以,如此初始化。
在java里,数组的初始值就是0。所以只需要设置d[0][j]。
- for (int j = 0 ; j < weight[0]; j++) { // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。
- dp[0][j] = 0;
- }
- // 正序遍历
- for (int j = weight[0]; j <= bagweight; j++) {
- dp[0][j] = value[0];
- }
- public static void testweightbagproblem(int[] weight, int[] value, int bagsize){
- int wlen = weight.length, value0 = 0;
- //定义dp数组:dp[i][j]表示背包容量为j时,前i个物品能获得的最大价值
- int[][] dp = new int[wlen + 1][bagsize + 1];
- //初始化:背包容量为0时,能获得的价值都为0
- for (int i = 0; i <= wlen; i++){
- dp[i][0] = value0;
- }
- //遍历顺序:先遍历物品,再遍历背包容量
- for (int i = 1; i <= wlen; i++){
- for (int j = 1; j <= bagsize; j++){
- if (j < weight[i - 1]){
- dp[i][j] = dp[i - 1][j];
- }else{
- dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
- }
- }
- }
一维滚动数组。
- public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
- int wLen = weight.length;
- //定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
- int[] dp = new int[bagWeight + 1];
- //遍历顺序:先遍历物品,再遍历背包容量
- for (int i = 0; i < wLen; i++){
- for (int j = bagWeight; j >= weight[i]; j--){
- dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
- }
- }
把nums[i]的值,看作重量的值和value的值。
- class Solution {
- public boolean canPartition(int[] nums) {
-
- int sum=0;
- for(int num:nums){
- sum+=num;
- }
- // 特判:如果是奇数,就不符合要求
- if ((sum %2 ) != 0) {
- return false;
- }
- int rong=sum/2;
-
- int[][] dp =new int [nums.length][rong+1];
-
- for(int j=nums[0];j<=rong;j++){
- dp[0][j]=nums[0];
- }
- for(int i=1;i<nums.length;i++){
- for(int j=0;j<=rong;j++){
- if(j<nums[i]) {
- dp[i][j]=dp[i-1][j];
-
- }else{
- dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-nums[i]]+nums[i]);
-
- }
-
- }
- }
- //当走完了i,容量为rong的时候,如果里面这个值,正好是rong,也就是sum/2。说明找到了。
- if(dp[nums.length-1][rong]==rong){
- return true;
- }else{
- return false;
- }
-
-
- }
- }
- class Solution {
- public boolean canPartition(int[] nums) {
- int sum=0;
-
- for(int i=0;i<nums.length;i++)
- {
- sum+=nums[i];
- }
- int target=sum/2;
- double a=sum-target;
- if(a!=target) return false;
-
- int [] dp=new int [target+1];
- //背包容量为 target
-
- //一维背包,初始值。
- dp[0]=0;
-
- for(int i=0;i<nums.length;i++)
- {
- //一维背包,j的终点 是nums[i]。
- for(int j=target;j>=nums[i];j--)
- {
- dp[j]=Math.max(dp[j],dp[j-nums[i]] +nums[i]);
- }
- }
- if(dp[target]==target)
- {
- return true;
- }else {
- return false;
- }
-
- }
- }
思路:本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
然后 结果=sum-2*dp数组最后的长度 即可。
这题的代码正好解决了这一类的问题,背就完事。
- class Solution {
- public int findTargetSumWays(int[] nums, int target) {
- int sum =0;
- for(int num:nums){
- sum+=num;
- }
- //过程逆过来,留下来的数的和。留下来-减去的=target。留下来+减去的=sum。
-
- //所以 留下来的=(target+sum)/2。这个数必须得是整数
- if((target+sum)%2==1){
- return 0;
- }
- //如果目标值的绝对值大于sum,也没办法。
- if(Math.abs(target)>sum){
- return 0;
- }
-
- //此时问题就转化为,装满容量为x背包,有几种方法。回溯肯定超时。
- //dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。
- int left=(target+sum)/2;
- int [][] dp=new int [nums.length][left+1];
-
- //初始化
- dp[0][0]=1;
-
- //只装第0个物品的目标也是给背包装满。装满了才算一种。
- //dp[0][j],看第一个元素的大小情况,进行赋值1(如果第一个元素为0.则dp[0][0]应该为2)。
- for(int j=0;j<left+1;j++){
- if(j==nums[0]){
- dp[0][j]+=1;
- }
- }
-
- for(int i=1;i<nums.length;i++){
- //这里j从0开始。
- for(int j=0;j<left+1;j++){
- //不拿/拿 第i件物品的方案数
- if(j<nums[i]){
- dp[i][j]=dp[i-1][j];
- }else{
- dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i]];
- }
-
- }
- }
- return dp[nums.length-1][left];
-
-
-
-
-
-
-
- }
- }
一维数组的
- class Solution {
- public int findTargetSumWays(int[] nums, int target) {
- int sum = 0;
- for (int i = 0; i < nums.length; i++) sum += nums[i];
- if ((target + sum) % 2 != 0) return 0;
- int size = (target + sum) / 2;
- if(size < 0) size = -size;
- int[] dp = new int[size + 1];
- dp[0] = 1;
- for (int i = 0; i < nums.length; i++) {
- for (int j = size; j >= nums[i]; j--) {
- dp[j] += dp[j - nums[i]];
- }
- }
- return dp[size];
- }
- }
记得这些代码有什么作用就行,不用管每一步怎么来的。
题目简要说明:找到的字符串,里面都会有0.1,
让找的字符串集合里最多有m个0,n个1。这个字符串集合尽量要大。
而m 和 n相当于是一个背包,两个维度的背包
- class Solution {
- public int findMaxForm(String[] strs, int m, int n) {
- int len =strs.length;
- int [][] nums = new int [len][2];
- //首先遍历一遍,把每个字符串0和1的数量存进新数组,然后用双重背包即可。
- for(int i=0;i<strs.length;i++)
- {
- int a=finZero(strs[i]);
- int b=findOne(strs[i]);
- nums[i][0]=a;
- nums[i][1]=b;
- }
- int [][] dp =new int [m+1][n+1];
- for(int i=0;i<len;i++)
- {
- for(int j=m;j>=nums[i][0];j--)
- {
- for(int k=n;k>=nums[i][1];k--)
- {
- //这个递推公式是求个数,所以是+1
- dp[j][k]=Math.max(dp[j][k],dp[j-nums[i][0]][k-nums[i][1]]+1);
- }
- }
- }
- return dp[m][n];
- }
-
- //这两个函数,发现每个字符串的0,1数量
- int findOne(String s)
- {
- int cnt=0;
- for(int i=0;i<s.length();i++)
- {
- if(s.charAt(i)=='1') cnt++;
- }
- return cnt;
- }
- int finZero(String s)
- {
- int cnt=0;
- for(int i=0;i<s.length();i++)
- {
- if(s.charAt(i)=='0') cnt++;
- }
- return cnt;
- }
-
- }
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
- //先遍历物品,再遍历背包
- private static void testCompletePack(){
- int[] weight = {1, 3, 4};
- int[] value = {15, 20, 30};
- int bagWeight = 4;
- int[] dp = new int[bagWeight + 1];
- for (int i = 0; i < weight.length; i++){ // 遍历物品
- for (int j = weight[i]; j <= bagWeight; j++){ // 遍历背包容量
- dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
- }
- }
- for (int maxValue : dp){
- System.out.println(maxValue + " ");
- }
- }
-
- //先遍历背包,再遍历物品
- private static void testCompletePackAnotherWay(){
- int[] weight = {1, 3, 4};
- int[] value = {15, 20, 30};
- int bagWeight = 4;
- int[] dp = new int[bagWeight + 1];
- for (int i = 1; i <= bagWeight; i++){ // 遍历背包容量
- for (int j = 0; j < weight.length; j++){ // 遍历物品
- if (i - weight[j] >= 0){
- dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);
- }
- }
- }
- for (int maxValue : dp){
- System.out.println(maxValue + " ");
- }
- }
一维数组的。太啥比,就是把本章第10题的,内层for的遍历顺序调反即可。
- class Solution {
- public int change(int amount, int[] nums) {
- int [] dp=new int [amount+1];
- dp[0] = 1;
- for (int i = 0; i < nums.length; i++) {
- for (int j = nums[i]; j <amount+1; j++) {
- dp[j] += dp[j - nums[i]];
- }
- }
- return dp[amount];
-
-
- }
- }
组合不强调顺序,(1,5)和(5,1)是同一个组合。
排列强调顺序,(1,5)和(5,1)是两个不同的排列。
看题目,本题其本质是本题求的是排列总和,而且仅仅是求排列总和的个数
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
此时当外层for遍历背包时,得让j从0开始。
- class Solution {
- public int combinationSum4(int[] nums, int target) {
- int[] dp=new int[target+1];
- dp[0]=1;
- for(int i=0;i<target+1;i++){
- for(int j=0;j<nums.length;j++){
- if(i>=nums[j])//外层遍历容量时,里面容量i必须大于nums[j]
- dp[i]+=dp[i-nums[j]];//此时外层遍历背包容量时,写成这样
- }
- }
- return dp[target];
-
- }
- }
比如上4楼,112和221是两种排列。用完全背包。
这题可以看出,当dp求最多时,除了dp[0],都会初始化成0,来递推比较,在递推公式那里用max函数比较。
当dp求最少的时候,除了除了dp[0],都会初始化成int的MAX_VALUE,在递推公式那里用min函数比较。
- class Solution {
- public int coinChange(int[] coins, int amount) {
- int[] dp=new int[amount+1];//最少需要的硬币数
-
- int max=Integer.MAX_VALUE;
- //初始化dp数组为最大值
- for (int j = 0; j < dp.length; j++) {
- dp[j] = max;
- }
- dp[0]=0;
- for(int i=0;i<coins.length;i++){
- for(int j=coins[i];j<amount+1;j++){
- if(dp[j-coins[i]]!=max){
- //只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要
- dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
-
- }
-
- }
-
- }
- //如果没有通往前面的道路,那dp[amount]的值不会到dp[0],而是保持初始值
- if(dp[amount]==max){
- return -1;
- }else{
- return dp[amount];
-
- }
-
-
- }
- }
- class Solution {
- //我们拿的数得是平方的数。
- public int numSquares(int n) {
- int[] dp=new int[n+1];
-
- int max=Integer.MAX_VALUE;
- //初始化dp数组为最大值
- for (int j = 0; j < dp.length; j++) {
- dp[j] = max;
- }
- dp[0]=0;
- //这里的难点就是把nums[i],转化为i*i
- for(int i=0;i*i<=n;i++){
- for(int j=i*i;j<=n;j++){
- if(dp[j-i*i]!=max){
- //只有dp[j-i*i]不是初始最大值时,该位才有选择的必要
- dp[j]=Math.min(dp[j],dp[j-i*i]+1);
-
- }
-
- }
-
- }
- return dp[n];
-
- }
- }
此题类似于分割回文串。
思路:挨个判断,S里的字符串是不是在字典里都出现过。
在for里创造一个[j,i)的子串区间,判断这个子串是否在字典里出现过。
- class Solution {
- //思路:挨个判断,S里的字符串是不是在字典里都出现过。
- public boolean wordBreak(String s, List<String> wordDict) {
- boolean [] dp=new boolean[s.length()+1];
- //默认为false了。因此你要把true往后传递呀
- dp[0]=true;
- //得用sbstring方法,i肯定在右侧,i-1得>=0,所以让i从1开始,j从0开始,
- for(int i=1;i<=s.length();i++){
- //做右界,所以范围为1-
- for(int j=0;j<i;j++){
- //之前访问到j这里也得是true,必须得所有的单词都在字典里
- if(wordDict.contains(s.substring(j,i))&&dp[j]==true){
- dp[i]=true;
- }
- }
- }
- return dp[s.length()];
-
-
- }
- }
有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。
比如说这种题是多重背包。
我们可以把它摊开 ,转化成了01背包的问题。
重新建一个数组,将3维变成二维。
- public void testMultiPack1(){
- // 版本一:改变物品数量为01背包格式
- List<Integer> weight = new ArrayList<>(Arrays.asList(1, 3, 4));
- List<Integer> value = new ArrayList<>(Arrays.asList(15, 20, 30));
- List<Integer> nums = new ArrayList<>(Arrays.asList(2, 3, 2));
- int bagWeight = 10;
-
- for (int i = 0; i < nums.size(); i++) {
- while (nums.get(i) > 1) { // 把物品展开为i
- weight.add(weight.get(i));
- value.add(value.get(i));
- nums.set(i, nums.get(i) - 1);
- }
- }
-
- int[] dp = new int[bagWeight + 1];
- for(int i = 0; i < weight.size(); i++) { // 遍历物品
- for(int j = bagWeight; j >= weight.get(i); j--) { // 遍历背包容量
- dp[j] = Math.max(dp[j], dp[j - weight.get(i)] + value.get(i));
- }
- System.out.println(Arrays.toString(dp));
- }
- }
题目描述:每家都有钱,不能连续偷两家。
分析:
1.如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,即:第i-1房一定是不考虑的,找出 下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为dp[i-2] 加上第i房间偷到的钱。
2.如果不偷第i房间,那么dp[i] = dp[i - 1],即考虑i-1房,(注意这里是考虑)
然后dp[i]取最大值,即dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
从递推公式dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);可以看出,递推公式的基础就是dp[0] 和 dp[1]
,选好出发点。
从dp[i]的定义上来讲,dp[0] 一定是 nums[0],dp[1]就是nums[0]和nums[1]的最大值即:dp[1] = max(nums[0], nums[1]);
这里没有背包,不是背包的题,所以只需要遍历数组即可。
- // 动态规划
- class Solution {
- public int rob(int[] nums) {
- if (nums == null || nums.length == 0) return 0;
- if (nums.length == 1) return nums[0];
-
- int[] dp = new int[nums.length];
- dp[0] = nums[0];
- dp[1] = Math.max(dp[0], nums[1]);
- for (int i = 2; i < nums.length; i++) {
- dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
- }
-
- return dp[nums.length - 1];
- }
- }
题目表述:要偷的房子围成了一圈,首位相邻,不能偷相邻的屋子。
这题还是得动态规划,不能省事只算不相邻。
成环的情况可以分为三种处理。
成环问题:情况二,三包括情况1。
所以我们按照下标建两个数组,比较它们的结果即可。
- class Solution {
- public int rob(int[] nums) {
-
- if (nums == null || nums.length == 0)
- return 0;
-
- int len = nums.length;
- if (len == 1)
- return nums[0];
- return Math.max(work(nums, 0, len-2), work(nums, 1, len-1));
-
- }
- int work(int[] p,int start,int end){
-
- if(start==end){
- return p[start];
- }
- if(end-start==1){
- return Math.max(p[start],p[end]);
- }
-
- int[] nums=new int[end-start+1];
- int index=0;
- for(int i=start;i<=end;i++){
- nums[index]=p[i];
- index++;
- }
-
-
- //新建一个数组nums。此时就可以看成,可以忽略首尾相连的了。
-
- int[] dp=new int[nums.length];
- dp[0]=nums[0];
- dp[1]=Math.max(nums[0],nums[1]);
- for(int i=2;i<nums.length;i++){
- dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
- }
- return dp[nums.length-1];
-
-
- }
- }
经过仔细分析(手动严肃脸),正确的解题思路大致是这样的:
对于一个以 node 为根节点的二叉树而言,如果尝试偷取 node 节点,那么势必不能偷取其左右子节点,然后继续尝试偷取其左右子节点的左右子节点。
如果不偷取该节点,那么只能尝试偷取其左右子节点。
比较两种方式的结果,谁大取谁。
暴力递归
- class Solution {
- // 1.递归去偷,超时
- public int rob(TreeNode root) {
- if (root == null)
- return 0;
- int money = root.val;
- if (root.left != null) {
- money += rob(root.left.left) + rob(root.left.right);
- }
- if (root.right != null) {
- money += rob(root.right.left) + rob(root.right.right);
- }
- return Math.max(money, rob(root.left) + rob(root.right));
- }
- }
我们计算了root的四个孙子(左右孩子的孩子)为头结点的子树的情况,又计算了root的左右孩子为头结点的子树的情况(return 那里又计算了)。
所以计算左右孩子的时候其实又把孙子计算了一遍。复杂度为n的平方。
所以我们剪枝,如果这个值已经被计算过了,那就直接拿来用,不用再重新计算。
- //后序遍历,用儿子的与父亲比较,如果父亲的大,则拿父亲的钱,则下一步去找父亲的孙子。
- class Solution {
- Map<TreeNode,Integer> map=new HashMap<>();
-
- public int rob(TreeNode root) {
- if(root==null){
- return 0;
- }
- if(map.containsKey(root)){
- return map.get(root);
- }
- int money=root.val;
- if(root.left!=null){
- money+=rob(root.left.left)+rob(root.left.right);
- }
- if(root.right!=null){
- money+=rob(root.right.left)+rob(root.right.right);
- }
-
- int res=Math.max(money,rob(root.left)+rob(root.right));
- map.put(root,res);
- return res;
-
- }
-
-
- }
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
1.贪心算法的做法
- class Solution {
- //选左侧最小值,然后一边遍往右走,用右侧最大值减去最小值。
- public int maxProfit(int[] prices) {
- int low=Integer.MAX_VALUE;
- int res=0;
- for(int i=0;i<prices.length;i++){
- low=Math.min(prices[i],low);
- res=Math.max(prices[i]-low,res);
- }
- return res;
-
-
- }
- }
2.动态规划的做法
思路:确定dp,dp[i][0] 表示第i天持有股票所得最多现金,持有的概念就是自己还有股在身上。
dp[i][1] 表示第i天不持有股票所得最多现金,自己手里没有股票。
如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来
那么dp[i][0]应该选所得现金最大的,所以dp[i][0] = max(dp[i - 1][0], -prices[i]);
如果第i天不持有股票即dp[i][1], 也可以由两个状态推出来
同样dp[i][1]取最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
所以我们得初始化俩值。dp[0][0]和dp[0][1],
那么dp[0][0]表示第0天持有股票,此时的持有股票就一定是买入股票了,因为不可能有前一天推出来,所以dp[0][0] = -prices[0];
dp[0][1]表示第0天不持有股票,不持有股票那么现金就是0,所以dp[0][1] = 0;
确定遍历顺序
从递推公式可以看出dp[i]都是有dp[i - 1]推导出来的,那么一定是从前向后遍历。
- class Solution {
-
- public int maxProfit(int[] prices) {
- int [][] dp=new int[prices.length][2];
- dp[0][0]=-prices[0];
- dp[0][1]=0;
- for(int i=1;i<prices.length;i++){
- dp[i][0]=Math.max(dp[i-1][0],-prices[i]);
- dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
- }
- return dp[prices.length-1][1];
-
-
- }
- }
在这一周里可以你可以尽可能地完成更多的交易(多次买卖一支股票)。一个星期可以交易好几次,与上一题不同,上一次只能买一次。
这里重申一下dp数组的含义:
如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来
买新的时候,必须得之前不持有。在第i天持有时,还可能有之前赚的钱在。
- class Solution {
- public int maxProfit(int[] prices) {
- int [][] dp=new int[prices.length][2];
- dp[0][0]=-prices[0];
- dp[0][1]=0;
- for(int i=1;i<prices.length;i++){
- //与第一题不同,这里改了
- dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
- dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
- }
- return dp[prices.length-1][1];
-
- }
- }
这次给了局限,你最多只能完成两次交易。
1.确定dp数组有以下几种状态。
0没有操作(没有持有)
1第一次买入(第一次持有)
2第一次卖出(第一次不持有)
3第二次买入 (第二次持有)
4第二次卖出 (第二次不持有)
dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。
需要注意:dp[i][1],表示的是第i天,买入股票的状态,并不是说一定要第i天买入股票(即第一次持有)
2.达到dp[i][1]状态,有两个具体操作:
同理dp[i][2]也有两个操作:
所以dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])。
所以
dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
3.确定初始值。
dp[0][0] = 0;第0天也没操作。
dp[0][1] = -prices[0];
dp[0][2]=0;//今天买今天卖。
dp[0][3]=-prices[0];//又买一遍。
dp[0][4]==0;
4.确定遍历顺序
从递归公式其实已经可以看出,一定是从前向后遍历,因为dp[i],依靠dp[i - 1]的数值。
- class Solution {
- public int maxProfit(int[] prices) {
- int[][] dp=new int[prices.length][5];
- dp[0][0]=0;
- dp[0][1]=-prices[0];
- dp[0][2]=0;
- dp[0][3]=-prices[0];
- dp[0][4]=0;
-
- for(int i=1;i<prices.length;i++){
- dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
- dp[i][2]=Math.max(dp[i-1][2],dp[i-1][1]+prices[i]);
- dp[i][3]=Math.max(dp[i-1][3],dp[i-1][2]-prices[i]);
- dp[i][4]=Math.max(dp[i-1][4],dp[i-1][3]+prices[i]);
-
- }
- return dp[prices.length-1][4];
-
- }
- }
上一题的两笔改成k笔。
- class Solution {
- public int maxProfit(int k, int[] prices) {
- if (prices.length == 0) return 0;
- int[][] dp=new int[prices.length][2*k+1];
- //初始化数组。
- for(int i=0;i<=2*k;i++){
- if(i%2==0){
- dp[0][i]=0;
-
- }else{
- dp[0][i]=-prices[0];
- }
- }
- for(int i=1;i<prices.length;i++){
- //改造了一下,只能交易二次的那一题
- for(int j=1;j<=2*k;j++){
- if(j%2==1){
- dp[i][j]=Math.max( dp[i - 1][j],dp[i-1][j-1] - prices[i]);
- }else{
- dp[i][j]=Math.max( dp[i - 1][j],dp[i-1][j-1] +prices[i]);
- }
-
- }
-
- }
- return dp[prices.length-1][2*k];
-
- }
- }
之前多次买卖同一只股票,这里如果今天卖了,那么明天不能买,得隔一天才能买。这次的买入,距离上次的卖出,得中间隔着一天。
思路:分为四种状态。
达到持有股票状态(状态一)即:dp[i][0],有两个具体操作:
达到保持卖出股票状态(状态二)即:dp[i][1],有两个具体操作:
dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
达到今天就卖出股票状态(状态三),即:dp[i][2] ,只有一个操作:
即:dp[i][2] = dp[i - 1][0] + prices[i];
达到冷冻期状态(状态四),即:dp[i][3],只有一个操作:
dp[i][3] = dp[i - 1][2];
综上所述
- dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3], dp[i - 1][1]) - prices[i]);
- dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
- dp[i][2] = dp[i - 1][0] + prices[i];
- dp[i][3] = dp[i - 1][2];
这里主要讨论一下第0天如何初始化。
如果是持有股票状态(状态一)那么:dp[0][0] = -prices[0],买入股票所剩现金为负数。
保持卖出股票状态(状态二),第0天没有卖出dp[0][1]初始化为0就行,
今天卖出了股票(状态三),同样dp[0][2]初始化为0,因为最少收益就是0,绝不会是负数。
同理dp[0][3]也初始为0。
最后结果是取 状态二,状态三,和状态四的最大值,不少同学会把状态四忘了,状态四是冷冻期,最后一天如果是冷冻期也可能是最大值。
- class Solution {
- public int maxProfit(int[] prices) {
- int[][] dp=new int [prices.length][4];
- dp[0][0]=-prices[0];
- dp[0][1]=0;
- dp[0][2]=0;
- dp[0][3]=0;
- for(int i=1;i<prices.length;i++){
- dp[i][0] = Math.max(dp[i - 1][0], Math.max(dp[i - 1][3], dp[i - 1][1]) - prices[i]);
- dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][3]);
- dp[i][2] = dp[i - 1][0] + prices[i];
- dp[i][3] = dp[i - 1][2];
- }
- //最后结果是取 状态二,状态三,和状态四的最大值,是冷冻期也可能是最大值。
- return Math.max(dp[prices.length-1][1],Math.max(dp[prices.length-1][2],dp[prices.length-1][3]));
-
-
- }
- }
- class Solution {
- public int maxProfit(int[] prices, int fee) {
-
- int [][] dp=new int[prices.length][2];
- dp[0][0]=-prices[0];
- dp[0][1]=0;//表示第1天不持有股票所得最多现金
- for(int i=1;i<prices.length;i++){
- //的区别就是这里需要多一个减去手续费的操作。
- dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
- dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]-fee);
- }
- return dp[prices.length-1][1];
-
- }
- }
我没用动态规划的方法做,错了。这题应该用动态规划。
dp[i]表示i之前包括i的以nums[i]结尾最长上升子序列的长度.
位置 i 的最长升序子序列 等于 j从0到i-1各个位置的最长升序子序列 + 1 的最大值。
dp[i],所有的位置上初始值都为1。因为包括它们自身。
- class Solution {
- public int lengthOfLIS(int[] nums) {
- if(nums==null||nums.length==0){
- return 0;
- }
- if(nums.length==1){
- return 1;
- }
- int[] dp=new int[nums.length];
- Arrays.fill(dp,1);
-
- for(int i=1;i<nums.length;i++){
- for(int j=0;j<=i-1;j++){
- //我现在在位置i,如果我比这之前的有个数要大,那么长度就为这个数的最长+1。
- if(nums[i]>nums[j]){
- dp[i]=Math.max(dp[i],dp[j]+1);
- }
- }
-
-
- }
- //最后一位,并不一定是最大长度
- int res=0;
- for(int i=0;i<dp.length;i++){
- res=Math.max(dp[i],res);
- }
- return res;
-
- }
- }
没用动态规划,因为太简单了。
- class Solution {
- public int findLengthOfLCIS(int[] nums) {
- if(nums==null||nums.length==0){
- return 0;
- }
- if(nums.length==1){
- return 1;
- }
-
- int pre=0;
- int maxLength=1;
- //记录一下最长连续增加即可。用pre表示左区间。
- for(int i=0;i<nums.length-1;i++){
- if(nums[i+1]>nums[i]){
- maxLength=Math.max(maxLength,i+1-pre+1);
- }else{
- pre=i+1;
- }
-
- }
- return maxLength;
-
- }
- }
-
-
-
我用双指针的暴力破解方法失败!
dp[i][j] :以下标i 为结尾的A,和以下标j 为结尾的B,最长重复子数组长度为dp[i][j]。
根据dp[i][j]的定义,dp[i][j]的状态只能由dp[i - 1][j - 1]推导出来。
即当A[i ] 和B[j ]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;
所以 i和j都要从1开始遍历。
初始化dp[i][0]与dp[0][j],因为dp[i][j]从这里推导出来。如果碰到相等的,让它们为1即可。
- for(int j=0;j<nums2.length;j++ ){
- if(nums1[0]==nums2[j]){
- dp[0][j]=1;
- res=1;
- }
- }
- for(int j=0;j<nums1.length;j++ ){
- if(nums1[j]==nums2[0]){
- dp[j][0]=1;
- res=1;
- }
- }
所以dp[i][0] 和dp[0][j]初始化为0。
并不是dp[i][j]里的i和j都取最大值时,才有最长重复子数组。dp[i][j]那里可能是0。
所以在这个过程需要一个res来记录。
完整代码如下
- class Solution {
- public int findLength(int[] nums1, int[] nums2) {
-
- int[][] dp=new int[nums1.length+1][nums2.length+1];
- int res=0;
- //因为 dp[i][j]=dp[i-1][j-1]+1;,i和j必须从1开始,
- for(int j=0;j<nums2.length;j++ ){
- if(nums1[0]==nums2[j]){
- dp[0][j]=1;
- res=1;
- }
- }
- for(int j=0;j<nums1.length;j++ ){
- if(nums1[j]==nums2[0]){
- dp[j][0]=1;
- res=1;
- }
- }
- for(int i=1;i<nums1.length;i++){
- for(int j=1;j<nums2.length;j++){
- if(nums1[i]==nums2[j]){
- dp[i][j]=dp[i-1][j-1]+1;
- res=Math.max(res,dp[i][j]);
- }
- }
- }
- return res;
-
-
- }
- }
不用两个for来定义初始值的版本,多给了一个长度。
- class Solution {
- public int findLength(int[] nums1, int[] nums2) {
- int result = 0;
- int[][] dp = new int[nums1.length + 1][nums2.length + 1];
-
- for (int i = 1; i < nums1.length + 1; i++) {
- for (int j = 1; j < nums2.length + 1; j++) {
- if (nums1[i - 1] == nums2[j - 1]) {
- dp[i][j] = dp[i - 1][j - 1] + 1;
- result = Math.max(result, dp[i][j]);
- }
- }
- }
-
- return result;
- }
- }
与最长重复子数组的区别是,里面的元素重复时,可以不是连续的。
处理两种情况,当这俩不相等时,你得取
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
- class Solution {
- public int longestCommonSubsequence(String text1, String text2) {
- int[][] dp=new int[text1.length()+1][text2.length()+1];
- //dp[i][j] : S1的0到i-1,S2的0到j-1 最长公共子序列长度。
-
- int res=0;
- for(int i=1;i<=text1.length();i++){
- for(int j=1;j<=text2.length();j++){
- if(text1.charAt(i-1)==text2.charAt(j-1)){
- dp[i][j]=Math.max(dp[i][j],dp[i-1][j-1]+1);
- res=Math.max(res,dp[i][j]);
-
- }else{
- dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j]);
- res=Math.max(res,dp[i][j]);
- }
-
- }
-
- }
- return res;
-
- }
- }
这题与最长公共子序列一摸一样。
- class Solution {
- public int maxUncrossedLines(int[] A, int[] B) {
- int [][] dp = new int[A.length+1][B.length+1];
- int res=0;
- for(int i=1;i<=A.length;i++) {
- for(int j=1;j<=B.length;j++) {
- if (A[i-1]==B[j-1]) {
- dp[i][j]=dp[i-1][j-1]+1;
- res=Math.max(dp[i][j],res);
- }
- else {
- dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1]);
- }
- }
- }
- //也可以返回dp[A.length][B.length];因为与最长公共子序列那题不同的是,它只走一遍。
- return res;
- }
- }
- class Solution {
- public int maxSubArray(int[] nums) {
- if(nums.length==1){
- return nums[0];
- }
- int [] dp=new int[nums.length];
- int res=nums[0];
- dp[0]=nums[0];
- //当dp[i]小于0了,就让它从下一个整数开始。
- for(int i=1;i<nums.length;i++){
- dp[i]=Math.max(nums[i],dp[i-1]+nums[i]);
- res=Math.max(res,dp[i]);
- }
-
- return res;
-
- }
- }
进阶:
如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
如果考察的是连续子序列,用KMP算法。
用动态规划吧。最长公共子序列那一套。如果最长公共子序列长度一样,则是。
- class Solution {
- public boolean isSubsequence(String s, String t) {
- int[][] dp=new int[s.length()+1][t.length()+1];
- //dp[i][j] : S1的0到i-1,S2的0到j-1 最长公共子序列长度。
-
- int res=0;
- for(int i=1;i<=s.length();i++){
- for(int j=1;j<=t.length();j++){
- if(s.charAt(i-1)==t.charAt(j-1)){
- dp[i][j]=Math.max(dp[i][j],dp[i-1][j-1]+1);
- res=Math.max(res,dp[i][j]);
-
- }else{
- dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j]);
- res=Math.max(res,dp[i][j]);
- }
-
- }
-
- }
- return (res==s.length());
- }
-
- }
错误思路:用最长公共子序列的方法,当dp里长度==子序列的时候+1。
dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。
2.这一类问题,基本是要分析两种情况
当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。
一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。
一部分是不用s[i - 1]来匹配(看作s[i - 1] 与 t[j - 1]不相等),个数为dp[i - 1][j]。因为t是s的子串
例如: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的,但是字符串s也可以不用s[3]来匹配,即用s[0]s[1]s[2]组成的bag。
所以当s[i - 1] 与 t[j - 1]相等时,dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
当s[i - 1] 与 t[j - 1]不相等时(真正的不相等),dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即:dp[i - 1][j]
因为已知t只能是s的子串,在前面最长公共子序列里取的是max(dp[i][j-1],dp[i-1][j]);因为不知道谁是谁的子串。
3.初始值怎么设置?
从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j];
当i等于0时,有dp[0][j],当j等0时,有dp[i-1][0]
中可以看出dp[i][0] 和dp[0][j]是一定要初始化的。
dp[0][j]肯定为0。此时S的 0减去1=-1 之前,t肯定匹配不上。
dp[i][0]。此时t之前的 下标为-1的串,匹配结果只有 1个。所以定为1。
- class Solution {
-
- public int numDistinct(String s, String t) {
- //dp[i][j]:以i-1为结尾的s的子序列 中 出现以j-1为结尾的t的子序列的个数为dp[i][j]。
- int[][] dp=new int[s.length()+1][t.length()+1];
- //dp[0][j]肯定为0。此时S的 0减去1=-1 之前,t肯定匹配不上。
- //dp[i][0]。此时t之前的 下标为-1的串,匹配结果只有 1个。所以定为1。
- for(int i=0;i<s.length();i++){
- dp[i][0]=1;
- }
- for(int i=1;i<=s.length();i++){
- for(int j=1;j<=t.length();j++){
- if(s.charAt(i-1)==t.charAt(j-1)){
- //左面加的是当作匹配上,右边加的是当作匹配不上
- dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
-
- }else{
- //确实匹配不上时
- dp[i][j]=dp[i-1][j];
- }
-
- }
- }
- //这个时候递推肯定最大
- return dp[s.length()][t.length()];
-
- }
- }
- class Solution {
- //找到最长公共子序列的长度,然后分别去除两个字符串中除了最长公共子序列的字符数。
- public int minDistance(String S1, String S2) {
- int[][] dp=new int[S1.length()+1][S2.length()+1];
- int res=0;
- for(int i=1;i<=S1.length();i++){
- for(int j=1;j<=S2.length();j++){
- if(S1.charAt(i-1)==S2.charAt(j-1)){
- dp[i][j]=Math.max(dp[i][j],dp[i-1][j-1]+1);
- res=Math.max(res,dp[i][j]);
- }else{
- dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
- }
- }
- }
- return S1.length()+S2.length()-2*res;
-
-
- }
- }
1.dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。
2.确定递推公式
- if (word1[i - 1] == word2[j - 1])
- 不操作
- if (word1[i - 1] != word2[j - 1])
- 增
- 删
- 换
if (word1[i - 1] == word2[j - 1])
那么说明不用任何编辑,dp[i][j]
就应该是 dp[i - 1][j - 1]
,即dp[i][j] = dp[i - 1][j - 1];
if (word1[i - 1] != word2[j - 1])
,此时就需要编辑了,如何编辑呢?
dp[i][j] = dp[i][j - 1] + 1;
操作三替换元素:,word1
替换word1[i - 1]
,使其与word2[j - 1]
相同,此时不用增加元素,那么以下标i-2
为结尾的word1
与 j-2
为结尾的word2
的最近编辑距离 加上一个替换元素的操作。 即 dp[i][j] = dp[i - 1][j - 1] + 1;//换成一样的了。
3.初始化问题,dp[i][0] 和 dp[0][j]肯定得用到。
那么dp[i][0] 和 dp[0][j] 表示什么呢?
dp[i][0]应该表示word1的子序列 0到i-1 全删光,共i个操作步骤。
dp[0][j]应该表示word1从头增加到word2子序列 0到j-1的长度,共j个步骤。
dp[0][0]=0;这俩都是空了都不用动手。
- class Solution {
- //将word1转换成word2所用最少操作数。
- public int minDistance(String S1, String S2) {
-
-
- //dp表示 S1的 0到i-1子串,与S2的0到j-1的子串的最短编辑距离。
- int[][] dp=new int[S1.length()+1][S2.length()+1];
- for(int i=0;i<=S1.length();i++){
- dp[i][0]=i;
- }
- for(int j=0;j<=S2.length();j++){
- dp[0][j]=j;
- }
-
- for(int i=1;i<=S1.length();i++){
- for(int j=1;j<=S2.length();j++){
- if(S1.charAt(i-1)==S2.charAt(j-1)){
- dp[i][j]=dp[i-1][j-1];
-
- }else{
- //求的是最小编辑距离
- dp[i][j]= Math.min(Math.min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+1);
- }
- }
- }
- return dp[S1.length()][S2.length()];
-
-
-
-
- }
- }
-
-
动态规划做法
布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
整体上是两种,就是s[i]与s[j]相等,s[i]与s[j]不相等这两种。
当s[i]与s[j]不相等,dp[i][j]一定是false。
当s[i]与s[j]相等时,有如下三种情况
dp[i + 1][j - 1],所以一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]都是经过计算的。
- class Solution {
- public int countSubstrings(String s) {
- //dp[i][j]表示i和j这个区间的是回文子串。
- boolean [][] dp=new boolean[s.length()][s.length()];
- int count=0;
- for(int i=s.length()-1;i>=0;i--){
- //j得大于等于i
- for(int j=i;j<s.length();j++){
- if(s.charAt(i)==s.charAt(j)){
- //当a和aa时,直接可以设置位true。
- if(j-i<=1){
- dp[i][j]=true;
- count++;
- }else{
- //j-i>1时,如果它的子是回文,且此时已有s[i]==s[j],则计数加一。
- if(dp[i+1][j-1]){
- dp[i][j]=true;
- count++;
-
- }
- }
- }
- }
- }
- return count;
- }
- }
双指针做法:中心扩散法
首先确定回文串,就是找中心然后向两边扩散看是不是对称的就可以了。
在遍历中心点的时候,要注意中心点有两种情况。
一个元素可以作为中心点,两个元素也可以作为中心点。
每个中心点标识一个字符串S。
- class Solution {
- public int countSubstrings(String s) {
- int result=0;
- for(int i=0;i<s.length();i++){
- result+=work(s,i,i);
- result+=work(s,i,i+1);
- }
-
-
- return result;
- }
- int work(String S,int index,int index2){
- int count=0;
- //尽量把判断写在一起。不然可能会超时
- while(index2<S.length()&&index>=0&&S.charAt(index)==S.charAt(index2)){
- count++;
- index--;
- index2++;
- }
- return count;
-
-
-
- }
- }
序列的区别就是可以不连续。
方法一:动态规划复杂
如果s[i]与s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;
如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加入 并不能增加[i,j]区间回文子串的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列。
加入s[j]的回文子序列长度为dp[i + 1][j]。
加入s[i]的回文子序列长度为dp[i][j - 1]。
那么dp[i][j]一定是取最大的,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
dp数组如何初始化
首先要考虑当i 和j 相同的情况,从递推公式:dp[i][j] = dp[i + 1][j - 1] + 2; 可以看出 递推公式是计算不到 i 和 j 相同时候的情况。
所以需要手动初始化一下,当i与j相同,那么dp[i][j]一定是等于1的,即:一个字符的回文子序列长度就是1。
- class Solution {
- public int longestPalindromeSubseq(String s) {
- int [][] dp=new int [s.length()+1][s.length()+1];
- //i到j之间的最长回文子序列。
-
- // dp[i][j]=dp[i+1][j-1]+2;
- for(int i=s.length()-1;i>=0;i--){
- dp[i][i]=1;//我们只看两位的。
- for(int j=i+1;j<s.length();j++){
- if(s.charAt(i)==s.charAt(j)){
- dp[i][j]=dp[i+1][j-1]+2;
- }else{
- dp[i][j]=Math.max(dp[i+1][j],dp[i][j-1]);
- }
- }
- }
- //返回的区间应该位0-s.length()-1
- return dp[0][s.length()-1];
-
- }
- }
方法二:s与s.reverse()的最长公共子序列即为其最长回文子序列
- class Solution {
- public int longestPalindromeSubseq(String text1) {
-
- //用StringBuffer,反转一下。
- String text2=new StringBuffer(text1).reverse().toString();
-
- int[][] dp = new int[text1.length() + 1][text2.length() + 1]; // 先对dp数组做初始化操作
- for (int i = 1 ; i <= text1.length() ; i++) {
- char char1 = text1.charAt(i - 1);
- for (int j = 1; j <= text2.length(); j++) {
- char char2 = text2.charAt(j - 1);
- if (char1 == char2) { // 开始列出状态转移方程
- dp[i][j] = dp[i - 1][j - 1] + 1;
- } else {
- dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
- }
- }
- }
- return dp[text1.length()][text2.length()];
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。