商品折扣后的最终价格
原题
给你一个数组 prices
,其中 prices[i]
是商店里第 i
件商品的价格。
商店里正在进行促销活动,如果你要买第 i
件商品,那么你可以得到与 prices[j]
相等的折扣,其中 j
是满足 j > i
且 prices[j] <= prices[i]
的 最小下标 ,如果没有满足条件的 j
,你将没有任何折扣。
请你返回一个数组,数组中第 i
个元素是折扣后你购买商品 i
最终需要支付的价格。
示例 1:
输入:prices = [8,4,6,2,3]
输出:[4,2,4,2,3]
解释:
商品 0 的价格为 price[0]=8 ,你将得到 prices[1]=4 的折扣,所以最终价格为 8 - 4 = 4 。
商品 1 的价格为 price[1]=4 ,你将得到 prices[3]=2 的折扣,所以最终价格为 4 - 2 = 2 。
商品 2 的价格为 price[2]=6 ,你将得到 prices[3]=2 的折扣,所以最终价格为 6 - 2 = 4 。
商品 3 和 4 都没有折扣。
示例 2:
输入:prices = [1,2,3,4,5]
输出:[1,2,3,4,5]
解释:在这个例子中,所有商品都没有折扣。
示例 3:
输入:prices = [10,1,1,6]
输出:[9,0,1,6]
提示:
1 <= prices.length <= 500
1 <= prices[i] <= 10^3
class Solution {
public int[] finalPrices(int[] prices) {
}
}
解题思路
- 遍历输入的价格数组,从第一个价格开始。
- 对于每个价格,从它的下一个价格开始遍历,找到第一个比当前价格小或等于当前价格的价格。
- 如果找到了这样的价格,将其从当前价格中减去,并停止查找。
- 返回修改后的价格数组。
代码示例
class Solution {
public int[] finalPrices(int[] prices) {
for (int i = 0; i < prices.length - 1; i++) {
for (int j = i + 1; j < prices.length; j++) {
if (prices[j] <= prices[i]) {
prices[i] = prices[i] - prices[j];
break;
}
}
}
return prices;
}
}
优化
思路
我们需要找到每个商品右侧第一个价格小于等于当前商品的价格,如果直接用两个嵌套循环去遍历数组,时间复杂度会是 O ( n 2 ) O(n^2) O(n2),对于较大的输入规模效率不高。
单调栈(Monotonic Stack)是一种栈的数据结构,根据栈中元素的单调性,可以分为单调递增栈和单调递减栈。对于许多问题,使用单调栈可以将时间复杂度降低到
O
(
n
)
O(n)
O(n),因为每个元素最多只会被 push
和 pop
操作一次。
单调栈非常适合处理那些需要维护元素顺序性质的问题,并且能在较短时间内找到答案。这类问题的特点是涉及到元素之间的比较和相对位置,常常需要在动态更新的过程中保持某种顺序关系,例如寻找左/右侧第一个更大/小的元素。
单调递增栈的入栈、出栈过程
* 定义栈顶元素最大,栈底元素最小的栈为单调递增栈。
- 入栈过程:
- 对于每个元素,如果栈为空,直接入栈;
- 如果栈不为空,比较当前元素与栈顶元素的大小:
- 如果当前元素大于等于栈顶元素,则直接入栈;
- 如果当前元素小于栈顶元素,则持续弹出栈顶元素,直到栈为空或者当前元素大于等于栈顶元素,然后将当前元素入栈。
- 出栈过程:
- 从栈顶弹出元素。
使用单调递增栈寻找右侧第一个更小的元素
以数组 [8, 4, 6, 2, 3]
为例:
步骤 | 操作 | 栈 |
---|---|---|
1 | 8 入栈 | [8] |
2 | 8 出栈,4 入栈 | [4] |
3 | 6 入栈 | [4, 6] |
4 | 6 出栈,4 出栈,2 入栈 | [2] |
5 | 3 入栈 | [2, 3] |
栈最后剩下 [2, 3]
,意味着这两个元素右侧没有更小的值。
优化后代码
class Solution {
public int[] finalPrices(int[] prices) {
int n = prices.length;
int[] result = new int[n];
Deque<Integer> stack = new ArrayDeque<Integer>();
// 构造单调递增栈
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && prices[stack.peek()] >= prices[i]) {
// 将当前索引出栈
int index = stack.pop();
// 更新最终价格数组
result[index] = prices[index] - prices[i];
}
// 将当前索引入栈
stack.push(i);
}
// 栈中剩下的元素右侧没有更小的元素
while (!stack.isEmpty()) {
int index = stack.pop();
result[index] = prices[index];
}
return result;
}
}
逐步分析
以 prices = [8,4,6,2,3]
为例:
索引 i | !stack.isEmpty() | prices[stack.peek()] >= prices[i] | 操作 | stack | result[] |
---|---|---|---|---|---|
0 | false | - | 当前元素索引 0 入栈 | [0] | [0, 0, 0 , 0, 0] |
1 | true | true | 索引 0 出栈,将该索引处最终价格更新为 4 | [] | [4, 0, 0 , 0, 0] |
false | - | 当前元素索引 1 入栈 | [1] | [4, 0, 0 , 0, 0] | |
2 | true | false | 当前元素索引 2 入栈 | [1, 2] | [4, 0, 0 , 0, 0] |
3 | true | true | 索引 2 出栈,将该索引处最终价格更新为 4 | [1] | [4, 0, 4 , 0, 0] |
true | true | 索引 1 出栈,将该索引处最终价格更新为 2 | [] | [4, 2, 4 , 0, 0] | |
false | - | 当前元素索引 3 入栈 | [3] | [4, 2, 4 , 0, 0] | |
4 | true | false | 当前元素索引 4 入栈 | [3, 4] | [4, 2, 4 , 0, 0] |
栈 stack
最后剩下 [3, 4]
,意味着 prices[]
中对应索引处的元素右侧没有更小的值,本身即为最终价格,保留原来的值。