目录
1-1、题目列表(按照两个平台输出,LeetCode 和 牛客)
一、该类题目---基础信息列表&背景
大厂面试算法题,为了显示高逼格,显示高档次,经常跟实际问题相结合(如 股票买卖/ 找零钱问题/ 汽油问题 / 打家劫舍 / 分糖果 / 岛屿问题 ),想要考察面试人,从实际问题转化成熟悉的解题模型,灵活运用能力。近期问了些面试字节的朋友,30%都考到了股票。本次这类题型的梳理,也是对自己解决这类算法的一个总结。
- 题目难易分类【easy】【middle】【hard】
- 题目频率(牛客有考察次数)【高频】(10<=考察次数) 【中频】(2 <=考察次数 < 10) 【普通】
1-1、题目列表(按照两个平台输出,LeetCode 和 牛客)
| 实际问题 | 刷题平台 & 题序号 & 题目难易度 & 考察频次 |
| 股票买卖 【贪心】 【动态规划】 | LeetCode(题目难易度---出现频率) 121. 买卖股票的最佳时机【easy】-----89.25% 122. 买卖股票的最佳时机 II【middle】-----72.22% 123. 买卖股票的最佳时机 III【hard】-----52.67% 188. 买卖股票的最佳时机 IV)【hard】-----64.63% 其他高频 309. 最佳买卖股票时机含冷冻期【middle】-----66.89% 剑指 Offer 63. 股票的最大利润【middle】-----65.73% 714. 买卖股票的最佳时机含手续费【middle】-----46.0 % 牛客【题库--算法篇--面试高频榜单】 NC7 买卖股票的最好时机(一)【easy】【高频】-----贪心, 动态规划 NC134 买卖股票的最好时机(二)【middle】【中频】-----贪心, 动态规划 NC135 买卖股票的最好时机(三)【hard】【中频】----- 动态规划 NC167 买卖股票的最好时机(四)【hard】【普通】----- 动态规划 本次文章内容:大厂面试算法题---实际场景高逼格解决思路总结----股票买卖/ 找零钱问题/ 汽油问题 / 打家劫舍 / 分糖果 / 岛屿问题 总结归纳_安吉_lh1029的博客-CSDN博客 |
| 找零钱 【数组】 【动态规划】 | LeetCode(题目难易度---出现频率) 322. 零钱兑换【middle】-----72.24% 518. 零钱兑换 II【middle】-----55.95% 860. 柠檬水找零【easy】-----40.00% 牛客【题库--算法篇--面试高频榜单】 NC126 兑换零钱(一)【middle】【中频】----- 动态规划 NC203 兑换零钱(二)【middle】【普通】----- 数组,动态规划 |
| 汽油问题 【数组】 【动态规划】 | LeetCode(题目难易度---出现频率) 134. 加油站【middle】-------- 67.94% 牛客【题库--算法篇--面试高频榜单】 NC235 加油站【middle】【普通】----- 数组,贪心 |
| 打家劫舍 【动态规划】 | LeetCode(题目难易度---出现频率) 198. 打家劫舍【middle】-----74.11% 213. 打家劫舍 II【middle】-----53.29% 337. 打家劫舍 III【middle】-----65.02% 牛客【题库--算法篇--面试高频榜单】 NC176 打家劫舍(一)【middle】【普通】----- 动态规划 NC177 打家劫舍(二)【middle】【普通】----- 动态规划 NC178 打家劫舍(三)【hard】【普通】----- 动态规划 |
| 分糖果问题 【贪心】 | LeetCode(题目难易度---出现频率) 135. 分发糖果【hard】-----76.95% 575. 分糖果【easy】-----19.33% 1103. 分糖果 II【easy】-----7.16% 牛客【题库--算法篇--面试高频榜单】 NC130 分糖果问题【middle】【中频】----- 贪心 |
| 岛屿问题 【DFS】 【BFS】 【递归】 | LeetCode(题目难易度---出现频率) 200. 岛屿数量【middle】-----87.58% 695. 岛屿的最大面积【middle】-----72.09% 463. 岛屿的周长【easy】-----41.34% 1905. 统计子岛屿【middle】-----34.40% 牛客【题库--算法篇--面试高频榜单】 NC109 岛屿数量【middle】【高频】----- 广度优先BFS, DFS NC185 岛屿的最大面积【middle】【普通】----- 递归 |
[本文应该是涉及算法数量最多的一篇算法梳理了,初了岛屿问题属于DFS模式以外,其余的实际问题基本都是 动态规划,贪心思路,希望看完本文,能够通过各个算法的解题思路,对这两个算法----动态规划,贪心,如何解题有一个深度的了解!更能体会到大厂面试的套路。]
1-2、题型分析
这种跟实际结合较紧密的问题,问题名称和考点统计如下:
股票买卖【贪心】【动态规划】
找零钱【数组】【动态规划】
汽油问题【数组】【动态规划】
打家劫舍【动态规划】
分糖果问题【贪心】
岛屿问题【DFS】【BFS】【递归】
从上面分析,除了岛屿问题外,其余以【贪心】【动态规划】为主。
1-2-1、贪心算法理论
贪心算法(greedy algorithm ,又称贪婪算法):在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。
①建立数学模型来描述问题 。
②把求解的问题分成若干个子问题 。
③对每个子问题求解,得到子问题的局部最优解 。
贪心法求解的问题应具备如下2个特征:1、贪心选择性质。2、最优子结构性质
1-2-2、动态规划理论
动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
动态规划具备的特征:最优子结构性质,无后效性,子问题的重叠性
局限性:动态规划对于解决多阶段决策问题的效果是明显的,但是动态规划也有一定的局限性。首先,它没有统一的处理方法,必须根据问题的各种性质并结合一定的技巧来处理;另外当变量的维数增大时,总的计算量及存贮量急剧增大。因而,受计算机的存贮量及计算速度的限制,当今的计算机仍不能用动态规划方法来解决较大规模的问题,这就是“维数障碍”。
二、面试中算法实际问题具体分析
2-1、股票买卖问题
2-1-1、股票买卖最佳时机 (一)
(1)问题描述

