编辑距离、地下城游戏、POJ 3670、POJ 3616

https://leetcode-cn.com/problems/edit-distance/

dp[i][j]

j i h o r s e
r 1 2 2 3 4
o 2 1 2 3 4
s 3 2 2 2 3

if(char(i) == char2(j))
dp[i][j] = dp[i-1][j-1]

if(char(i) != char2(j))
dp[i][j] = dp[i-1][j-1]+1, hor, ro
dp[i][j-1] +1, hor, ro
dp[i-1][j] +1 hor, ro

horse, ros
可以插入删除替换

h o r s e
r 1 2 2 3 4
o 2 1 2 3 4
s 3 2 2 2 3

if(arr[i] == arr[j]) {
dp[i][j] = dp[i-1][j-1]
} else {
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
}

对比这两个 因为有插入操作对比 dp[i-1][j], dp[i][j-1]

==求解过程需要控制边界

 

class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length();
        int n = word2.length();

        //dp[i][j] 代表最小操作数(步骤),从 word1[0..i-1]转化为 word2[0..j-1].
        int[][] dp = new int[m + 1][n + 1];

        for(int i = 0; i <= m; i++) {
            dp[i][0] = i;
        }

        for(int i = 0; i <= n; i++) {
            dp[0][i] = i;
        }

        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                if(word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = 1 + Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1]));
                }
            }
        }

        return dp[m][n];
    }
}

 

---------------------------------------------------------------------------------------
https://leetcode-cn.com/problems/dungeon-game/comments/

求从左上角到右下角至少需要的体力值,网格中加号减号,就是健康值。中通不允许为0 或者更小

-2 -3 3

-5 -10 1

10 30 -5

到终点至少要有1个体力值,我们可以反向dp推导。

if(终点) {
  //表示到终点那步至少需要保留的体力值

  dp[i][j] = max(1, 1-data[i][j])

}

if(最后一行) {
  当前格子只能从本行的后一个格子过来,所以

  就是后一个格子至少的体力值减当前格子消耗的体力值

  就是当前格子至少保留的体力值。

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

}

if(最后一列) {
  当前格子只能从本列的下一个格子过来,所以

  就是下一个格子至少的体力值减当前格子消耗的体力值

  就是当前格子至少保留的体力值。

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

}

其他情况:

// 当前格子可以从下一个格子过来,或者从后一个格子过来

我们要取使用最小的体力值,和1比较要去最大的体力值。

因为如果dp[i][j] 值为负的,说明在移动的过程中,当前获取的体力值,

足以将后面的步子走完,所以在这一步只需要保留活着就好。

也就是每次和1比较


-2 -3 3

-5 -10 1

10 30 -5


7 5 2

6 11 5

1 1 6


dp[i][j]

-2 -3 3
v + -3 = 1
-5 -10 1 1 1- -5 -1 v + data[i][j] = dp[i+1][j] ==》 保留体力值 + 当前体力值 = 后面需要体力值
v + 10 = 6
10 30 -5(6) dp[i][j] = max(1, dp[i+1][j] - data[i][j])
1 1 -5

v + data[i][j] = dp[i][j+1] ==》 保留体力值 + 当前体力值 = 后面需要体力值

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


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

1

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

dp[0][0]

class Solution {
    public int calculateMinimumHP(int[][] dungeon) {
        int row = dungeon.length;
        int col = dungeon[0].length;
        int[][] dp = new int[row][col];

        for(int i = row - 1; i >= 0; i--) {
            for (int j = col - 1; j >= 0; j--) {
                if(i==row-1&&j==col-1) {
                    dp[i][j] = Math.max(1, 1-dungeon[i][j]);
                } else if (i == row - 1) {
                    dp[i][j] = Math.max(1,dp[i][j+1]-dungeon[i][j]);
                } else if (j == col - 1) {
                    dp[i][j] = Math.max(1, dp[i+1][j]-dungeon[i][j]);
                } else {
                    dp[i][j] = Math.max(1, Math.min(dp[i+1][j], dp[i][j+1])-dungeon[i][j]);
                }
            }
        }
        return dp[0][0];
    }
}

 


