阶段:
尝试,递归 --》 记忆化搜索,DP --》 严格表结构,DP --》 严格表精版
某些问题上,记忆化搜索 和 严格表结构 用同样的时间复杂度。
一个整数n代表 你有n个位置,
一个整数s代表 开始的位置, 1到n之间。
一个整数e 代表 代表要去的 目标位置
一个整数k 代表 机器人必须走k步
走的规则: 1位置只能走向2, n位置下一步只能走向n-1
问: 走k步,从s到e有多少种方法?
展开表示:
暴力递归:
public static int ways1(int N, int M, int K, int P) {
// 参数无效直接返回0
if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
return 0;
}
// 总共N个位置,从M点出发,还剩K步,返回最终能达到P的方法数
return walk(N, M, K, P);
}
// N : 位置为1 ~ N,固定参数
// cur : 当前在cur位置,可变参数
// rest : 还剩res步没有走,可变参数
// P : 最终目标位置是P,固定参数
// 该函数的含义:只能在1~N这些位置上移动,当前在cur位置,走完rest步之后,停在P位置的方法数作为返回值返回
public static int walk(int N, int cur, int rest, int P) {
// 如果没有剩余步数了,当前的cur位置就是最后的位置
// 如果最后的位置停在P上,那么之前做的移动是有效的
// 如果最后的位置没在P上,那么之前做的移动是无效的
if (rest == 0) {
return cur == P ? 1 : 0;
}
// 如果还有rest步要走,而当前的cur位置在1位置上,那么当前这步只能从1走向2
// 后续的过程就是,来到2位置上,还剩rest-1步要走
if (cur == 1) {
return walk(N, 2, rest - 1, P);
}
// 如果还有rest步要走,而当前的cur位置在N位置上,那么当前这步只能从N走向N-1
// 后续的过程就是,来到N-1位置上,还剩rest-1步要走
if (cur == N) {
return walk(N, N - 1, rest - 1, P);
}
// 如果还有rest步要走,而当前的cur位置在中间位置上,那么当前这步可以走向左,也可以走向右
// 走向左之后,后续的过程就是,来到cur-1位置上,还剩rest-1步要走
// 走向右之后,后续的过程就是,来到cur+1位置上,还剩rest-1步要走
// 走向左、走向右是截然不同的方法,所以总方法数要都算上
return walk(N, cur + 1, rest - 1, P) + walk(N, cur - 1, rest - 1, P);
}
第一步优化: 记忆化搜索,就加了缓存
第二步优化: 动态规划,纠结位置依赖顺序
严格表结构的动态规划。
时间复杂度:
leetcode322
给你一个正数数组,里面每一个值,代表一枚硬币的面值。
一个数代表一枚硬币。
aim = 10.
组成这个10,最少用多少硬币。 7 + 3
递归:
package com.wanghaha.algorithm;
public class Day8_03_62CoinsMin {
public static int minCoins1(int[] arr, int aim){
process(arr, 0, aim);
}
// arr[index..]组成出rest这么多钱,最少的硬币数量返回
public static int process(int[] arr, int index, int rest){
if(rest < 0){
return -1;
}
if( rest == 0 ){
return 0;
}
// rest > 0
if(index == arr.length ){
return -1;
}
// rest > 0 and 也有硬币
int p1 = process(arr, index +1 , rest);
int p2Next = process(arr, index + 1 , rest - arr[index]);
if(p1 == -1 && p2Next == -1){
return -1;
}else {
if(p1 == -1){
return p2Next + 1 ;
}
if(p2Next == -1 ){
return p1;
}
}
return Math.min(p1, p2Next);
}
}
记忆化搜索
public static int minCoins2(int[] arr, int aim){
int[][] dp = new int[arr.length +1 ][aim + 1];
for (int i = 0; i < arr.length + 1; i++) {
for (int j = 0; j < aim + 1; j++) {
dp[i][j] = -2;
}
}
return process2(arr, 0, aim, dp);
}
// arr[index..]组成出rest这么多钱,最少的硬币数量返回
public static int process2(int[] arr, int index, int rest,int[][] dp){
if(rest < 0){
return -1;
}
if(dp [index][rest] != -2 ){
return dp[index][rest];
}
if( rest == 0 ){
dp[index][rest] = 0;
}else if (index == arr.length ){
dp[index][rest] = -1;
}else {
int p1 = process2(arr, index +1 , rest,dp);
int p2Next = process2(arr, index + 1 , rest - arr[index],dp);
if(p1 == -1 && p2Next == -1){
dp[index][rest] = -1;
}else {
if(p1 == -1){
dp[index][rest] = p2Next + 1 ;
}else if (p2Next == -1 ){
dp[index][rest] = p1;
}else {
dp[index][rest] = Math.min(p1, p2Next);
}
}
}
return dp[index][rest];
}
严格表结构
public static int minCoins3(int[] arr, int aim){
int N = arr.length ;
int[][] dp = new int[N + 1][aim + 1];
for (int row = 0; row < N ; row++) {
dp[row][0] = 0;
}
for (int col = 1; col < aim; col++) {
dp[N][col] = -1;
}
for (int index = N-1; index >= 0 ; index--) {
for (int rest = 1; rest <= aim; rest++) {
int p1 = dp[index+1][rest];
int p2Next = -1;
if(rest - arr[index] >= 0 ){
p2Next = dp[index+1][rest - arr[index]];
}
if(p1 == -1 && p2Next == -1){
dp[index][rest] = -1;
}else {
if(p1 == -1){
dp[index][rest] = p2Next + 1 ;
}else if (p2Next == -1 ){
dp[index][rest] = p1;
}else {
dp[index][rest] = Math.min(p1, p2Next+1);
}
}
}
}
return dp[0][aim];
}
递归:
记忆化搜索
严格表结构:
有两个人玩游戏,能看到一个数组上所有的数字。
每个人依次拿,只能拿最左或者最右的数组。
两个人都很聪明,谁最后会获胜?返回获胜的分数。
先手和后手
package com.wanghaha.algorithm;
import java.util.List;
public class Day8_04_65GrapNumber {
public static int grapNumber(int[] arr){
if(arr == null || arr.length ==0 ){
return 0;
}
return Math.max(f(arr, 0, arr.length-1), s(arr, 0, arr.length-1));
}
public static int f(int[] arr, int i, int j ){
if(i == j){
return arr[i];
}
return Math.max(arr[i] + s(arr, i+1, j), arr[j] + s(arr, i, j-1) );
}
private static int s(int[] arr, int i, int j) {
if(i == j){
return 0;
}
return Math.min(f(arr, i+1, j), f(arr, i, j-1));
}
public static int grapNumber2(int[] arr){
if(arr == null || arr.length ==0 ){
return 0;
}
int[][] firstDP = new int[arr.length][arr.length];
int[][] secondDP = new int[arr.length][arr.length];
for (int i = 0; i < arr.length; i++) {
firstDP[i][i] = arr[i];
secondDP[i][i] = 0;
}
int j = 1;
int i = 0;
int a;
int b;
while (j != arr.length){
System.out.println("j: " + j);
a = i;
b = j;
while (a<arr.length && b< arr.length){
firstDP[a][b] = Math.max(arr[a] + secondDP[a+1][b], arr[b] + secondDP[a][b-1]);
secondDP[a][b] = Math.min(firstDP[a+1][b],firstDP[a][b-1]);
a ++ ;
b ++ ;
}
j++;
}
return Math.max(firstDP[0][arr.length-1],secondDP[0][arr.length-1]);
}
public static void main(String[] args) {
int[] arr = {1,9,11};
System.out.println(grapNumber(arr));
System.out.println(grapNumber2(arr));
}
}
马 ,10乘9的棋盘上,从 (0,0) 要到 (a,b)去
一定要跳K步
step张xy二维表
public static int getWays(int x, int y, int step) {
return process(x, y, step);
}
// 潜台词: 从(0,0)位置出发
// 要去往(x,y)位置, 必须跳 step步
// 返回方法数
public static int process(int x, int y, int step) {
if (x < 0 || x > 8 || y < 0 || y > 9) {
return 0;
}
if (step == 0) {
return (x == 0 && y == 0) ? 1 : 0;
}
return process(x - 1, y + 2, step - 1)
+ process(x + 1, y + 2, step - 1)
+ process(x + 2, y + 1, step - 1)
+ process(x + 2, y - 1, step - 1)
+ process(x + 1, y - 2, step - 1)
+ process(x - 1, y - 2, step - 1)
+ process(x - 2, y - 1, step - 1)
+ process(x - 2, y + 1, step - 1);
}
public static int dpWays(int x, int y, int step) {
if (x < 0 || x > 8 || y < 0 || y > 9 || step < 0) {
return 0;
}
int[][][] dp = new int[9][10][step + 1];
dp[0][0][0] = 1;
for (int h = 1; h <= step; h++) {
for (int r = 0; r < 9; r++) {
for (int c = 0; c < 10; c++) {
dp[r][c][h] += getValue(dp, r - 1, c + 2, h - 1);
dp[r][c][h] += getValue(dp, r + 1, c + 2, h - 1);
dp[r][c][h] += getValue(dp, r + 2, c + 1, h - 1);
dp[r][c][h] += getValue(dp, r + 2, c - 1, h - 1);
dp[r][c][h] += getValue(dp, r + 1, c - 2, h - 1);
dp[r][c][h] += getValue(dp, r - 1, c - 2, h - 1);
dp[r][c][h] += getValue(dp, r - 2, c - 1, h - 1);
dp[r][c][h] += getValue(dp, r - 2, c + 1, h - 1);
}
}
}
return dp[x][y][step];
}
public static int getValue(int[][][] dp, int row, int col, int step) {
if (row < 0 || row > 8 || col < 0 || col > 9) {
return 0;
}
return dp[row][col][step];
}
leetcode668
给行数和列数,表示这个格子有多大。
行数为5,列数为6。
Bob所在格子是(a,b)
Bob所在任何一个点,都等概率随机的向上下左右走。走K步。
Bob能活下来的概率是多少?
public static String bob1(int N, int M, int i, int j, int K) {
long all = (long) Math.pow(4, K);
long live = process(N, M, i, j, K);
long gcd = gcd(all, live); // 求一个最大公约数
return String.valueOf((live / gcd) + "/" + (all / gcd));
}
// N * M 的区域,Bob在(row,col)的位置, 走test步之后,获得的生存方法数
public static long process(int N, int M, int row, int col, int rest) {
if (row < 0 || row == N || col < 0 || col == M) {
return 0;
}
// row, col 没越界
if (rest == 0) {
return 1;
}
// 还没走完, row , col 也没越界。
long live = process(N, M, row - 1, col, rest - 1);
live += process(N, M, row + 1, col, rest - 1);
live += process(N, M, row, col - 1, rest - 1);
live += process(N, M, row, col + 1, rest - 1);
return live;
}
public static long gcd(long m, long n) {
return n == 0 ? m : gcd(n, m % n);
}
leetcode 518
一个数组整数,无重复值
一个位置上的值,代表一个面值
每一面值的货币无限多。
组出1000的货币,方法数都多少种。
递归尝试
public static int way1(int[] arr, int aim) {
return process(arr, 0, aim);
}
private static int process(int[] arr, int index, int rest) {
if (index == arr.length) {
return rest == 0 ? 1 : 0;
}
// arr[index] 0张 1张 。。 不要超过rest的钱数
int ways = 0;
for (int zhang = 0; arr[index] * zhang <= rest; zhang++) {
ways += process(arr, index + 1, rest-arr[index] * zhang);
}
return ways;
}
不优化的严格表结构
时间复杂度 N*(aim^2)
public static int way2(int[] arr, int aim){
if(arr == null || arr.length ==0){
return 0;
}
int N = arr.length;
int[][] dp = new int[N+1][aim + 1];
dp[N][0] = 1;
for (int index = N - 1; index >= 0 ; index--) {
for (int rest = 0; rest <= aim; rest++) {
int ways = 0;
for (int zhang = 0; arr[index] * zhang <= rest; zhang++) {
ways += dp[index + 1 ] [rest-arr[index] * zhang];
}
dp[index][rest] = ways;
}
}
return dp[0][aim] ;
}
斜率优化
临近的位置能不能替代 枚举行为。
public static int way3(int[] arr, int aim){
if(arr == null || arr.length ==0){
return 0;
}
int N = arr.length;
int[][] dp = new int[N+1][aim + 1];
dp[N][0] = 1;
for (int index = N - 1; index >= 0 ; index--) {
for (int rest = 0; rest <= aim; rest++) {
dp[index][rest] = dp[index+1][rest] ;
if(rest - arr[index] >= 0 ){
dp[index][rest] += dp[index][rest - arr[index]];
}
}
}
return dp[0][aim] ;
}
// for test
public static int[] generateRandomArray(int len, int max) {
int[] arr = new int[(int) (Math.random() * len) + 1];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * max) + 1;
}
return arr;
}
public static void main(String[] args) {
int len = 10;
int max = 10;
int testTime = 10000;
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArray(len, max);
int aim = (int) (Math.random() * 3 * max) + max;
if ( way1(arr, aim) != way2(arr,aim)
|| way1(arr, aim) != way3(arr,aim)) {
System.out.println("ooops!");
break;
}
}
}
尝试模型 : 从左往右,范围 7成以上
记忆化搜索的方式:
分析空间格子,严格表结构动态规划。
可以进一步优化,优化枚举行为。
尝试时,两件事