(2) 股票买卖最佳时机(一)问题分析思路
1、某一天买入,在未来某一个不同日子卖出,买卖仅一次。
2、要获取利益最大值,我们肯定会想:如果我是在历史最低点买的股票就好了!在题目中,我们只要用一个变量记录一个历史最低价格minPrice 作为买入。卖出则第i天,当前价格减去最低价格作为差价即可,prices[i] - minPrice。
3、因此用上面的思路,我们只要遍历一次就能得到结果了。
使用这个思路:
- 时间复杂度:O(n),只需要遍历一次。
- 空间复杂度:O(1),只使用了常数个变量。
(3) 具体代码实现---动态规划
public class Solution{
public int maxPrice(int[] prices){
int maxProfit = 0;
int minPrice = Integer.MAX_VALUE; //最低价格初始化一个最大值,以防止影响题目结果
for(int i = 0; i < prices.length; i++){
minPrice = Math.min(minPrice, prices[i]);
maxProfit = Math.max(maxProfit, prices[i]-minPrice);
}
return maxProfit;
}
}
2-1-2、股票买卖最佳时机 (二)
(1)问题描述

(2)股票买卖最佳时机(二)问题分析思路
最多只能持有一股股票,也可以先购入在同一天出售,意味着,可以多次买卖该只股票,但是再次购买前必须卖出之前的股票。
方法一:动态规划思路(通用)
跟股票买卖最佳时机(一)思路类似,设置一个二维矩阵表示状态。
状态 dp[i][j] 定义如下:
dp[i][j] 表示到下标为 i 的这一天,持股状态为 j 时,我们手上拥有的最大现金数。
注意:限定持股状态为 j 是为了方便推导状态转移方程,这样的做法满足 无后效性。
其中:
第一维 i 表示下标为 i 的那一天( 具有前缀性质,即考虑了之前天数的交易 );
第二维 j 表示下标为 i 的那一天是持有股票,还是持有现金。这里 0 表示持有现金(cash),1 表示持有股票(stock)。
确认初始值:
- 如果什么都不做,
dp[0][0] = 0; - 如果持有股票,当前拥有的现金数是当天股价的相反数,即
dp[0][1] = -prices[i];
使用二维数组模式代码实现:
public class Solution{
public int maxProfit(int[] prices){
int len = prices.length;
if(len < 2) return 0;
//初始值
int[][] dp = new int[len][2];
// 0 代表现金,1代表持有股票
dp[0][0] = 0;
dp[0][1] = -prices[0];
for(int i =1 ; i< len; i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
}
return dp[len-1][0];
}
}
复杂度分析:
时间复杂度:O(N) ,这里 N 表示股价数组的长度;
空间复杂度:O(N) ,虽然是二维数组,但是第二维是常数,与问题规模无关。
在上面思路上可以进一步进行优化,由于空间复杂度时二维数组O(N),但是全题目看只有只有现金和持有股票两种情况,因此可以使用两个变量(cash, hold )代替,而不使用数组作为辅助空间,这样空间复杂度可以降低, 具体代码实现如下:
public class Solution{
public int maxProfit(int[] prices){
if(prices.length < 2) return 0;
//进行初始化,使用两个变量表示整个流程
int cash = 0;
int hold = -prices[0];
for(int i = 1; i < prices.length; i++){
cash = Math.max(cash, hold + prices[i]);
hold = Math.max(hold, cash - prices[i]);
}
return cash;
}
}
优化后复杂度分析:
时间复杂度:O(N),这里 N 表示股价数组的长度;
空间复杂度:O(1),分别使用两个滚动变量,将一维数组状态优化到常数大小。
方法二:贪心思路
由于不限制交易次数,只要今天股价比昨天高,就交易。举例1 5 8 4 2 3 6 4 10 7 3 2 1 6