---------------------------------------------------------------------------------------------------------------------
POJ 3670

审题
* 题意:已知n个牛每个牛有一个组号1~3。现在他们排成一排准备吃饭。现在要通过改变他们的组号使得这排牛的序列是递增的或者递减的,
* 问需要的最少改变数量。

思考:

所有牛的编号一样也是满足题意,但是改变数量比较多。这是极限情况, ? 找一下代码哪里体现

要满足递增 或者递减
先考虑递增情况

因为每个牛可能有三个分组的情况,事实上我们需要暴力求取所有的可能性
dp也是一种变相的暴力

考虑一下怎么定义状态方程

dp[n][3] 表示 第i头牛为第j组的时候需要改变的最小次数
-----------------------------------------------------------------------
为了满足递增的情况,考虑递推方程

如果第i头牛分组为第一组的最小改变次数怎么得来的 dp[i][0];
i-1头牛只能是第一组的 最小改变次数, 加上 (当前牛 是第一组) ? 0 : 1

如果第i头牛分组为第二组的最小改变次数怎么得来的 dp[i][0];
i-1头牛可以是第一组的,也可以是第二组 最小改变次数, 加上 (当前牛 是第一组) ? 0 : 1

如果第i头牛分组为第三组的最小改变次数怎么得来的 dp[i][0];
i-1头牛可以是第一组的,也可以是第二组,也可以是第三组 最小改变次数, 加上 (当前牛 是第一组) ? 0 : 1

请大家写出地推方程 ~~~~~~~~~~

状态转移方程

求递增
0,1,2
dp[i][j] = 最改变编号少次数

dp[1][1] = data[1] == 1 ? 0 : 1
dp[1][2] = data[1] == 2 ? 0 : 1
dp[1][3] = data[1] == 3 ? 0 : 1

dp[2][1] = dp[1][1] + (data[2] == 1 ? 0 : 1)
第二头牛在第二组 改变最少次数 = min(第一头牛在第一组 改变最少次数 、 第一头牛在第二组 改变最少次数) + 第二头是不是在第二组
dp[2][2] = min(dp[1][1], dp[1][2]) + (data[2] == 2 ? 0 : 1)
dp[2][3] = min(dp[1][1], dp[1][2], dp[1][3]) + (data[2] == 3 ? 0 : 1)

dp[i][1] = dp[i-1][1] + (data[i] == 1 ? 0 : 1)
dp[i][2] = Math.min(dp[i-1][1], dp[i-1][2]) + (data[i] == 2 ? 0 : 1)
dp[i][3] = Math.min(dp[i-1][1], dp[i-1][2],dp[i-1][3]) + (data[i] == 3 ? 0 : 1)

dp[i][1],dp[i][2],dp[i][3]

求递减

dp[i][3] = dp[i-1][3] + (data[i] == 3 ? 0 : 1)
dp[i][2] = Math.min(dp[i-1][3], dp[i-1][2]) + (data[i] == 2 ? 0 : 1)
dp[i][1] = Math.min(dp[i-1][1], dp[i-1][2],dp[i-1][3]) + (data[i] == 1 ? 0 : 1)

package pro;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
/*
 * 题意:已知n个牛每个牛有一个组号1~3。现在他们排成一排准备吃饭。现在要通过改变他们的组号使得这排牛的序列是递增的或者递减的,
 * 问需要的最少改变数量。
思路:简单dp,O(n)可解,dp[i][j]表示第i头牛编号为j时所需要的最小改变量,递推方程见代码即可
 */
public class POJ3670 {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        // 牛的个数
        int N = Integer.valueOf(st.nextToken());
        int[] arr = new int[N+1];

        for (int i = 1; i <= N; i++) {
            st = new StringTokenizer(br.readLine());
            arr[i] = Integer.valueOf(st.nextToken()) - 1; // 每头牛对应的组数
        }

        int ans = minStep(arr);
        // 逆序继续求递增,其实就是正序的递减
        int[] arr2 = new int[N+1];
        for (int i = 1; i <= N; i++) {
            arr2[i] = arr[N-i+1];
        }
        int ans2 = minStep(arr2);

