1.HJ32 密码截取
2.HJ24 合唱队
3.HJ52 计算字符串的编辑距离
4.HJ61 放苹果
5.HJ75 公共子串计算
6.HJ41 称砝码
3.HJ52 计算字符串的编辑距离
描述
Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。编辑距离的算法是首先由俄国科学家 Levenshtein 提出的,故又叫 Levenshtein Distance 。
例如:
字符串A: abcdefg
字符串B: abcdef
通过增加或是删掉字符 ”g” 的方式达到目的。这两种方案都需要一次操作。把这个操作所需要的次数定义为两个字符串的距离。
要求:
给定任意两个字符串,写出一个算法计算它们的编辑距离。
数据范围:给定的字符串长度满足 1 \le len(str) \le 1000 \1≤len(str)≤1000
输入描述:
每组用例一共2行,为输入的两个字符串
输出描述:
每组用例输出一行,代表字符串的距离
import java.util.*;
public class Main
{
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
while(sc.hasNext())
{
String s1 = sc.nextLine();
String s2 = sc.nextLine();
System.out.println(distance(s1,s2));
}
}
private static int distance(String s1,String s2)
{
int m = s1.length();
int n = s2.length();
//定义dp数组
int[][] dp = new int[m+1][n+1];
//状态初始化
for(int i = 1;i <= m; i++)
{
dp[i][0] = i;
}
//状态转移
for(int i = 1;i <=m;i++)
{
for(int j = 1;j <= n;j++)
{
if(s1.charAt(i-1) == s2.charAt(j-1))
{
dp[i][j] = dp[i-1][j-1];
}
/*如果不相等,要么B字符串插入A中i位置对应字符即dp[i][j]=dp[i-1][j]+1
要么A字符串插入B中j位置对应字符即dp[i][j]=dp[i][j-1]+1,要么s1字符串
i位置字符被s2字符串j位置字符替换,即dp[i][j]=dp[i-1][j-1]+1
*/
else
{
dp[i][j]=Math.min(
Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
}
}
}
return dp[m][n];
}
}
2.HJ24 合唱队
描述
N 位同学站成一排,音乐老师要请最少的同学出列,使得剩下的 K 位同学排成合唱队形。
设KK位同学从左到右依次编号为 1,2…,K ,他们的身高分别为T_1,T_2,…,T_KT1,T2,…,TK ,若存在i(1\leq i\leq K)i(1≤i≤K) 使得T_1<T_2<......<T_{i-1}<T_iT1<T2<......<Ti−1<Ti 且 T_i>T_{i+1}>......>T_KTi>Ti+1>......>TK,则称这KK名同学排成了合唱队形。
通俗来说,能找到一个同学,他的两边的同学身高都依次严格降低的队形就是合唱队形。
例子:
123 124 125 123 121 是一个合唱队形
123 123 124 122不是合唱队形,因为前两名同学身高相等,不符合要求
123 122 121 122不是合唱队形,因为找不到一个同学,他的两侧同学身高递减。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
注意:不允许改变队列元素的先后顺序 且 不要求最高同学左右人数必须相等
数据范围: 1 \le n \le 3000 \1≤n≤3000
输入描述:
用例两行数据,第一行是同学的总数 N ,第二行是 N 位同学的身高,以空格隔开
输出描述:
最少需要几位同学出列
输入:8
186 186 150 200 160 130 197 200
输出:4
说明:
由于不允许改变队列元素的先后顺序,所以最终剩下的队列应该为186 200 160 130或150 200 160 130
题目分析:站成一排,从左到右找到中间人,从左到中间人是递增,从中间人到右是递减,所以从左到中间人找最长递增子序列的长度,找从中间人到右的递减子序列的长度。首先这个考察的是最长公共子序列长度怎么求取,变化是两个递增子序列的长度,一个是在从左到右到i位置是递增,还有一个从右到左,从n到i最长的递增子序列,这两个最长的递增子序列长度相加减去1是合唱队队形,遍历全部取最大值。
思路分析:
- 数据输入,有两部分数据,第一部分是合唱队的个数,第二个是每个数据
- 分为两部分的动态数组来记录左边的递增最长子序列
- 左边的,定义一个数组记录每个位置的最长递增序列
- 用双层for循环进行判断,第一层是循环数组是把每个位置初始化1,因为自己的一个数本身就是1个长度,第二层进行判断前i个数据中,比i数据小的话,则取当前数组的最长子序列长度+1,并取所有比i位置小的数据的最长递增子序列长度的最大值。
- 右边的,定义一个数组记录每个位置的最长递增子序列
- 用双层for循环进行判断,第一层是循环数组是把每个位置初始化1,因为自己的一个数本身就是1个长度,第二层进行判断前i个数据中,比i数据小的话,则取当前数组的最长子序列长度+1,并取所有比i位置小的数据的最长递增子序列长度的最大值。
- 再把i位置左边最长递增子序列长度和右边最长递增子序列长度相加减去1,作为该合唱队的个数,求取最大的合唱队长度。
- 最后用n-最长的递增子序列,就是最少需要取出多少人。
实际操作:
- 数据输入:第一个是int类型,第二个需要定义一个数组进行判断,进行for循环进行取数。
- 定义一维数组 left[i],记录从左到i位置最长的子序列长度。
- 定义一维数组right[i],记录从n-1到i位置最长的递增子序列
- 初始化数组,left[0] = 1; right[n-1] = 1;
- 遍历最左侧最长递增子序列,两层循环,第一层是循环数组是把每个位置初始化1,因为自己的一个数本身就是1个长度,第二层进行判断前i个数据中,比i数据小的话,则取当前数组的最长子序列长度+1,并取所有比i位置小的数据的最长递增子序列长度的最大值。
- 遍历最右边最长递增子序列,第一层是循环数组是把每个位置初始化1,因为自己的一个数本身就是1个长度,第二层进行判断前i个数据中,比i数据小的话,则取当前数组的最长子序列长度+1,并取所有比i位置小的数据的最长递增子序列长度的最大值
- 再把i位置左边最长递增子序列长度和右边最长递增子序列长度相加减去1,作为该合唱队的个数,求取最大的合唱队长度。
- 最后用n-最长的递增子序列,就是最少需要取出多少人。
import java.util.Scanner;
/**
问题分析:站成一排,从左到右找到中间人,从左到中间人是递增,从中间人到右是递减,
所以从左到中间人找最长递增子序列的长度,找从中间人到右的递减子序列的长度。
思路分析:
1.数据输入:输入两行,第一行是数字的个数,用int接收,第二行是所有数据,用数组接收。
2.
*/
public class Main
{
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
while(sc.hasNext())
{
int n = sc.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i++)
{
arr[i] = sc.nextInt();
}
int[] left = new int[n];
int[] right = new int[n];
left[0] = 1;
right[n-1] = 1;
//计算每个位置左侧的最长递增
for (int i=0; i < n; i++)
{
left[i] = 1;
for ( int j = 0; j < i;j++)
{
if (arr[i] > arr[j])
{
left[i] = Math.max(left[j]+1,left[i]);
}
}
}
//计算每个位置右侧的最长递减
for (int i = n-1; i >= 0;i--)
{
right[i] = 1;
for (int j = n-1;j > i;j--)
{
if (arr[i] > arr[j])
{
right[i] = Math.max(right[i],right[j]+1);
}
}
}
//记录每个位置的值
int[] result = new int[n];
for (int i = 0; i < n;i++)
{
result[i] = left[i] + right[i] - 1;
}
//找到最大的满足要求的值
int max = 1;
for (int i = 0;i < n;i++)
{
max = Math.max(result[i],max);
}
System.out.println(n - max);
}
}
}
8.HJ16 购物单
描述
王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 | 附件 |
电脑 | 打印机,扫描仪 |
书柜 | 图书 |
书桌 | 台灯,文具 |
工作椅 | 无 |
如果要买归类为附件的物品,必须先买该附件所属的主件,且每件物品只能购买一次。
每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。
王强查到了每件物品的价格(都是 10 元的整数倍),而他只有 N 元的预算。除此之外,他给每件物品规定了一个重要度,用整数 1 ~ 5 表示。他希望在花费不超过 N 元的前提下,使自己的满意度达到最大。
满意度是指所购买的每件物品的价格与重要度的乘积的总和,假设设第ii件物品的价格为v[i]v[i],重要度为w[i]w[i],共选中了kk件物品,编号依次为j_1,j_2,...,j_kj1,j2,...,jk,则满意度为:v[j_1]*w[j_1]+v[j_2]*w[j_2]+ … +v[j_k]*w[j_k]v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk]。(其中 * 为乘号)
请你帮助王强计算可获得的最大的满意度。
输入描述:
输入的第 1 行,为两个正整数N,m,用一个空格隔开:
(其中 N ( N<32000 )表示总钱数, m (m <60 )为可购买的物品的个数。)
从第 2 行到第 m+1 行,第 j 行给出了编号为 j-1 的物品的基本数据,每行有 3 个非负整数 v p q
(其中 v 表示该物品的价格( v<10000 ), p 表示该物品的重要度( 1 ~ 5 ), q 表示该物品是主件还是附件。如果 q=0 ,表示该物品为主件,如果 q>0 ,表示该物品为附件, q 是所属主件的编号)
输出描述:
输出一个正整数,为张强可以获得的最大的满意度。
示例1
输入:
1000 5 800 2 0 400 5 1 300 5 1 400 3 0 500 2 0 输出:2200
1)题目分析:
-
这个题目是0和1背包问题,可以用动态规划,背包问题是定义,变量是物品和背包剩余容量
-
dp[i][j]表示前i个物品,背包容量为j时的最大价值,物品的重量用w[i]表示,
-
物品的价值用v[i]表示。
-
分为两种状态:分为放或者不放两种选择
-
放得话就是dp[i][j] = dp[i-1][j-w[i]] + v[i]
-
不放得话就是dp[i][j] = dp[i-1][j]
-
最后取得dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-w[i]] + v[i])
2)思路分析:
- 本题在此基础上有一些条件,就是有附件的必须要有主件,
- 首先分两种情况,放或者不放
- 不放的时候一种方法,
- 当放的时候,有四种方法,
- 第一只放主件;
- 第二只放1个主件和附件1
- 第三十放1个主件和附件2
- 第四是放1个主件、附件1和附件2
- 总共有5种方法,这五种方法每次取最大值
3)实际操作
- 首先这道题的输入参数比较多,自己需要定义数据结构体,该数据结构比较复杂。我们分析样例,第一行有两个整型的数据,第一个是用户手上的钱,第二个是物品的个数,剩余的几行都是每个物品的三个属性,第一个是物品的价格,第二个是物品的重要度,第三个是标识,代表的是是否是主件,如果是主件则为0,如果是附件数据表示的是则为第几个主件的附件。那么可以定义一维数组,数组的长度为物品的个数,数组的每个元素是一个物品对象,里面包含了四个属性,该物品的价格v,物品的重要度,物品不是主件,附件1初始化-1,附件2初始化-2。
- 定义输入流对象,接收两个整型的数据,价格和物品个数
- 定义一维数组,数组的长度是物品的个数
- 遍历一维数组,给每一个值创建一个物品对象 new Goods
- 遍历输入的每一行,赋值给每一个对象
- 遍历第一行的每个数据,第一个数据赋值给goods[i].v = v
- 遍历第一行的每个数据,第二个数据赋值给goods[i].p = p*v
- 遍历第一行的每个数据,第三个数据进行判断,如果为0,则把物品为否的主件改为是 goods[i].main = true;如果不是,则为附件,如果附件goods[i].a1 == -1,则说明是第一个附件,把当前第几个物品赋值给a1,如果附件goods[i].a1 != -1,goods[i].a2 != -1是,则说明是第二个附件,把当前第几个物品赋值给a2。 这样就把输入的数据处理完成了。
- 输入的数据流处理完成后,就是处理业务规则。
- 首先是变量是物件的个数和钱的数量。所以可以定义dp[i][j]表示前i件物品,手上持有j钱时最大满意度。
- 用for循环进行双层遍历,并进行判断。
- 目前,有i件物品,手上持有j钱时,当不取i物品时,用户最大满意度为dp[i][j] = dp[i-1][ j ]
- 目前,有i件物品,手上持有j钱时,取i物品时,判断该物品是不是附件,if(!goods[i-1].main),如果是继续循环,如果不是,
- 则判断为主件,手持的钱是否大于买i-1的东西,if(j >= goods[i - 1].v),,则拿当前的i-1物品,dp[i][j] = Math.max(dp[i][j],dp[i-1][j-goods[i-1].v]+goods[i-1].p);
- 则判断是否为附件1if(goods[i-1].a1 == -1),手持的钱是否大于买i-1的东西和附件1的钱,如果是的话,dp[i][j] = Math.max(dp[i][j],dp[i-1][j-goods[i-1].v-goods[goods[i-1].a1].v]+goods[i-1].p+goods[goods[i-1].a1].p);
- 则判断是否为附件2if(goods[i-1].a2 == -1),手持的钱是否大于买i-1的东西和附件2的钱,如果是的话,dp[i][j] = Math.max(dp[i][j],dp[i-1][j-goods[i-1].v-goods[goods[i-1].a2].v]+goods[i-1].p+goods[goods[i-1].a2].p);
- 剩下的是拿附件1和附件2的东西,最后一种方法。
import java.util.Scanner;
/**
题目分析:
这个题目是0和1背包问题,可以用动态规划,背包问题是定义,变量是物品和背包剩余容量
dp[i][j]表示前i个物品,背包容量为j时的最大价值,物品的重量用w[i]表示,
物品的价值用v[i]表示。
分为两种状态:分为放或者不放两种选择
放得话就是dp[i][j] = dp[i-1][j-w[i]] + v[i]
不放得话就是dp[i][j] = dp[i-1][j]
最后取得dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-w[i]] + v[i])
本题在此基础上有一些条件,就是有附件的必须要有主件,
首先分两种情况,放或者不放
不放的时候一种方法,
当放的时候,有四种方法,
第一只放主件;
第二只放1个主件和附件1
第三十放1个主件和附件2
第四是放1个主件、附件1和附件2
总共有5种方法,这五种方法每次取最大值
实际操作:
首先定义dp[i][j]表示前i件物品,手上持有金额为j的时候用户最大满意度,
物品的金额为w[i],物品的满意度v[i],附件设为下标为a1、a2
递推公式:
不放:dp[i][j] = dp[i-1][j]
第一只放主件:dp[i-1][j-w[i]] + v[i]
第二只放1个主件和附件1:dp[i-1][j-w[i]-w[a1]] + v[i]+v[a1]
第三只放1个主件和附件2:dp[i-1][j-w[i]-w[a2]] + v[i]+v[a2]
第四是只放1个主件、附件1和附件2:dp[i-1][j-w[i]-w[a1]-w[a2]] + v[i]+v[a2]+v[a1]
最后取得dp[i][j] =
Math.max
(
Math.max(dp[i-1][j],dp[i-1][j-w[i]] + v[i]),
Math.max(dp[i-1][j-w[i]-w[a1]] + v[i]+v[a1],dp[i-1][j-w[i]] + v[i]),
Math.max(dp[i-1][j-w[i]-w[a2]] + v[i]+v[a2],dp[i-1][j-w[i]-w[a1]-w[a2]] + v[i]+v[a2]+v[a1])
)
初始化值:
*/
public class Main
{
public static void main(String[] args)
{
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext())
{
int n = scanner.nextInt();
int m = scanner.nextInt();
Goods[] goods = new Goods[n];
for(int i=0; i < n; i++)
{
goods[i] = new Goods();
}
for (int i=0; i < m; i++)
{
int v = scanner.nextInt();
int p = scanner.nextInt();
int q = scanner.nextInt();
goods[i].v = v;
goods[i].p = p*v;
if(q==0)
{
goods[i].main = true;
}
else if(goods[q-1].a1 == -1)
{
goods[q-1].a1 = i;
}
else
{
goods[q-1].a2 = i;
}
}
int[][] dp = new int[m+1][n+1];
for(int i = 1; i <= m; i++)
{
for(int j = 1; j <= n; j++)
{
dp[i][j] = dp [i-1][j];
if(!goods[i-1].main)
{
continue;
}
if(j >= goods[i-1].v)
{
dp[i][j] = Math.max(dp[i][j],dp[i-1][j-goods[i-1].v]+goods[i-1].p);
}
if( goods[i-1].a1 != -1 && j >= goods[i-1].v +goods[goods[i-1].a1].v)
{
dp[i][j] = Math.max(dp[i][j],dp[i-1][j-goods[i-1].v-goods[goods[i-1].a1].v]+goods[i-1].p+goods[goods[i-1].a1].p);
}
if( goods[i-1].a2 != -1 && j >= goods[i-1].v +goods[goods[i-1].a2].v)
{
dp[i][j] = Math.max(dp[i][j],dp[i-1][j-goods[i-1].v-goods[goods[i-1].a2].v]+goods[i-1].p+goods[goods[i-1].a2].p);
}
if( goods[i-1].a2 != -1 &&goods[i-1].a1 != -1 && j >= goods[i-1].v +goods[goods[i-1].a2].v+goods[goods[i-1].a1].v)
{
dp[i][j] = Math.max(dp[i][j],dp[i-1][j-goods[i-1].v-goods[goods[i-1].a2].v-goods[goods[i-1].a1].v]+goods[i-1].p+goods[goods[i-1].a2].p+goods[goods[i-1].a1].p);
}
}
}
System.out.println(dp[m][n]);
}
}
}
class Goods
{
int v;
int p;
boolean main = false;
int a1 = -1;
int a2 = -1;
}
7.走方格数
描述
请计算n*m的棋盘格子(n为横向的格子数,m为竖向的格子数)从棋盘左上角出发沿着边缘线从左上角走到右下角,总共有多少种走法,要求不能走回头路,即:只能往右和往下走,不能往左和往上走。
注:沿棋盘格之间的边缘线行走
数据范围: 1 \le n,m \le 8 \1≤n,m≤8
输入描述:
输入两个正整数n和m,用空格隔开。(1≤n,m≤8)
输出描述:
输出一行结果
思路分析:
- 这个是两种状态,向左或者向右,那么可以递归,会重复用到之前的值,所以用动态递归
- 定义dp[i][j]]是从i,j位置走到右下角有dp[i][j]种算法
- 递推公式dp[i][j]=dp[i-1][j]+dp[i][j-1]
- 初始化值,当只有一行或者一列时,都只有一种方法dp[i][0]=1;,dp[0][j]=1;
public class Main
{
public static void main(String[] args)
{
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext())
{
int n = scanner.nextInt();
int m = scanner.nextInt();
System.out.println(scheme(n, m));
}
}
//定义函数,参数是方格的横和列的位置,返回值是走法数
public static int scheme(int n, int m)
{
if(n<1 || m> 8)
{ return 0;}//定义一个二维数据dp[i][j]是从i,j这个位置走到右下角的方法数
int[][] dp = new int[n+1][m+1];
for(int i=0;i<n+1;i++)
{
dp[i][0]=1;
for(int j = 0;j <m+1;j++)
{
dp[0][j]=1;
if(i==0||j==0)
{
// dp[i][j]=1;
}
else
{
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
}
return dp[n][m];
}
}
4.放苹果
题目解析:把m个苹果放到n个盘子里面,有多少种方法,
这个里面存在放或者不放两种选择,变化的参数是苹果和盘子,而且会存在重复,所以用动态规划
思路分析:
- dp[m][n]是把m个苹果放到n个盘子里面有多少种方法
- 当n>m时,无论如何都至少有n-m个空盘子,且这些盘子影响不了分发,所以d[m][n]=d[m][n-m]
- 当m>=n时,该问题变成了是否有空盘子的问题
- 当有空盘子,那么至少为一个d[m][n]=dp[m][n-1]
- 当没有空盘子,那么先把m个苹果分完后,再把m-n个苹果放到n个盘子里d[m][n]=dp[m-n][n]
- 处理初始值和边界条件,只有1个盘子,或者只有1个苹果都只有一种方法
实际操作:
- 定义一个二维数组int dp[][] = new dp[m+1][n+1]
- 定义递推公司,进行if判断
- 当n>m,d[m][n]=d[m][m]
- 当m>=n,d[m][n]=dp[m][n-1]+dp[m-n][n]
- 当m=1或者n=1 d[m][n] = 1
import java.util.Scanner;
public class Main
{
public static void main(String[] args)
{
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext())
{
int m = scanner.nextInt();
int n = scanner.nextInt();
System.out.println(putApple(m,n));
}
scanner.close();
}
//创建一个函数,给两个参数,苹果数目m,盘子数目n,输出方法数
private static int putApple(int m,int n)
{
if( m < 0 || n < 0 || m > 10 ||n > 10 || n == 0)
return 0;
int dp[][] = new int[m+1][n+1];
for(int j = 0; j <= n; j++) {
dp[0][j] = 1;
}
for ( int i = 1; i < m+1; i++)
{
for (int j = 1; j < n+1; j++)
{
if( i == 1 || j == 1 )
{
dp[i][j] = 1;
}
if (i < j)
{
dp[i][j] = dp[i][j-1];
}
else
{
dp[i][j] = dp[i][j-1] + dp[i-j][j];
}
}
}
return dp[m][n];
}
}
7.机器人动态递归
动态规划的题是指递归场景中存在大量重复计算是,加的傻缓存的优化思想:
- 第一步先判断是否是递归;
- 第二步进行分析看是否有重复计算;
- 第三步设计动态规划
1.题目分析:
一行N个位置,机器人在中间位置可以往左或者往右走,方法有多少种,看到这个一般有是或者否的两种选择,且问有多少种方法。那么就可以考虑动态规划。
思路分析:(整体变化的值是当前位置和剩余步数)
- 定义一个函数,含义是总共N个位置,机器人在M当前位置,还剩K步,到达P位置的所有方法数。
- 当在1位置时,只能去2位置,且剩下k-1步,这个剩下操作跟前一步一样,只是参数不一样,进行递归调用
- 当在N位置时,只能去N-1位置,且剩下k-1步,这个剩下操作跟前一步一样,只是参数不一样,进行递归调用
- 当在中间位置 时,有两种选择,去M-1的位置和M+1的位置,且剩下k-1步,这个剩下操作跟前一步一样,只是参数不一样,进行递归调用。
2.实现操作:
- 定义一个函数,返回值是所有方法数,参数是N、M、K、P
- 定义一个二维动态数组记录历史操作:dp[i][j],代表的是当前在i位置,还剩j步,到达指定位置有多少种方法
- 初始化值:当i在目标位置时,dp[aim][]=1
- 推导公式1:双层循环,当i=1时,dp[1][j]=dp[2][j-1]
- 推导公式2:双层循环,当i=n时,dp[n][j]=dp[n-1][j-1]
- 推导公式3:双层循环,当在中间时,dp[i][j]=dp[i-1][j-1]+dp[i+1][j-1]
- 返回dp[M][k]
- 进行非法制的判断
3.机试关键代码
package com.atguigu.mr;
// 机器人当前来到的位置是cur,
// 机器人还有rest步需要去走,
// 最终的目标是aim,
// 有哪些位置?1~N
// 返回:机器人从cur出发,走过rest步之后,最终停在aim的方法数,是多少
public class Code02_RotbotWalk {
public static void main(String[] args)
{
System.out.println(robot(5, 2, 4, 6));
}
public static int robot(int N, int start, int aim, int K)
{
if (N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1) {
return -1;
}
int[][] dp = new int[N + 1][K + 1];
dp[aim][0] = 1;
for (int rest = 1; rest <= K; rest++) {
dp[1][rest] = dp[2][rest - 1];
for (int cur = 2; cur < N; cur++) {
dp[cur][rest] = dp[cur - 1][rest - 1] + dp[cur + 1][rest - 1];
}
dp[N][rest] = dp[N - 1][rest - 1];
}
return dp[start][K];
}
}
4.总结
8.