备注:【贪心算法】和【动态规划】相比,它既不看前面(也就是说它不需要从前面的状态转移过来),也不看后面(无后效性,后面的选择不会对前面的选择有影响),因此贪心算法时间复杂度一般是线性的,空间复杂度是常数级别的;
其实可以简化成,只要是两天之间是上涨的,那我们就要这一段的收益。这个逻辑适用于贪心思路。此题贪心算法的决策是: 只加正数 。具体代码实现如下:
import java.util.*;
public class Solution{
public int maxProfit(int[] prices){
if(prices.length < 2) return 0;
int res = 0;
for(int i =1; i< prices.length; i++){
//贪心思路,分段计算,只有diff > 0, 才会重新设置maxPro
// 因此用Math.max直接进行获取最大值即可
int diff = prices[i]-prices[i-1];
res = Math.max(res, res + diff);
}
return res;
}
}
备注:这行代码 res = Math.max(res, res + diff);
等价写成 : res += Math.max(0, diff); // 即等价于只累加diff > 0的分段区间
复杂度分析:
- 时间复杂度:O(N),这里 N 表示股价数组的长度;
- 空间复杂度:O(1)。
2-1-3、股票买卖最佳时机 (三)
(1)问题描述

(2)股票买卖最佳时机(三)问题分析思路
1、根据题目,得出最多可以对该股票有两笔交易操作,一笔交易代表着一次买入与一次卖出,但是再次购买前必须卖出之前的股票。
2、这题用上题贪心思路就不适合,因为有笔数的限定,因此问题回归到动态规划上面。
使用多维数组表示 dp[i][j][k] (i代表具体哪天,j表示买入股票行为是1次买入还是2次买入,k表示当前是否持有股票),具体含义如下:
状态定义:dp[i][j][k] 表示在 [0, i] 区间里(状态具有前缀性质),交易进行了 j 次,并且状态为 k 时我们拥有的现金数。其中 j 和 k 的含义如下:
| i 表示: 具体哪天 时间范围 | j表示: j = 0 表示没有交易发生; | k表示: k = 0 表示当前不持股; |
进行初始化:
- dp[0][0][0] = 0:这是显然的;
- dp[0][0][1]:表示一次交易都没有发生,但是持股,这是不可能的,也不会有后序的决策要用到这个状态值,可以不用管;
- dp[0][1][0] = 0:表示发生了 1 次交易,但是不持股,这是不可能的。虽然没有意义,但是设置成 0 不会影响最优值;
- dp[0][1][1] = -prices[0]:表示发生了一次交易,并且持股,所以我们持有的现金数就是当天股价的相反数;
- dp[0][2][0] = 0:表示发生了 2 次交易,但是不持股,这是不可能的。虽然没有意义,但是设置成 0 不会影响最优值;
- dp[0][2][1] = 负无穷:表示发生了 2 次交易,并且持股,这是不可能的。注意:虽然没有意义,但是不能设置成 0,这是因为交易还没有发生,必须规定当天 k 状态为 1(持股),需要参考以往的状态转移,一种很有可能的情况是没有交易是最好的情况。
- 注意:只有在之前的状态有被赋值的时候,才可能有当前状态。
具体代码实现如下:
public class Solution{
public int maxProfit(int[] prices){
int len = prices.length;
if(len < 2 ) return 0;
/**
进行初始化dp[i][j][k], 初始化第一天时资金情况
i 代表时间范围
j 代表购买股票次数
k 代表此时是否持有股票 0:没持有 1:持有
**/
int[][][] dp = new int[len][3][2];
// 第 3 维规定了必须持股,因此是 -prices[0]
dp[0][1][1] = -prices[0];
// 还没发生的交易,持股的时候应该初始化为负无穷
dp[0][2][1] = Integer.MIN_VALUE;
for(int i = 1; i < len; i++){
//顺序转移,先持有再卖出
dp[i][1][1] = Math.max(dp[i-1][1][1], -prices[i]);
dp[i][1][0] = Math.max(dp[i-1][1][0], dp[i-1][1][1]+prices[i]);
dp[i][2][1] = Math.max(dp[i-1][2][1], dp[i-1][1][0]-prices[i]);
dp[i][2][0] = Math.max(dp[i-1][2][0], dp[i-1][2][1]+prices[i]);
}
return Math.max(dp[len-1][1][0],dp[len-1][2][0]);
}
}
复杂度分析:
时间复杂度:O(N),这里 NN 表示股价数组的长度;
空间复杂度:O(N),虽然是三维数组,但是第二维、第三维是常数,与问题规模无关。
以下是空间优化的代码。
空间优化代码:
1、三维数组降为二维数组,
2、根据本题,其实就转化为4个变量之间的逻辑 ,即第一次买入,第一次卖出,第二次买入,第二次卖出之间的转换。因此完全可以降为不用辅助空间额外数组,直接用O(1)模式。
具体代码实现如下:
public class Solution{
public int maxProfit(int[] prices){
int len = prices.length;
if(len < 2 ) return 0;
//进行初始化
int hold1 = -prices[0];
//不符合实际逻辑,第一天无法卖出,赋予0,不影响最优解
int noHold1 = 0;
//不符合逻辑,第一天无法第二次买入,因此复值赋予最小值
int hold2 = Integer.MIN_VALUE;
//不符合实际逻辑,赋予0,不影响最优解
int noHold2 = 0;
for(int i =1; i< prices.length; i++){
hold1 = Math.max(hold1, -prices[i]);
noHold1 = Math.max(noHold1 , hold1 +prices[i]);
hold2 = Math.max(hold2, noHold1-prices[i]);
noHold2 = Math.max(noHold2, hold2 + prices[i]);
}
return Math.max(noHold1, noHold2);
}
}
2-1-4、股票买卖最佳时机 (四)
(1)问题描述

