动态规划算法专题(三):简单多状态

目录

1. 按摩师

1.1 算法原理

1.2 算法代码

2、打家劫舍 II

2.1 算法原理

 2.2 算法代码

3、删除并获得点数 

3.1 算法原理

3.2 算法代码

4、粉刷房子

4.1 算法原理

 4.2 算法代码

5、买卖股票的最佳时机含冷冻期

5.1 算法原理

5.2 算法代码

6、买卖股票的最佳时机含手续费

6.1 算法原理

6.2 算法代码

7、买卖股票的最佳时机 III 【hard】

7.1 算法原理

7.2 算法代码

8、买卖股票的最佳时机 IV

8.1 算法原理

8.2 算法代码


1. 按摩师

. - 力扣(LeetCode)

1.1 算法原理

  • 状态表示:

以i位置为结尾,到达i位置的最长预约时间数。此时dp[i]又分为选择i位置和不选i位置:
1. f[i]:选择i位置
2. g[i]:不选i位置
f[i]=g[i-1]+arr[i];//选了当前位置,就不能选前一个位置
g[i]=max(f[i-1],g[i-1]);//不选当前位置,前一个位置也可选可不选(挑最大值)

  • 初始化:

f[0]=arr[0];

g[0]=0;

  • 建表顺序:

从左往右

  • 返回值:

max(f[n-1], g[n-1]);

1.2 算法代码

class Solution {
    public int massage(int[] nums) {
        int n = nums.length;
        if(n == 0) return 0;
        int[] f = new int[n];//选当前位置
        int[] g = new int[n];//不选当前位置
        f[0] = nums[0];
        g[0] = 0;
        for(int i = 1; i < n; i++) {
            f[i] = g[i - 1] + nums[i];
            g[i] = Math.max(f[i - 1], g[i - 1]);
        }
        return Math.max(g[n - 1], f[n - 1]);
    }
}

2、打家劫舍 II

. - 力扣(LeetCode)

2.1 算法原理

分情况:

  1. nums[0]偷 --> nums[n-1]不能偷 --> [2,n-2]
  2. nums[0]不偷 --> [1,n-1](正常打家劫舍)
  • 状态表示:

以i位置为结尾,偷到该位置的最大金额
1.f[i]:该位置偷
2.g[i]:该位置不偷

  • 状态转移方程:

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

  • 初始化:

f[left] = nums[left];  g[left] = 0;

  • 返回值:

max(f[right],g[right]);

 2.2 算法代码

class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        //分情况:1. 第一个位置填 2. 第一个位置不填
        return Math.max(nums[0] + rob1(nums, 2, n - 2), rob1(nums, 1, n - 1));
    }

    public int rob1(int[] nums, int left, int right) {
        if(left > right) return 0;
        int n = nums.length;
        int[] f = new int[n];//该位置填
        int[] g = new int[n];//该位置不填
        f[left] = nums[left];
        for (int i = left; i <= right; i++) {
            f[i] = nums[i] + g[i - 1];
            g[i] = Math.max(f[i - 1], g[i - 1]);
        }
        return Math.max(f[right], g[right]);
    }
}

3、删除并获得点数 

. - 力扣(LeetCode)

3.1 算法原理

选择值为x的点数后,则 x+1 / x-1 数值将被删除,也就是说不能增加值为 x+1 / x-1 的点数,故可转化“打家劫舍”问题。

  • 状态表示:

到达i位置时,能获得的最大点数。
f[i]:到达i位置,arr[i]必选。   g[i]:到达i位置,arr[i]不选。

  • 状态转移方程:

f[i]=arr[i]+g[i-1];   g[i]=max(g[i-1],f[i-1]);

  • 初始化:

f[0]=arr[0];

  • 返回值:

max(f[MAX_V],g[MAX_V]);

  • 预处理arr:

将nums中的数,统计到arr中,然后在arr中进行一次“打家劫舍”。(相邻的数不选,转化为“打家劫舍”问题)

3.2 算法代码

