-
常用的数据结构:数组、链表、栈、队列、哈希表、树、图等的基本概念和实现;
-
常用的算法:DFS / BFS、最短路径算法(Dijkstra)、贪心算法、动态规划、蓄水池算法、Manacher 算法、字符串匹配算法等;
动态规划算法
-
动态规划(Dynamic programming,简称DP),核心思想是把原问题分解成子问题进行求解。
与分治法不同,分治法通常利用递归求解,将分解后的子问题看作独立的,通常会反复求解公共子问题。
动态规划通常利用迭代法自底向上求解,或者有记忆的递归法。分解后的子问题认为有重叠部分,将结果保存起来,每个子问题只求解一次。
递归:递归常被用来描述以自相似方法重复事物的过程,在数学和计算机科学中,指的是在函数定义中使用函数自身的方法。
迭代:重复反馈过程的活动,每一次迭代的结果会作为下一次迭代的初始值。 -
适用场景:动态规划方法通常用来求解最优化问题。
-
使用动态规划的问题应该具备:最优子结构(原问题的最优解包含子问题的最优解,先求得子问题的最优解然后求原问题最优解);子问题重叠(子问题再分解成子问题,子问题之间存在重叠的情况)
-
动态规划问题自底向上求解;贪心策略是先选择当时(局部)最好的选择
-
举例:斐波那契数列、矩阵链乘法(代价最小的组合方法)、最大公共子序列LCS、背包问题。
-
过程:划分子问题、表示子问题、状态转移(父问题如何由子问题推导)、确定边界(初始状态,最小子问题,最终状态)
-
实现:自底向上、自顶向下
- 自底向上:根据初始状态,逐步推导到最终状态,而这个转移的过程,必定是一个拓扑序。例如线性模型、区间模型。经典实现是斐波那契数列。
- 自顶向下:从最终状态出发,如果遇到一个子问题还未求解,那么就先求解子问题。如果子问题已经求解,那么直接使用子问题的解,所以自顶向下动态规划又有一个形象生动的名字,叫做记忆化搜索,一般我们采用递归的方式进行求解。一般用在树上面,因为根据父亲结点,很容易找到所有的子问题,也就是所有的子结点;而自底向上的话,要去统计这个结点的所有兄弟结点是否已经实现。会稍微复杂一点,而且比较难理解。
【参考】https://baijiahao.baidu.com/s?id=1631319141857419948&wfr=spider&for=pc
1 背包问题
给定一个数组 arr,和一个整数 aim。如果可以任意选择 arr 中的数字,能不能累加得到 aim,返回 true 或者 false。
递归
- 每个位置的数可以选择相加或不相加,用f[i][sum]表示到第i个数字时的累加值是sum,即f[1][sum]两种情况,加第一个数或不加第一个数。然后每个节点再分两种情况,可以用二叉树表示。
- 测试用例:arr={3,2,5},aim=7.
- f[3][7]在不加第一个数,加第二个数,加第三个数的路径上
public class IsAimByRecursion {
public static boolean isAim(int[] arr, int aim){
if(arr == null || arr.length < 1){
return false;
}
return process(arr, 0, 0, aim);
}
//sum表示下标i之前的数字的决策累加和(不包括a[i])
public static boolean process(int[] arr, int i, int sum, int aim){
// 递归终止条件,到数组最后一个数时,sum是否等于aim
if(i == arr.length){
return sum == aim;
}
// 表示当前数字没有被加上,开始下一个数字(第一轮a[0]不加上,i+1开始下一次递归决定a[1])
return process(arr, i + 1, sum, aim)
// 表示当前数字被加上,开始下一个数字
// 最后有一个等于aim,一路往上都返回true
|| process(arr, i + 1, sum + arr[i], aim);
}
}
动态规划
public static boolean isAimByDP(int target,int[] arrs){
int sum = 0;
for(int i = 0; i < arrs.length; i++){
sum += arrs[i];
}
//所有值加起来都没有目标值大,直接返回false
if(target > sum)
return false;
boolean[][] dp = new boolean[arrs.length+1][sum+1];
//将最后一行已经知道结果的填充进去
for (int j = 0; j <= sum; j++) {
dp[arrs.length][j] = j==target;
}
for(int i = arrs.length - 1; i >= 0; i--){
for(int j = 0; j <= sum; j++){
if(j + arrs[i] <= sum){
dp[i][j] = dp[i+1][j] || dp[i+1][j+arrs[i]];
}
}
}
return dp[0][0];
}
2 矩阵最小路径问题
给你一个二维数组,二维数组中的每个数都是正数,要求从左上角走到右下角,每一步只能向右或者向下。沿途经过的数字要累加起来。返回最小的路径和。
递归
- 从左上角开始,递归地向右或向下找最小路径。