概要
写到力扣的739题,是栈的知识点,但是在这里使用双端队列比栈的速度要更快,使用数组模拟栈会更快,下面是我的一些总结。
解题:
解题思路
生成一个栈,遍历温度,如果遍历到的温度比栈顶元素大,说明找到了比栈顶
元素还要高的温度,计算两者之间的天速,将结果存储到对应位置,重复这个操作
直到栈为空或者栈顶的温度不小于当前温度。(模拟数组和双端队列和上述解题相同)
代码实现
1. 栈
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] res = new int[n]; // 初始化结果数组,默认值为0
Stack<Integer> stack = new Stack<>(); // 存储温度的索引
for (int i = 0; i < n; i++) {
// 如果当前温度大于栈顶索引对应的温度,找到更高温度
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int idx = stack.pop(); // 弹出栈顶元素
res[idx] = i - idx; // 计算两天之间的差距
}
// 当前温度的索引入栈
stack.push(i);
}
return res; // 返回结果
}
}
代码解释:
- 栈的作用:栈中存储的是温度的索引。栈中的温度是单调递减的,保证了我们每次遇到更高温度时,栈顶元素是我们要找的“上一天”。
- 处理逻辑:
- 当当前温度
temperatures[i]
大于栈顶索引对应的温度时,说明找到了栈顶那一天的下一个更高温度。 - 计算天数差
i - idx
,并更新到结果数组中。
- 当当前温度
- 结果数组初始化:
res
默认值为0
,如果没有找到更高温度,这个0
就是答案。
时间复杂度:
- 由于每个索引最多进栈和出栈一次,因此该算法的时间复杂度为 O(n)
2.双端队列
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] res = new int[n];
Deque<Integer> q = new LinkedList<>();
for (int i = 0; i < n; i++) {
while (!q.isEmpty() && temperatures[i] > temperatures[q.peekLast()]) {
int index = q.pollLast();
res[index] = i - index;
}
q.offer(i);
}
return res;
}
}
代码解析:
- 双端队列的使用:
- 我们使用
Deque
接口,并通过LinkedList
来实现。双端队列允许我们在队列两端进行操作,因此可以方便地处理温度和索引。 offerLast(i)
用于将当前索引加入到队列尾部。pollLast()
用于从队列尾部移除满足条件的元素。
- 我们使用
- 逻辑和栈的区别:
- 栈是只在一端(栈顶)进行操作,而双端队列可以在两端都进行操作。在这个问题中,双端队列的行为和栈有类似的地方,但它提供了更灵活的操作选项。
- 处理逻辑:
- 如果当前温度比队列尾部对应的温度要高,说明当前温度是这些天的下一个更高温度,因此更新这些天的结果。
- 遍历完所有天数后,队列中剩下的元素没有找到比它们更高的温度,因此结果数组中的这些位置默认为
0
。
优化分析:
- 时间复杂度:同样的,双端队列的每个索引最多进队列和出队列一次,因此时间复杂度依然是 O(n)。
- 空间复杂度:由于我们使用了一个长度为 n 的结果数组和一个双端队列,空间复杂度为 O(n)。
虽然这个方法在实际性能上与使用栈的解法相似,但双端队列可以在某些特定的场景下提
供更多灵活性。
3.数组模拟栈
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] res = new int[n]; // 存储结果的数组,默认为0
int[] stack = new int[n]; // 用于模拟栈,存储温度的索引
int top = -1; // 指向栈顶元素的指针
for (int i = 0; i < n; i++) {
// 当当前温度大于栈顶元素对应的温度时,找到更高温度
while (top >= 0 && temperatures[i] > temperatures[stack[top]]) {
int idx = stack[top--]; // 弹出栈顶元素
res[idx] = i - idx; // 计算两天之间的差距
}
// 将当前索引压入栈
stack[++top] = i;
}
return res;
}
}
主要的优化点:
-
使用数组模拟栈:原先的
Stack
对象被一个整型数组stack
替代,top
变量用于跟踪栈顶元素。这样可以避免Stack
类的对象创建开销以及泛型的装箱、拆箱操作,提高运行效率。 -
减少对象操作:不再使用
Stack
对象的pop
和push
方法,而是通过简单的数组操作(如stack[++top] = i
)来模拟入栈和出栈的操作,减少了方法调用的开销。 -
逻辑保持不变:所有逻辑保持不变,依然是单调栈的思路来处理每一天的温度,只是在实现上做了简化和优化。
时间复杂度分析:
- 时间复杂度:每个索引最多进栈一次,出栈一次,因此时间复杂度依然是 O(n)。
- 空间复杂度:额外的栈空间为 O(n),因此空间复杂度依然是 O(n)。
虽然这个优化版与原版代码在理论上的时间复杂度和空间复杂度相同,但在实际运行
中,由于减少了对 Stack
对象的依赖以及不必要的内存开销,实际性能会稍微更好一些。
小结
-
使用栈的方式:
- 特点:栈用于存储索引,确保每个温度在遇到更高温度时出栈,并更新结果。
- 时间复杂度:O(n),因为每个元素最多进栈和出栈一次。
- 空间复杂度:O(n),用于存储栈和结果数组。
- 适用场景:处理“下一天”的问题非常高效,逻辑清晰,适合解决这种单调递增或递减问题。
-
使用数组模拟栈:
- 特点:使用数组模拟栈,避免了Java
Stack
对象的额外开销,减少不必要的对象操作。 - 时间复杂度:O(n),与栈方式相同。
- 空间复杂度:O(n),用数组代替栈,减少了装箱和拆箱操作的开销。
- 适用场景:当性能要求较高时,使用数组模拟栈可以减少栈对象带来的开销,提升实际运行效率。
- 特点:使用数组模拟栈,避免了Java
-
使用双端队列(Deque):
- 特点:双端队列允许从两端进行插入和删除操作,提供了灵活的操作方式。
- 时间复杂度:O(n),每个元素最多进队列和出队列一次。
- 空间复杂度:O(n),队列和结果数组都需要存储。
- 适用场景:适合处理需要灵活操作的场景,虽然性能与栈方式相似,但在特殊场合提供了更多的操作可能性。
栈和数组模拟栈 方式更适合处理这种单调递增或递减问题,逻辑简洁、性能高效。
双端队列 方式虽然功能更灵活,但在这个问题中和栈的表现相似,适用于需要灵活性更高的场景。