因为算法课上骆老师讲的是真的好,所以对动态规划还是比较熟悉的。总结来说就是自底向上求解,主要在于DP转移方程
的分析,然后构造DP数组
进行填表即可,有时可能需要注意保存求解路径。
简单分析一个最大连续子序列之和问题:
给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },
其中 1 <= i <= j <= K。最大连续子序列是所有连续子序中元素和最大的一个,
例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和为20。
DP数组
,求解过程就是填表过程,从最底层(m(i,i)
)开始,然后依次向上沿对角线进行求解,每次都会用到之前已经计算过的结果。这个例子还是很简单的,因为DP转移方程
很明确。O(n)
的解法,参考动态规划经典五题。 public static void dp(int[] seq) {
int n = seq.length;
int[][] dparr = new int[n][];
int sum = 0;
int start = 0;
int end = 0;
// 最底层(最外列)求解
for (int i = 0; i < n; i++) {
dparr[i] = new int[i + 1];
dparr[i][i] = seq[i];
if (dparr[i][i] > sum) {
sum = dparr[i][i];
start = i;
end = i;
}
}
// 自底向上求解
for (int i = 1; i < n; i++) {
for (int j = 0; j < n - i; j++) {
// 在原有的基础上进行计算
// 转移方程
dparr[j + i][j] = dparr[j + i - 1][j] + seq[j + i];
if (dparr[j + i][j] > sum) {
sum = dparr[j + i][j];
start = j;
end = j + i;
}
}
}
System.out.println("maxsum = " + sum);
for (int i = start; i <= end; i++) {
System.out.print(seq[i] + " ");
}
}
O(n)
复杂度代码
public static void maxsum(int[] seq) {
int maxSum = 0, thisSum = 0;
for (int i = 0; i < seq.length; i++) {
thisSum += seq[i];
if (thisSum > maxSum) {
maxSum = thisSum;
} else if (thisSum < 0) {
thisSum = 0;
}
}
System.out.println("\nmaxsum = " + maxSum);
}
桌子上有一堆石头,每一次你们轮流取1至3颗石头。最后一个取走石头的人就是赢家。第一轮由你先取。
根据题设条件:
当n∈[1,3]时,先手必胜。
当n == 4时,无论先手第一轮如何选取,下一轮都会转化为n∈[1,3]的情形,此时先手必负。
当n∈[5,7]时,先手必胜,先手分别通过取走[1,3]颗石头,可将状态转化为n == 4时的情形,此时后手必负。
当n == 8时,无论先手第一轮如何选取,下一轮都会转化为n∈[5,7]的情形,此时先手必负。
……
以此类推,可以得出结论:
当n % 4 != 0时,先手必胜;否则先手必负。
两个人玩取球的游戏。
一共有N个球,每人轮流取球,每次可取集合{n1,n2,n3}中的任何一个数目。
如果无法继续取球,则游戏结束。
此时,持有奇数个球的一方获胜。
如果两人都是奇数,则为平局。
假设双方都采用最聪明的取法,
第一个取球的人一定能赢吗?
试编程解决这个问题。
输入格式:
第一行3个正整数n1 n2 n3,空格分开,表示每次可取的数目 (0
解题思路是减治法+动态规划
动态规划的应用在于,可以构建两个玩家的DP数组,table[i][j]
保存两个玩家当前分别有i
个球和j
个球时的胜负情况;
减治法,实际上是一种递归的实现,初始时是最上层的情况,然后不断递归,到可以判断胜负为止(即拈游戏中球数目为4时)。
/**
* 拈游戏 n个石头, 两个人每次取1-m个, 取走最有一个石头的玩家获胜.
* 减治法思想, 1<=n<=m时, 先手获胜; n=m+1时, 先手失败;
* m+2<=n<=2m+1, 先手获胜; n=2m+2时, 先手失败;
* 以此类推, n%(m+1)!=0时, 先手获胜
*
* 取球博弈 n个球, 每次可取{n1, n2, n3}中任意值数目球
* 最后, 持有奇数个球的一方获胜; 都是奇数则平局;
* 解决思路, 减值法和动态规划 为两个玩家分别建立一个DP表,
* (i,j)保存玩家1拥有i个球、玩家2拥有j个球时的结果
* 结果中, +表示当前玩家获胜,-表示失败, 0表示平局 递归进行解的搜索
*
*
*/
public class Main {
// 每次可以取得球数目
static int[] ns = new int[3];
// 五局初始的球数目
static int[] xs = new int[5];
// 分别保存两个玩家的动态表
static char[][][] table;
static int min;
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in = new Scanner(System.in);
for (int i = 0; i < 3; i++) {
ns[i] = in.nextInt();
}
for (int i = 0; i < 5; i++) {
xs[i] = in.nextInt();
}
getMin();
int k;
for (int i: xs) {
table = new char[2][1000][1000];
k = dp(0, 0, 0, i);
System.out.print((char)k + " ");
}
}
public static void getMin() {
int m = 99999;
for (int n : ns) {
if (n < m) {
m = n;
}
}
min = m;
}
// 当前的玩家
// 两个玩家分别拥有的球数目
// 初始球数目
// 终止条件为当前球数小于最小可取球数
// 否则继续递归
public static char dp(int p, int aowns, int bowns, int s) {
int rest = s - aowns - bowns;
// 已经计算过了则直接返回
if (table[p][aowns][bowns] != '\u0000') {
return table[p][aowns][bowns];
}
// 如果没法再取球则计算双方胜败
if (rest < min) {
if (aowns % 2 == 1 && bowns % 2 == 0) {
table[p][aowns][bowns] = '+';
} else if (aowns % 2 == 0 && bowns % 2 == 1) {
table[p][aowns][bowns] = '-';
} else {
table[p][aowns][bowns] = '0';
}
return table[p][aowns][bowns];
}
if (p == 0) {
boolean flag = false;
for (int i : ns) {
if (rest >= i) {
if (dp(1, aowns + i, bowns, s) == '+') {
return table[0][aowns][bowns] = '+';
} else if (dp(1, aowns + i, bowns, s) == '0') {
flag = true;
}
}
}
if (flag) {
table[0][aowns][bowns] = '0';
} else {
table[0][aowns][bowns] = '-';
}
}
if (p == 1) {
boolean flag = false;
for (int i : ns) {
if (rest >= i) {
if (dp(0, aowns, bowns + i, s) == '-') {
return table[1][aowns][bowns] = '-';
} else if (dp(0, aowns + i, bowns, s) == '0') {
flag = true;
}
}
}
if (flag) {
table[1][aowns][bowns] = '0';
} else {
table[1][aowns][bowns] = '+';
}
}
return table[p][aowns][bowns];
}
}
动态规划经典五题
leetcode_292_拈游戏
第七届蓝桥杯取球博弈详解
算法-动态规划 Dynamic Programming–从菜鸟到老鸟