零、动态规划
https://cloud.tencent.com/developer/article/1817113
1.什么样的问题可以考虑使用动态规划解决呢?
如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划。(比如跳台阶问题,自顶向下的话,会有多次重复调用,即重叠子问题,因此可以逆向自底向上思考,考虑动态规划)
动态规划比较经典的场景:
- 最长递增子序列
- 最小编辑距离
- 背包问题
- 凑零钱问题
2.动态规划的解题思路
动态规划的核心思想就是拆分子问题,记住过往,减少重复计算,并且动态规划一般都是自底向上的。
思路如下:
- 穷举分析
- 确定边界
- 找出规律,确定最优子结构
最优子结构
一道动态规划问题,其实就是一个递推问题。假设当前决策结果是f(n),则最优子结构就是要让 f(n-k) 最优,最优子结构性质就是能让转移到n的状态是最优的,并且与后面的决策没有关系,即让后面的决策安心地使用前面的局部最优解的一种性质 - 写出状态转移方程
接下来以为leetcode300题为例,
一、300最长递增子序列
1.题目
https://leetcode-cn.com/problems/longest-increasing-subsequence/
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
2.思路
参考:https://cloud.tencent.com/developer/article/1817113
依次遍历先找到以i为尾节点的最长的递增序列,然后根据记录的之前最大值的记录和当前值比较
3.代码
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums.length == 0) {
return 0;
}
int[] dp = new int[nums.length];
//初始化就是边界情况
dp[0] = 1;
int maxans = 1;
//自底向上遍历
for (int i = 1; i < nums.length; i++) {
dp[i] = 1;
//从下标0到i遍历
for (int j = 0; j < i; j++) {
//找到前面比nums[i]小的数nums[j],即有dp[i]= dp[j]+1
if (nums[j] < nums[i]) {
//因为会有多个小于nums[i]的数,也就是会存在多种组合了嘛,我们就取最大放到dp[i]
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
//求出dp[i]后,dp最大那个就是nums的最长递增子序列啦
maxans = Math.max(maxans, dp[i]);
}
return maxans;
}
}
二、509斐波那契数
https://leetcode-cn.com/problems/fibonacci-number/submissions/
解法可参考上一篇动态化搜索的博文
三、62不同路径
1.题目
https://leetcode-cn.com/problems/unique-paths/
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
2.思路
1.暴力解法,dfs但是需要加map记录已经走过的节点 key存储节点,value存储该节点对应的路径数
2.动态规划
3.代码
1.暴力解法
class Solution {
public int uniquePaths(int m, int n) {
//dfs尝试
int count =0;
Map<Pair,Integer> cache = new HashMap<>();
return dfs(m,n,0,0,cache,count);
}
public int dfs(int m, int n, int r,int l,Map<Pair, Integer> cache,int count){
//cache缓存已经走过的节点
Pair p = new Pair(r,l);
if (r == m-1 || l == n-1) return 1;
if (cache.containsKey(p)) return cache.get(p);
cache.put(p,dfs(m,n,r+1,l,cache,count)+dfs(m,n,r,l+1,cache,count));
return cache.get(p);
}
}
2.动态规划
class Solution {
public int uniquePaths(int m, int n) {
//存储每个ij对应可走的路径数
int [][] res = new int [m][n];
//边界位置可走的方案只有一个
for (int i=0; i<m;++i){
res[i][0]=1;
}
for (int j=0; j<n;++j){
res[0][j]=1;
}
for(int i=1;i<m;++i){
for(int j=1;j<n;++j){
res[i][j]=res[i-1][j]+res[i][j-1];
}
}
return res[m-1][n-1];
}
}
三、121买卖股票的最佳时机
1.题目
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
2.思路
动态规划
记录当前手里持股和不持股的现金流,依次遍历即可找到最大的现金流
3.代码
暴力解法——枚举每一天对应的利润,取最大值,会超时
class Solution {
public int maxProfit(int[] prices) {
//base case
if (prices.length < 2) return 0;
int maxPrice=0;
for(int i=1; i<prices.length; ++i){
for(int j=0; j<i;++j){
maxPrice = Math.max(prices[i]-prices[j],maxPrice);
}
}
return maxPrice;
}
}
动态规划
class Solution {
public int maxProfit(int[] prices) {
//base case
if (prices.length < 2) return 0;
//存储持股或不持股状态下的当前现金流
int[][] res = new int[prices.length][2];
//初始化第一天现金流为0;第一天持股现金流为第一天股票的交易额;
res[0][0]=0;
res[0][1] = -prices[0];
//从第二天遍历找到
for(int i=1; i<prices.length;i++){
//不持股的两种情况1.i-1不持股,什么都没做;2.i-1持股,i抛售
res[i][0] = Math.max(res[i-1][0], prices[i]+res[i-1][1]);
//持股的两种情况1.i-1持股,什么都没做;2.i-1不持股,i买入
res[i][1] =Math.max(res[i-1][1], -prices[i]);
}
//最后手里一定没有股票
return res[prices.length-1][0];
}
}
四、70爬楼梯
1.题目
https://leetcode-cn.com/problems/climbing-stairs/
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
2.思路
暴力解法会超时,累计和的问题可采用滚动数组的方法,记录i-2 i-1和sum的值,不断更新
3.代码
class Solution {
public int climbStairs(int n) {
//base case
if (n<=2) return n;
//滚动数组思想,即i的方法数为i-1和i-2的方法数的总和
int n1 = 1;
int n2 =0;
int sum = 2;
for(int i=3; i<=n;++i){
n2 = n1;
n1 =sum;
sum = n1+n2;
}
return sum;
}
}
五、279 完全平方数
1.题目
https://leetcode-cn.com/problems/perfect-squares/
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
2.思路
https://leetcode-cn.com/problems/perfect-squares/solution/wan-quan-ping-fang-shu-by-leetcode-solut-t99c/
3.代码
class Solution {
public int numSquares(int n) {
//枚举每个数和剩余数的可能性总和
int[] res = new int[n+1];
res[0]=0;
for(int i =1; i<=n;i++){
int minn =Integer.MAX_VALUE;
for(int j=1; j*j<=i;++j){
minn = Math.min(res[i-j*j],minn);
}
res[i]=minn+1;
}
return res[n];
}
}
六、221最大正方形
1.题目
https://leetcode-cn.com/problems/maximal-square/
在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。
2.思路
1.暴力解法
找到最长的边,然后计算最大的面积
2.动态规划
3.代码
1.暴力解法
class Solution {
public int maximalSquare(char[][] matrix) {
//暴力解法
//正方形问题的关键需要判断最大的边长
int row = matrix.length;
int line = matrix[0].length;
int maxSide =0;
if(row==0||line==0 || matrix == null) return maxSide;
//遇到的1视为左上角
for(int i = 0; i<row;++i){
for(int j=0; j<line;++j){
if (matrix[i][j] == '1'){
maxSide = Math.max(1, maxSide);
int tempSide = Math.min(row-i, line-j) ;
//以i为左上角,找到当前最大的边长
for(int k=1;k<tempSide;++k){
boolean judge = true;
if (matrix[i+k][j+k]=='0') break;
for(int m=0; m<k;++m){
//break的意思是跳出当前循环,如果不加指示符,还会执行下面的语句
if (matrix[i+m][j+k] =='0' || matrix[i+k][j+m]=='0' ){
judge =false;
break;
}
}
if(judge){
maxSide = Math.max(maxSide,k+1);
}else{
break;
}
}
}
}
}
int maxSquare = maxSide*maxSide;
return maxSquare;
}
}
2.动态规划
class Solution {
public int maximalSquare(char[][] matrix) {
//暴力解法
//正方形问题的关键需要判断最大的边长
int row = matrix.length;
int line = matrix[0].length;
int maxSide =0;
if(row==0||line==0 || matrix == null) return maxSide;
//遇到的1视为右下角,取左边,左上,上的最小值+当前值1
int [][] temp = new int[row][line];
for (int i =0; i < row; ++i){
for(int j=0; j<line;++j){
if(matrix[i][j] == '1'){
if(i==0||j==0) {
temp[i][j]=1;
}else{
temp[i][j] = Math.min(Math.min(temp[i-1][j],temp[i-1][j-1]),temp[i][j-1])+1;
}
}
maxSide = Math.max(maxSide,temp[i][j]);
}
}
int maxSquare = maxSide*maxSide;
return maxSquare;
}
}