(2)股票买卖最佳时机(四)问题分析思路
1、乍看题目,允许多次买卖,感觉跟最佳时机(二)没啥区别,但是仔细分析,是按照制定的K笔交易进行买卖,这样就必须要结合最佳时机(三)了。此时需要注意的有
- 先买入才能卖出
- 不能同时参加多笔交易,再次买入时,需要先卖出
k >= 0才能进行交易,否则没有交易次数
2、沿用之前的思路,多维数组解决方法
dp[i][k][0] //第i天 还可以交易k次 手中没有股票
dp[i][k][1] //第i天 还可以交易k次 手中有股票
// 今天没有持有股票,分为两种情况
// 1. dp[i - 1][k][0],昨天没有持有,今天不操作。
// 2. dp[i - 1][k][1] + prices[i] 昨天持有,今天卖出,今天手中就没有股票了。
dp[i][k][0] = Math.max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i])
// 今天持有股票,分为两种情况:
// 1.dp[i - 1][k][1] 昨天持有,今天不操作
// 2.dp[i - 1][k - 1][0] - prices[i] 昨天没有持有,今天买入。
dp[i][k][1] = Math.max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i])//最大利润就是这俩种情况的最大值
具体代码实现:
class Solution {
public int maxProfit(int k, int[] prices) {
int len = prices.length;
if(len < 2) return 0;
int[][] profit =new int[k+1][2];
//进行初始化,profit[][0] 不持有股票,profit[][1] 持有股票
//第一次进行赋予初始值赋值
for(int j = 0; j <= k; j++) {
//不持有股票初始赋0,持有则赋予最小值,不影响后面动态赋值
profit[j][0] = 0;
profit[j][1] = -prices[0];
}
for(int i = 1 ; i < len ; i++){
for(int j = 1; j <= k ; j++) {
//为K次买卖计算收益, 进行反转,先持有,后卖出
profit[j][1] = Math.max(profit[j][1], profit[j-1][0]-prices[i]);
profit[j][0] = Math.max(profit[j][0], profit[j][1]+prices[i]);
}
}
return profit[k][0];
}
}
2-1-5、股票其他相关问题 (包含冷冻期,包含手续费)
2-1-5-1、股票买卖【冷冻期】解决思路与代码实现
309. 最佳买卖股票时机含冷冻期【middle】-----66.89%
此题目不做详细解答,就是买卖股票(二)的引申,因为是允许多次,依赖前一天的结果。但是增加【冷冻期模式】,因此初始化比较关键:
// 进行初始化, 0--买出,1--买入,2--冷冻期
int[][] dp = new int[len][3];
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
具体代码如下:
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if(len < 2) return 0;
// 进行初始化, 0--买出,1--买入,2--冷冻期
int[][] dp = new int[len][3];
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
for(int i=1;i < len; i++){
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][2]-prices[i]); //冷静期后再买入
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]+prices[i]);
dp[i][2] = Math.max(dp[i-1][2], dp[i-1][0]);
}
return dp[len-1][0]; //最后返回最后一天的买出最大值
}
}
2-1-5-2、股票买卖【手续费计算】解决思路与代码实现
714. 买卖股票的最佳时机含手续费【middle】-----46.0 %
此题目不做详细解答,就是买卖股票(二)的引申,因为是允许多次,依赖前一天的结果。但是增加【手续费的计算】,因此在买出的时候减去手续费即可。
public class Solution{
public int maxPrice(int[] prices, int fee){
if(prices.length < 2) return 0;
//进行初始化
int hold = -prices[0];
int noHold = 0;
for(int i = 0 ; i< prices.length; i++){
hold = Math.max(hold, noHold-prices[i]);
//卖出的时候,计算记得减去手续费即可
noHold = Math.max(noHold, hold+prices[i]-fee);
}
return noHold;
}
}
到此【股票问题全部解决】
-----------------------------------
2-1-6、同类型引申问题-----打家劫舍解法
LeetCode(题目难易度---出现频率)
198. 打家劫舍【middle】-----74.11%
213. 打家劫舍 II【middle】-----53.29%
337. 打家劫舍 III【middle】-----65.02%
2-1-6-1、打家劫舍问题(一)---【数组+动态规划】
此类题目题目解法类似,在此不做详细引申说明了,就说明下引申边界条件。
边界条件为:

