算法现状
1、科技公司面试必考算法
2、题目类型多,没有固定模板
3、难度属于中上
4、根据面试经验,一半失败的面试都与动态规划有关
基本概念
算法导论这本书是这样介绍这个算法的,动态规划与分治方法类似,都是通过组合子问题的解来来求解原问题的。再来了解一下什么是分治方法,以及这两者之间的差别,分治方法将问题划分为互不相交的子问题,递归的求解子问题,再将它们的解组合起来,求出原问题的解。而动态规划与之相反,动态规划应用与子问题重叠的情况,即不同的子问题具有公共的子子问题(子问题的求解是递归进行的,将其划分为更小的子子问题)。在这种情况下,分治方法会做许多不必要的工作,他会反复求解那些公共子子问题。而动态规划对于每一个子子问题只求解一次,将其解保存在一个表格里面,从而无需每次求解一个子子问题时都重新计算,避免了不必要的计算工作。
什么是动态规划?
如下图所示,题A为动态规划类型的题目,而题B却不是。
动态规划题目特点
相关问题举例
问题一(求最大最小值动态规划)
直觉做法
最少的硬币组合——>尽量使用面值大的硬币,最后如果可以用一种硬币付清就行
7+7+7=21
21+2+2+2=27
此处需要6枚硬币
但正确答案却不是这样的,而是:
7+5+5+5+5=27,需要5枚硬币。
动态规划解法
步骤一:确定状态
- 状态在动态规划中的作用属于定海神针
- 简单来说,解动态规划的时候需要解开一个数组,数组的每个元素f[i]或者f[i][j]代表什么
——类似于解数学题中,X、Y、Z代表什么 - 确定状态需要两个意识
——最后一步
——子问题
最后一步
关键点:
子问题
由上可给出一个递归解法:
但上述递归算法存在问题:
如图可知,该递归解法做了很多重复计算,效率低下。
该如何避免这个问题了?我们可以将计算结果保存下来,并改变计算顺序,让我们接着往下看。
步骤二:状态转移方程
步骤三:初始条件和边界情况
步骤四:计算顺序
小结
leetcode例题
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
链接:https://leetcode.cn/problems/gaM7Ch
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
示例 4:
输入:coins = [1], amount = 1
输出:1
示例 5:
输入:coins = [1], amount = 2
输出:2
提示:
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104
class Solution {
public int coinChange(int[] coins, int amount) {
int[] f=new int[amount+1];
int n=coins.length;
f[0]=0;
for (int i=1;i<=amount;i++){
f[i]=Integer.MAX_VALUE;
for (int j=0;j<n;j++){
if (i>=coins[j]&&f[i-coins[j]]!=Integer.MAX_VALUE){
f[i]=Math.min(f[i-coins[j]]+1,f[i]);
}
}
}
if (f[amount]==Integer.MAX_VALUE) {
f[amount]=-1;
}
return f[amount];
}
}
结果
问题二(计数)
步骤一:确定状态
最后一步
子问题
步骤二:状态转移方程
步骤三:初始条件和边界情况
步骤四:计算顺序
leetcode例题
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
链接:https://leetcode.cn/problems/2AoeFn
示例 1:
输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
示例 3:
输入:m = 7, n = 3
输出:28
示例 4:
输入:m = 3, n = 3
输出:6
提示:
1 <= m, n <= 100
题目数据保证答案小于等于 2 * 109
class Solution {
public int uniquePaths(int m, int n) {
int[][] f=new int[m][n];
f[0][0]=1;
for (int i=0;i<m;i++){
for (int j=0;j<n;j++){
if (i==0||j==0){
f[i][j]=1;
}else {
f[i][j]=f[i-1][j]+f[i][j-1];
}
}
}
return f[m-1][n-1];
}
}
结果
问题三(求存在性)
步骤一:确定状态
最后一步
子问题
步骤二:转移方程
步骤三:初始条件和边界情况
步骤四:计算顺序
leetcode例题
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
链接:https://leetcode.cn/problems/jump-game/
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
提示:
1 <= nums.length <= 3 * 104
0 <= nums[i] <= 105
class Solution {
public boolean canJump(int[] nums) {
boolean[] f=new boolean[nums.length];
f[0]=true;
for (int i=1;i<nums.length;i++){
f[i]=false;
for (int j=0;j<i;j++){
if (nums[j]>=i-j&&f[j])
f[i]=true;
}
}
return f[f.length-1];
}
}
结果