class Solution {
    public int deleteAndEarn(int[] nums) {
        Arrays.sort(nums);
        int maxVal = nums[nums.length - 1];
        //arr[i]表示i这个数的总和
        int[] arr = new int[maxVal + 1];
        //预处理
        for(int x : nums) arr[x] += x;
        int[] f = new int[maxVal + 1];
        int[] g = new int[maxVal + 1];
        //初始化
        f[0] = arr[0];
        //dp
        for(int i = 1; i < maxVal + 1; i++) {
            f[i] = arr[i] + g[i - 1];
            g[i] = Math.max(f[i - 1], g[i - 1]);
        }
        return Math.max(f[maxVal], g[maxVal]);
    }
}

4、粉刷房子

. - 力扣(LeetCode)

4.1 算法原理

  • 状态表示:

dp[i][0]:粉刷到第i个房子时,第i个房子刷红色,此时的最小花费
dp[i][1]:粉刷到第i个房子时,第i个房子刷蓝色,此时的最小花费
dp[i][2]:粉刷到第i个房子时,第i个房子刷绿色,此时的最小花费

  • 状态转移方程:

dp[i][0]=min(dp[i-1][1],dp[i-1][2])+costs[i-1][0];
dp[i][1]=min(dp[i-1][0],dp[i-1][2])+costs[i-1][1];
dp[i][2]=min(dp[i-1][0],dp[i-1][1])+costs[i-1][2];

  • 初始化:多开辟一行.

下标映射:dp[i][x]-->costs[i-1][x];
虚拟节点值:dp[0][j]=0;

  • 填表顺序:

从上往下

  • 返回值:

min(dp[n][0],dp[n][1],dp[n][2]);

 4.2 算法代码

class Solution {
    public int minCost1(int[][] costs) {
        int n = costs.length;
        int[] f = new int[n + 1];// 红
        int[] g = new int[n + 1];// 蓝
        int[] h = new int[n + 1];// 绿
        for (int i = 1; i <= n; i++) {
            f[i] = Math.min(g[i - 1], h[i - 1]) + costs[i - 1][0];
            g[i] = Math.min(f[i - 1], h[i - 1]) + costs[i - 1][1];
            h[i] = Math.min(g[i - 1], f[i - 1]) + costs[i - 1][2];
        }
        return Math.min(Math.min(f[n], g[n]), h[n]);
    }
    public int minCost(int[][] costs) {
        int n = costs.length;
        int[][] dp = new int[n + 1][3];
        for (int i = 1; i <= n; i++) {
            dp[i][0] = Math.min(dp[i - 1][1], dp[i - 1][2]) + costs[i - 1][0];
            dp[i][1] = Math.min(dp[i - 1][0], dp[i - 1][2]) + costs[i - 1][1];
            dp[i][2] = Math.min(dp[i - 1][0], dp[i - 1][1]) + costs[i - 1][2];
        }
        return Math.min(Math.min(dp[n][0], dp[n][1]), dp[n][2]);
    }
}

5、买卖股票的最佳时机含冷冻期

. - 力扣(LeetCode)

5.1 算法原理

  • 状态表示:

dp[i][0]:第i天后,处于“买入”状态,此时的最大利润
dp[i][1]:第i天后,处于“可交易”状态,此时的最大利润(“冷冻期”过后的可交易状态)
dp[i][2]:第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][2]);// 可交易

dp[i][2] = dp[i - 1][0] + prices[i];// 冷冻期

  • 初始化:

dp[0][0]=-prices[0];
dp[0][1]=0;

dp[0][2]=0;

  • 填表顺序:

从上到下

  • 返回值:

max(dp[n][1],dp[n][2]);

5.2 算法代码

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[][] dp = new int[n][3];
        dp[0][0] = -prices[0]; dp[0][1] = 0; dp[0][2] = 0;
        for(int i = 1; i < n; 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][2]);// 可交易
            dp[i][2] = dp[i - 1][0] + prices[i];// 冷冻期
        }
        return Math.max(dp[n - 1][1], dp[n - 1][2]);
    }
}

6、买卖股票的最佳时机含手续费

6.1 算法原理

  • 状态表示:

f[i]:第i天结束后,处于"买入"状态,此时的最大利润。

g[i]:第i天结束后,处于"卖出"状态,此时的最大利润。

  • 状态转移方程:

f[i]=max(f[i-1], g[i-1]-prices[i]);
g[i]=max(f[i-1]+prices[i]-fee,g[i-1]);

  • 初始化:

f[0]=-prices[0];
g[0]=0;

  • 填表顺序:

从左往右,两个表一起填

  • 返回值:

g[n-1]

6.2 算法代码

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        int[] f = new int[n];// 该天结束后,处于买入状态,最大利润
        int[] g = new int[n];// 该天结束后,处于卖出状态,最大利润
        f[0] = -prices[0]; g[0] = 0;
        for(int i = 1; i < n; i++) {
            f[i] = Math.max(f[i - 1], g[i - 1] - prices[i]);
            g[i] = Math.max(f[i - 1] + prices[i] - fee, g[i - 1]);
        }
        return g[n - 1];
    }
}

7、买卖股票的最佳时机 III 【hard】

. - 力扣(LeetCode)

7.1 算法原理

  • 状态表示:

f[i][j]:第i天结束后,完成了j次交易,处于"买入"状态,利润最大值
g[i][j]:第i天结束后,完成了j次交易,处于"卖出"状态,利润最大值

  • 状态转移方程:

f[i][j]=max(f[i-1][j], g[i-1][j]-p[i]);

g[i][j]=g[i-1][j];
if(j-1 >= 0) --> g[i][j]=max(g[i][j], f[i-1][j-1]+p[i]);

  • 初始化:

f[0][0]=-p[0];// 第0天,买入
g[0][0]=0;// 卖出的可交易状态,利润为0
f[0][1/2]=g[0][1/2]=min;// 交易次数是有限的,要尽可能减小交易次数

  • 建表顺序:

从上到下,从左到右,两个表一起填

  • 返回值

g表最后一行中的最大值

注意,进行加减操作时,防溢出:

  1. 正无穷表示:0x3f3f3f3f
  2. 负无穷表示:-0x3f3f3f3f


7.2 算法代码

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[][] f = new int[n][3];
        int[][] g = new int[n][3];
        // -0x3f3f3f3f 表示无穷小
        // 初始化
        for(int i = 0; i < 3; i++) f[0][i] = g[0][i] = -0x3f3f3f3f;
        f[0][0] = -prices[0];
        g[0][0] = 0;
        // 填表
        for(int i = 1; i < n; i++) {
            for(int j = 0; j < 3; j++) {
                f[i][j] = Math.max(f[i - 1][j], g[i - 1][j] - prices[i]);
                g[i][j] = g[i - 1][j];
                if(j - 1 >= 0) g[i][j] = Math.max(g[i][j], f[i - 1][j - 1] + prices[i]);
            }
        }
        int ret = 0;
        for(int x : g[n - 1]) ret = Math.max(ret, x);
        return ret;
    }
}

8、买卖股票的最佳时机 IV

. - 力扣(LeetCode)

8.1 算法原理

题7解法采用的是通用解法,本题解法与题7完全一致。

8.2 算法代码

class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        k = Math.min(k, n / 2);
        int[][] f = new int[n][k + 1];
        int[][] g = new int[n][k + 1];
        // -0x3f3f3f3f 表示无穷小
        // 初始化
        for(int i = 0; i <= k; i++) f[0][i] = g[0][i] = -0x3f3f3f3f;
        f[0][0] = -prices[0];
        g[0][0] = 0;
        // 填表
        for(int i = 1; i < n; i++) {
            for(int j = 0; j <= k; j++) {
                f[i][j] = Math.max(f[i - 1][j], g[i - 1][j] - prices[i]);
                g[i][j] = g[i - 1][j];
                if(j - 1 >= 0) g[i][j] = Math.max(g[i][j], f[i - 1][j - 1] + prices[i]);
            }
        }
        int ret = 0;
        for(int x : g[n - 1]) ret = Math.max(ret, x);
        return ret;
    }
}

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值