具体代码实现如下:
class Solution {
public int rob(int[] nums) {
int len = nums.length;
if(len == 0) return 0;
if(len == 1) return nums[0];
//进行初始化
int[] dp = new int[len];
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for(int i = 2; i < len; i++){
dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[len-1];
}
}
同理引入dp[nums.length]的辅助空间数组,目前看最高总金额只和该房屋的前两间房屋的最高总金额相关,因此可以使用滚动数组,在每个时刻只需要存储前两间房屋的最高总金额。
既dp[0] 可以用变量first代替, dp[1] 可以用second代替,进行滚动起来,从而空间复杂度进行优化,对应的代码实现如下:
class Solution {
public int rob(int[] nums) {
int len = nums.length;
if(len == 0) return 0;
if(len == 1) return nums[0];
//进行初始化
int first = nums[0];
int second = Math.max(nums[0],nums[1]);
for(int i = 2; i < len; i++){
int temp = second;
second = Math.max(second, first+nums[i]);
first = temp;
}
return second;
}
}
复杂度分析
时间复杂度:O(n),其中 nn 是数组长度。只需要对数组遍历一次。
空间复杂度:O(1)。使用滚动数组,可以只存储前两间房屋的最高总金额,而不需要存储整个数组的结果,因此空间复杂度是 O(1)。
2-1-6-2、打家劫舍问题(二)---【环+动态规划】
在上题基础上,引入了闭环的逻辑思维,第一家和最后一家不能同时选择,因此存在两种选择模式。dp---选择第一家,不选择最后一家的,因此这个时候len-1的。 dp2---不选择第一家,最后一家可以选择,因此dp2是len长度空间的。
进行两次选择遍历,然后获取最大值即可。
class Solution {
public int rob (int[] nums) {
int len = nums.length;
if(nums==null || len==0) return 0;
if(len==1) return nums[0];
//进行初始化,并进行初始化赋值
int[] dp = new int[len];
dp[0]= nums[0];
dp[1]= Math.max(nums[0],nums[1]);
//情况一,选择了第一家,不选择最后一家的情况
for(int i =2; i <len-1 ;i++){
dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]);
}
//情况二,不选择第一家
int[] dp2 = new int[len];
dp2[0]=0;
dp2[1] =nums[1];
for(int i =2; i < len; i++){
dp2[i] = Math.max(dp2[i-1],dp2[i-2]+nums[i]);
}
return Math.max(dp[len-2],dp2[len-1]);
}
}
2-1-6-3、打家劫舍问题(三)-----【二叉树dfs+动态规划】
参考LeetCode两篇解题思路,然后结合自身情况进行梳理:
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/solution/dong-tai-gui-hua-by-liweiwei1419-5/
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/solution/yi-chong-jie-fa-tuan-mie-mai-mai-gu-piao-h5hd/
https://leetcode.cn/problems/house-robber/solution/da-jia-jie-she-by-leetcode-solution/
本文深入剖析了面试中常见的股票买卖系列与打家劫舍问题,涵盖动态规划和贪心算法。通过对多种场景的分析,如股票买卖的最佳时机(一到四)及包含冷冻期、手续费的情况,以及打家劫舍问题的不同版本,阐述了解题思路和代码实现。动态规划和贪心算法在解决这类问题中的应用得到了详细解释,旨在提升读者对这两种算法的理解和应用能力。

被折叠的 条评论
为什么被折叠?