        System.out.println(Math.min(ans, ans2));
    }

    private static int minStep(int[] arr) {
        // 表示第i头牛当它的编号为j组时,最小需要改变的的次数,
        // 并且它表示在i之前的牛的编号不存在比i牛编号大的编号,
        // 确保dp求出来的最小改变次数是单调递增的
        int N = arr.length;
        int[][] dp =new int[N][3];
        // 初始化假设第一头牛之前的牛分组为0组
        dp[0][0] = dp[0][1] = dp[0][2] = 0;

        for (int i = 1; i < N; i++) {
            dp[i][0] = dp[i-1][0] + (arr[i]!=0 ? 1 : 0);
            dp[i][1] = Math.min(dp[i-1][0],dp[i-1][1]) + (arr[i]!=1 ? 1 : 0);
            dp[i][2] = Math.min(Math.min(dp[i-1][0], dp[i-1][1]), dp[i-1][2]) + (arr[i]!=2 ? 1 : 0);
        }

        return Math.min(dp[N-1][0], Math.min(dp[N-1][1], dp[N-1][2])); // 单调有可能是全是1组  或者只有 12组 或者 123组
    }
}

 


-------------------------------------------------------------------------------------------------------------------------------
POJ 3616 模拟考试题

* 在一个农场里,在长度为N个时间可以挤奶,但只能挤M次,且每挤一次就要休息t分钟;
* 接下来给m组数据表示挤奶的时间与奶量求最大挤奶量

思考: 求最大的挤奶量, 因为时间不能重叠
定义dp[i] 到时间端i时 可以得到最大收益。

我们考虑到结束时间必须小于等于下一段时间的开始时间
我们先把时间端按照结束时间从小到大排序

时间端i为止可以获得的最大挤奶量 = i-1的最大挤奶量 加 当前的挤奶量
需要保证 i-1时间端的结束时间小于等于 i时间端的开始时间

即:
if (times[j].end <= times[i].start) {
dp[i] = Math.max(dp[i], dp[j] + times[i].value);
}

 

package pro;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Comparator;
import java.util.StringTokenizer;

/*
 * 在一个农场里,在长度为N个时间可以挤奶,但只能挤M次,且每挤一次就要休息t分钟;
 * 接下来给m组数据表示挤奶的时间与奶量求最大挤奶量
 */
public class POJ3616 {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        int N = Integer.parseInt(st.nextToken()); // 总时间
        int M = Integer.parseInt(st.nextToken()); // 挤奶时间段
        int R = Integer.parseInt(st.nextToken()); // 休息时间
        Times[] times = new Times[M];

        for (int i = 0; i < M; i++) {
            st = new StringTokenizer(br.readLine());
            int s = Integer.parseInt(st.nextToken()); // 总时间
            int e = Integer.parseInt(st.nextToken()); // 挤奶时间段
            int v = Integer.parseInt(st.nextToken()); // 休息时间
            times[i] = new Times(s,e+R,v);
        }

        Arrays.sort(times, new Comparator<Times>() {
            @Override
            public int compare(Times o1, Times o2) {
                return o1.end - o2.end;
            }
        });
        // 定义dp[i]为:,到dp[i].end这个时间点为止,可以获得的最大收益。
        int[] dp = new int[M+1];
        int ans = 0;
        for (int i = 0; i < M; i ++) {
            dp[i] = times[i].value;
            for (int j = 0; j < i; j ++) {
                if (times[j].end <= times[i].start) {
                    dp[i] = Math.max(dp[i], dp[j] + times[i].value);
                }
            }
            ans = Math.max(ans, dp[i]);
        }

        System.out.println(ans);
    }

    private static class Times {
        int start;
        int end;
        int value;

        public Times(int start, int end, int value) {
            this.start = start;
            this.end = end;
            this.value = value;
        }
    }
}

 

posted @ 2021-06-02 11:29  姓蜀名黍  阅读(46)  评论(0)    收藏  举报