文章目录
- 0 引言
- 1 121. 买卖股票的最佳时机
- 1. 1 暴力法
- 1.2 一次遍历
- 1.3 动态规划
- 2 122. 买卖股票的最佳时机 II
- 2.1 一次遍历
- 2.2 动态规划
- 3 123. 买卖股票的最佳时机 III
- 3.1 动态规划
- 4 188. 买卖股票的最佳时机 IV
- 4.1 动态规划
- 5 309. 买卖股票的最佳时机含冷冻期
- 5.1 动态规划
- 6 714. 买卖股票的最佳时机含手续费
- 6.1 动态规划
- Reference
0 引言
本文主要记录如何解决LeetCode中的《买卖股票的最佳时机》,相关题目总共有6道,主要解法是动态规划,也可以通过这几道题熟悉动态规划算法。
👉 简单—> 121. 买卖股票的最佳时机
👉 中等—> 122. 买卖股票的最佳时机 II
👉 困难—> 123. 买卖股票的最佳时机 III
👉 困难—> 188. 买卖股票的最佳时机 IV
👉 中等—> 309. 买卖股票的最佳时机含冷冻期
👉 中等—> 714. 买卖股票的最佳时机含手续费
1 121. 买卖股票的最佳时机
给定一个数组
prices
,它的第i
个元素prices[i]
表示一支给定股票第i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回0
。
核心问题:虽然是模拟买卖股票,但这题是开了天眼
,知道后续的股票涨跌,所以在已知给定数组中,找出其中差值最大的两个数,但第二个数必须排在第一个数后边。
1. 1 暴力法
很容易想到暴力解法,主循环和子循环来求差值,c++
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = int(prices.size());
int ans = 0;
for (int i = 0; i < n; ++i){
for (int j = i+1; j < n; ++j){
ans = max(ans, prices[j] - prices[i]);
}
}
return ans;
}
};
但这样的时间复杂度: O ( n 2 ) O(n^2) O(n2),超时不符合题目要求。后面的题目就不记录暴力法的解法了。
1.2 一次遍历
仔细想想,暴力解法其实多了一次不必要的循环,其实一次遍历不仅可以计算差值,还能记录数组中的最小值,有了最小值的更新,就可以更新最大差值,即最大利润。c++
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int inf = 1e9;
int minprice = inf, maxprofit = 0;
for (int i = 0; i < int(prices.size()); ++i){
maxprofit = max(maxprofit, prices[i] - minprice);
minprice = min(prices[i], minprice);
}
return maxprofit;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),只需要遍历一次。
- 空间复杂度: O ( 1 ) O(1) O(1),只使用了常数个变量。
1.3 动态规划
动态规划(dynamic programming,DP)
简单解释:
首先写一下1+1+1+1=?
“它等于多少呢?”
易知“等于4
!”
如果在左边添一个1+
,即1+1+1+1+1=?
“会等于多少呢?”
也易知“等于5
!”
“为什么会计算得这么快呢?”
“因为4+1=5
”
所以,没有重新计算1+1+1+1+1
的值,因为记住了前面的和等于4
,再次计算时只需再加1
就可以了。
这种通过记住一些事情来节省时间,就是动态规划的精髓。
具体来说,如果一个问题的子问题会被重复利用,则可以考虑使用动态规划。
动态规划一般分为一维、二维、多维(使用 状态压缩),对应形式为 d p ( i ) dp(i) dp(i)、 d p ( i ) ( j ) dp(i)(j) dp(i)(j)、 d p ( i ) ( j ) dp(i)(j) dp(i)(j)。本题用的是一维 d p ( i ) dp(i) dp(i)
本题就是用动态规划的 d p [ i ] dp[i] dp[i] 表示前 i i i 天的最大利润,因为始终要使利润最大化,则遍历过程中:
d p [ i ] = m a x ( d p [ i − 1 ] , p r i c e s [ i ] − m i n p r i c e ) dp[i]=max(dp[i−1],prices[i]−minprice) dp[i]=max(dp[i−1],prices[i]−minprice)
c++
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = int(prices.size());
if (n == 0) return 0;
int minprice = prices[0];
// 创建了一个名为dp的整数类型向量(vector),其长度为n,并且初始化所有元素的值为0
vector<int> dp (n, 0);
for (int i = 1; i < n; ++i){
minprice = min(minprice, prices[i]);
dp[i] = max(dp[i - 1], prices[i] - minprice);
}
return dp[n - 1];
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),只需要遍历一次。
- 空间复杂度: O ( n ) O(n) O(n),使用了长度为 n n n的数组。
2 122. 买卖股票的最佳时机 II
给你一个整数数组
prices
,其中prices[i]
表示某支股票第i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
通过题目和示例可知:本题相对于第I
题,可以多次交易的,但每次交易前都要先售出才能再买入。所以本题需要计算多次交易的利润和。
2.1 一次遍历
与第I
题的一次遍历有些许差异,遍历过程中,只要第i
天的价格比第i-1
的价格低,就把差值计入利润和中。c++代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int inf = 1e9;
int profit = 0;
for (int i = 1; i < int(prices.size()); ++i){
if (prices[i] > prices[i-1]){
profit += (prices[i] - prices[i-1]);
}
}
return profit;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),只需要遍历一次。
- 空间复杂度: O ( 1 ) O(1) O(1),只使用了常数个变量。
2.2 动态规划
与第I
题的动态规划有些许差异,这次是多次交易,动态规划的利润和
d
p
[
i
]
dp[i]
dp[i] 有增加有减少,即买入时利润和减少,卖出时利润和增加。
所以分两种情况,如何区分呢?
d
p
[
i
]
dp[i]
dp[i] 增加一维,用二维来表示
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j],但这里的第二维其实只需用来表示没有股票[0]
或持有股票[1]
,也即没有股票的利润和
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0]和持有股票的利润和
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1]。
- d p [ i ] [ 0 ] dp[i][0] dp[i][0]时,如何计算 d p [ i ] [ 0 ] dp[i][0] dp[i][0]呢?肯定是由前一天计算得出,前一天同样也有两种情况, d p [ i − 1 ] [ 0 ] dp[i-1][0] dp[i−1][0]即前一天已经没有股票,或者 d p [ i − 1 ] [ 1 ] dp[i-1][1] dp[i−1][1]前一天手里有股票,但为了计算得到 d p [ i ] [ 0 ] dp[i][0] dp[i][0],需要今天卖掉股票(价格是今日的 p r i c e [ i ] price[i] price[i]),即 d p [ i − 1 ] [ 1 ] + p r i c e [ i ] dp[i-1][1]+price[i] dp[i−1][1]+price[i],两种情况取最大值。
- 同理, d p [ i ] [ 1 ] dp[i][1] dp[i][1]时,前一天同样有两种情况, d p [ i − 1 ] [ 1 ] dp[i-1][1] dp[i−1][1]即前一天已经有股票,或者 d p [ i − 1 ] [ 0 ] dp[i-1][0] dp[i−1][0]前一天手里没有股票,但为了计算得到 d p [ i ] [ 1 ] dp[i][1] dp[i][1],需要今天买入股票(价格是今日的 p r i c e [ i ] price[i] price[i]),即 d p [ i − 1 ] [ 0 ] − p r i c e [ i ] dp[i-1][0]-price[i] dp[i−1][0]−price[i],两种情况取最大值。
对于初始状态, d p [ 0 ] [ 0 ] = 0 , d p [ 0 ] [ 1 ] = − p r i c e s [ 0 ] dp[0][0]=0,dp[0][1]=−prices[0] dp[0][0]=0,dp[0][1]=−prices[0]。
c++
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = int(prices.size());
int dp[n][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < n; ++i){
// dp[i][0]第 i 天手里没有股票的利润和
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
// dp[i][1]第 i 天手里有股票的利润和
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[n - 1][0];
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),只需要遍历一次。
- 空间复杂度: O ( n ) O(n) O(n),使用了长度为 n n n的数组。
3 123. 买卖股票的最佳时机 III
给定一个数组,它的第
i
个元素是一支给定的股票在第i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
3.1 动态规划
本题有了最多两笔交易的限制,不能像之前那样多次买卖交易,而两次交易的话,其实最多就是4个状态:
- 第一次买入后的利润和;
- 第一次卖出后的利润和;
- 第二次买入后的利润和;
- 第二次卖出后的利润和;
首先设第一次买入后的利润和为
b
u
y
1
buy1
buy1,而第
i
i
i天对应的有两种情况,一种是没有交易,仍未
b
u
y
1
buy1
buy1;一种是之前还没操作过,第一次买入,利润和即
−
p
r
i
c
e
s
[
i
]
-prices[i]
−prices[i];
b
u
y
1
=
m
a
x
(
b
u
y
1
,
−
p
r
i
c
e
s
[
i
]
)
buy1 = max(buy1, -prices[i])
buy1=max(buy1,−prices[i])
其次设第一次卖出后的利润和为
s
e
l
l
1
sell1
sell1,而第
i
i
i天对应的有两种情况,一种是没有交易,仍未
s
e
l
l
1
sell1
sell1;一种是第一次卖出,利润和即
b
u
y
1
+
p
r
i
c
e
s
[
i
]
buy1+prices[i]
buy1+prices[i];
s
e
l
l
1
=
m
a
x
(
s
e
l
l
1
,
b
u
y
1
+
p
r
i
c
e
s
[
i
]
)
sell1 = max(sell1, buy1 + prices[i])
sell1=max(sell1,buy1+prices[i])
然后设第二次买入后的利润和为
b
u
y
2
buy2
buy2,而第
i
i
i天对应的有两种情况,一种是没有交易,仍未
b
u
y
2
buy2
buy2;一种是第一次卖出后还没有第二次买入,利润和即
s
e
l
l
1
−
p
r
i
c
e
s
[
i
]
sell1-prices[i]
sell1−prices[i];
b
u
y
2
=
m
a
x
(
b
u
y
2
,
s
e
l
l
1
−
p
r
i
c
e
s
[
i
]
)
buy2 = max(buy2, sell1 - prices[i])
buy2=max(buy2,sell1−prices[i])
最后设第二次卖出后的利润和为
s
e
l
l
2
sell2
sell2,而第
i
i
i天对应的有两种情况,一种是没有交易,仍未
s
e
l
l
2
sell2
sell2;一种是第二次卖出,利润和即
b
u
y
2
+
p
r
i
c
e
s
[
i
]
buy2+prices[i]
buy2+prices[i]。
s
e
l
l
2
=
m
a
x
(
s
e
l
l
2
,
b
u
y
2
+
p
r
i
c
e
s
[
i
]
)
sell2 = max(sell2, buy2 + prices[i])
sell2=max(sell2,buy2+prices[i])
对于初始状态, b u y 1 = − p r i c e s [ 0 ] , s e l l 1 = 0 , b u y 2 = − p r i c e s [ 0 ] , s e l l 2 = 0 buy1 = -prices[0], sell1 = 0, buy2 = -prices[0], sell2 = 0 buy1=−prices[0],sell1=0,buy2=−prices[0],sell2=0。
c++
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int buy1 = -prices[0], sell1 = 0, buy2 = -prices[0], sell2 = 0;
for (int i = 1; i < n; ++i){
buy1 = max(buy1, -prices[i]);
sell1 = max(sell1, buy1 + prices[i]);
buy2 = max(buy2, sell1 - prices[i]);
sell2 = max(sell2, buy2 + prices[i]);
}
return sell2;
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 p r i c e s prices prices 的长度。
- 空间复杂度: O ( 1 ) O(1) O(1)。
4 188. 买卖股票的最佳时机 IV
给你一个整数数组
prices
和一个整数k
,其中prices[i]
是某支给定的股票在第i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成k
笔交易。也就是说,你最多可以买k
次,卖k
次。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
4.1 动态规划
相比上一题的3.1
解法,本题由2
次的限制变成了k
次,但整体的思想是可以延用的,对应的
b
u
y
2
buy2
buy2、
s
e
l
l
2
sell2
sell2也变成了
b
u
y
[
j
]
buy[j]
buy[j]、
s
e
l
l
[
j
]
sell[j]
sell[j],即有2种状态:
- 第
j
次买入后的利润和,设为 b u y [ j ] buy[j] buy[j]; - 第
j
次卖出后的利润和,设为 s e l l [ j ] sell[j] sell[j];
首先第
j
j
j次买入交易对应的有两种情况,一种是没有交易,利润和仍未
b
u
y
[
j
]
buy[j]
buy[j];一种是要进行买入,利润和是在前一次
[
j
−
1
]
[j-1]
[j−1]卖出后的利润和
s
e
l
l
[
j
−
1
]
sell[j-1]
sell[j−1]的基础上,减去本次交易的价格
p
r
i
c
e
s
[
i
]
prices[i]
prices[i],即
s
e
l
l
[
j
−
1
]
−
p
r
i
c
e
s
[
i
]
sell[j-1]-prices[i]
sell[j−1]−prices[i];
b
u
y
[
j
]
=
m
a
x
(
b
u
y
[
j
]
,
s
e
l
l
[
j
−
1
]
−
p
r
i
c
e
s
[
i
]
)
buy[j] = max(buy[j], sell[j - 1] - prices[i])
buy[j]=max(buy[j],sell[j−1]−prices[i])
其次第
j
j
j次卖出交易对应的也有两种情况,一种是没有交易,利润和仍未
s
e
l
l
[
j
]
sell[j]
sell[j];一种是要进行卖出,利润和是在第
[
j
]
[j]
[j]次买入后的利润和
b
u
y
[
j
]
buy[j]
buy[j]的基础上,加上本次交易的价格
p
r
i
c
e
s
[
i
]
prices[i]
prices[i],即
b
u
y
[
j
]
+
p
r
i
c
e
s
[
i
]
buy[j] + prices[i]
buy[j]+prices[i];
s
e
l
l
[
j
]
=
m
a
x
(
s
e
l
l
[
j
]
,
b
u
y
[
j
]
+
p
r
i
c
e
s
[
i
]
)
sell[j] = max(sell[j], buy[j] + prices[i])
sell[j]=max(sell[j],buy[j]+prices[i])
对于初始状态, v e c t o r b u y ( 3 , − p r i c e s [ 0 ] ) , s e l l ( 3 , 0 ) vector buy(3, -prices[0]), sell(3, 0) vectorbuy(3,−prices[0]),sell(3,0)。
c++
代码如下:
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
vector buy(3, -prices[0]), sell(3, 0);
for (int i = 1; i < n; ++i) {
for (int j = 1; j <= k; j++) {
buy[j] = max(buy[j], sell[j - 1] - prices[i]);
sell[j] = max(sell[j], buy[j] + prices[i]);
}
}
return sell[k];
}
};
复杂度分析:
- 时间复杂度: O ( n m i n ( n , k ) ) O(nmin(n,k)) O(nmin(n,k)),其中 n n n 是数组 p r i c e s prices prices 的大小,即我们使用二重循环进行动态规划需要的时间;
- 空间复杂度: O ( m i n ( n , k ) ) O(min(n,k)) O(min(n,k)),一维数组。
5 309. 买卖股票的最佳时机含冷冻期
给定一个整数数组
prices
,其中第prices[i]
表示第i
天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为1
天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
5.1 动态规划
本题可以在第2
题的基础上思考,多了一个冷冻期的限制。
所以也是分两种情况,用二维来表示 d p [ i ] [ j ] dp[i][j] dp[i][j],设持有股票的收益 d p [ i ] [ 0 ] dp[i][0] dp[i][0]和不持有股票的利润和 d p [ i ] [ 1 ] dp[i][1] dp[i][1]**。
-
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0]时,有两种情况,一种是持有股票并不交易,收益就是和前一天一样,即
d
p
[
i
−
1
]
[
0
]
dp[i-1][0]
dp[i−1][0];或今日进行买入交易,由于有
1
天冷冻期的限制,所以是在前2
天的不持有股票的利润和 d p [ i − 2 ] [ 1 ] dp[i-2][1] dp[i−2][1]的基础上,减去今日需要买入股票价(价格是今日的 p r i c e [ i ] price[i] price[i]),即 d p [ i − 2 ] [ 1 ] − p r i c e [ i ] dp[i-2][1]-price[i] dp[i−2][1]−price[i],两种情况取最大值。
d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 2 ] [ 1 ] − p r i c e s [ i ] ) dp[i][0] = max(dp[i-1][0], dp[i-2][1]-prices[i]) dp[i][0]=max(dp[i−1][0],dp[i−2][1]−prices[i]) - 同理,
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1]时,也有两种情况,一种是持续不持有股票,收益就是和前一天一样,即
d
p
[
i
−
1
]
[
1
]
dp[i-1][1]
dp[i−1][1];或今日进行卖出交易,由于卖出没有限制,所以仍旧是在前
1
天的持有股票的利润和 d p [ i − 1 ] [ 0 ] dp[i-1][0] dp[i−1][0]的基础上,增加今日卖出的股票价(价格是今日的 p r i c e [ i ] price[i] price[i]),即 d p [ i − 1 ] [ 0 ] + p r i c e [ i ] dp[i-1][0]+price[i] dp[i−1][0]+price[i],两种情况取最大值。
d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] + p r i c e s [ i ] ) dp[i][1] = max(dp[i-1][1], dp[i-1][0]+prices[i]) dp[i][1]=max(dp[i−1][1],dp[i−1][0]+prices[i])
对于初始状态, d p [ 0 ] [ 0 ] = − p r i c e s [ 0 ] , d p [ 1 ] [ 0 ] = m a x ( − p r i c e s [ 0 ] , − p r i c e s [ 1 ] ) , d p [ 1 ] [ 1 ] = m a x ( 0 , p r i c e s [ 1 ] − p r i c e s [ 0 ] ) dp[0][0]=−prices[0],dp[1][0]=max(-prices[0], -prices[1]),dp[1][1] = max(0, prices[1]-prices[0]) dp[0][0]=−prices[0],dp[1][0]=max(−prices[0],−prices[1]),dp[1][1]=max(0,prices[1]−prices[0])。
c++
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if(n == 1){
return 0;
}
vector<vector<int>> dp(n, vector<int>(2));
dp[0][0] = -prices[0];
dp[1][0] = max(-prices[0], -prices[1]);
dp[1][1] = max(0, prices[1]-prices[0]);
for(int i = 2; i < n; ++i){
dp[i][0] = max(dp[i-1][0], dp[i-2][1]-prices[i]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0]+prices[i]);
}
return dp[n-1][1];
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),只需要遍历一次。
- 空间复杂度: O ( n ) O(n) O(n),使用了长度为 n n n的数组。
6 714. 买卖股票的最佳时机含手续费
给定一个整数数组
prices
,其中prices[i]
表示第i
天的股票价格 ;整数fee
代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
6.1 动态规划
本题可以在第2
题的基础上思考,比第5
题简单,仅仅多了一个手续费的问题,即在
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0]时,卖出股票时,减去对应的手续费fee
,其他保持不变。
-
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0]时,状态转移方程:
d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] − f e e ) dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee) dp[i][0]=max(dp[i−1][0],dp[i−1][1]+prices[i]−fee) -
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1]时,状态转移方程:
d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] − p r i c e s [ i ] ) dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]) dp[i][1]=max(dp[i−1][1],dp[i−1][0]−prices[i])
对于初始状态, d p [ 0 ] [ 0 ] = 0 , d p [ 0 ] [ 1 ] = − p r i c e s [ 0 ] dp[0][0]=0,dp[0][1]=−prices[0] dp[0][0]=0,dp[0][1]=−prices[0]。
c++
代码如下:
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = int(prices.size());
int dp[n][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < n; ++i){
// dp[i][0]第 i 天手里没有股票的利润和
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
// dp[i][1]第 i 天手里有股票的利润和
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[n - 1][0];
}
};
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),只需要遍历一次。
- 空间复杂度: O ( n ) O(n) O(n),使用了长度为 n n n的数组。
Reference
- 👉 简单—> 121. 买卖股票的最佳时机
- 👉 中等—> 122. 买卖股票的最佳时机 II
- 👉 困难—> 123. 买卖股票的最佳时机 III
- 👉 困难—> 188. 买卖股票的最佳时机 IV
- 👉 中等—> 309. 买卖股票的最佳时机含冷冻期
- 👉 中等—> 714. 买卖股票的最佳时机含手续费
⭐️👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍🌔