废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是【动态规划】,使用【数组】这个基本的数据结构来实现,这个高频题的站点是:CodeTop,筛选条件为:目标公司+最近一年+出现频率排序,由高到低的去牛客TOP101去找,只有两个地方都出现过才做这道题(CodeTop本身汇聚了LeetCode的来源),确保刷的题都是高频要面试考的题。
名曲目标题后,附上题目链接,后期可以依据解题思路反复快速练习,题目按照题干的基本数据结构分类,且每个分类的第一篇必定是对基础数据结构的介绍。
买卖股票的最佳时机【EASY】
来从动态规划最简单的题开始训练
题干
解题思路
按照动态规划的思路进行状态设计和状态转移方程编写
1 定义状态(定义子问题)
dp[i]:表示第i天卖出股票的最大利润。
2 状态转移方程(描述子问题之间的联系)
根据状态的定义,由于 prices[i]
一定会被选取,并且以 prices[i]
结尾的卖出日期与以 prices[i - 1]
结尾的卖出日期只相差一个元素 nums[i]
。假设数组 prices的值全都严格大于 0,那么一定有 dp[i] = dp[i - 1] + prices[i]-prices[i-1]
。可是 dp[i - 1]
有可能是负数,于是分类讨论:
- 如果
dp[i - 1] > 0
,那么可以把prices[i]-prices[i-1]
直接接在dp[i - 1]
表示的那个数组的后面,得到和更大的利润; - 如果
dp[i - 1] <= 0
,那么 prices[i] 加上前面的数 dp[i - 1] 以后值不会变大。于是 dp[i] 「另起炉灶」,此时单独的利润prices[i]-prices[i-1]
的值,就是 dp[i]。
以上两种情况的最大值就是 dp[i] 的值
3 初始化状态
dp[0]
根据定义,初始化第1天买入第一天卖出利润为0,初始化利润值
4 求解方向
这里采用自底向上,从最小的状态开始求解
5 找到最终解
这里的dp[i]只是第i天卖出的最大利润,并不是题目中的问题,买卖股票的最大利润,所以最终解并不是子问题的解,需要用一个MAX值承载,通过与dp[i]比较更新最终解
代码实现
给出代码实现基本档案
基本数据结构:数组
辅助数据结构:无
算法:动态规划
技巧:无
其中数据结构、算法和技巧分别来自:
- 10 个数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie 树
- 10 个算法:递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法
- 技巧:双指针、滑动窗口、中心扩散
当然包括但不限于以上
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param prices int整型一维数组
* @return int整型
*/
public int maxProfit (int[] prices) {
// 1 初始化动态规划数组:维护第i天卖出的最大利润
int[] dp = new int[prices.length];
int maxValue = dp[0];
for (int i = 1; i < prices.length; i++) {
// 2 计算当前天和前一天卖出的利润差
int curValue = prices[i] - prices[i - 1];
// 3 状态转移方程:第i天卖出的最大利润为:如果i-1天卖出的最大利润为负数,则舍弃,否则累加第i天的最大利润
dp[i] = dp[i - 1] <= 0 ? curValue : dp[i - 1] + curValue;
// 4 每次计算完最小问题后更新最大值
maxValue = Math.max(dp[i], maxValue);
}
return maxValue;
}
}
复杂度分析
时间复杂度:遍历了一遍数组,所以时间复杂度为O(N)
空间复杂度:定义了动规数组,空间复杂度为O(N)
拓展知识:回顾动态规划
动态规划(Dynamic Programming,简称DP)是一种解决复杂问题的算法设计技术,常用于优化问题和组合问题的求解。它通过将原问题分解成子问题,并保存子问题的解,以避免重复计算,从而提高算法的效率。动态规划通常用于解决具有重叠子问题和最优子结构性质的问题。
动态规划的基本思想可以总结为以下几个步骤:
-
定义问题的状态:首先要明确定义问题的状态,这些状态可以用来描述问题的各种情况。
-
找到状态转移方程:状态转移方程描述了问题之间的联系,即如何从一个状态转移到另一个状态。这通常涉及到问题的递归关系,通过这个关系可以从较小规模的子问题得到更大规模的问题的解。
-
初始化状态:确定初始状态的值,这通常是问题规模最小的情况下的解。
-
自底向上或自顶向下求解:动态规划可以采用自底向上(Bottom-Up)或自顶向下(Top-Down)的方式求解问题。自底向上是从最小的状态开始逐步计算,直到得到最终问题的解;自顶向下是从最终问题开始,递归地计算子问题的解,直到达到最小状态。
-
根据问题的要求,从状态中找到最终解。
动态规划常见的应用领域包括:
-
最长公共子序列问题:在两个序列中找到一个最长的共同子序列,用于比较字符串相似性。
-
背包问题:在给定一定容量的背包和一组物品的情况下,选择一些物品放入背包,使得物品的总价值最大或总重量不超过背包容量。
-
最短路径问题:求解图中两点之间的最短路径,如Dijkstra算法和Floyd-Warshall算法。
-
硬币找零问题:给定一组硬币面额和一个目标金额,找到使用最少数量的硬币组合成目标金额。
-
斐波那契数列问题:求解斐波那契数列的第n个数,通过动态规划可以避免重复计算。
动态规划是一种强大的问题求解方法,但它并不适用于所有类型的问题。在使用动态规划时,需要仔细分析问题的性质,确保问题具有重叠子问题和最优子结构性质,以确保动态规划算法能够有效地解决问题。