编辑距离、地下城游戏、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; } } }

浙公网安备 33010602011771号