技术是开源的,知识是共享的。
数据结构与算法,是程序员的内功修炼。甚至有开发者还提出了程序=数据结构+算法
。
少侠我之前参加校招的时候,也是凭借在手撕代码环节出色的算法解题思路获得面试官的称赞并击败众多竞争者最后拿到字节跳动等大厂的offer。
为了保持算法心经的日臻纯熟,我暗下决心,决定开始写互联网数据结构与算法相关的文章,希望能帮助各位读者在以后的手撕代码中游刃有余,对面试官进行360°的反击,让一同面试的同僚瞠目结舌,疯狂收割互联网大厂offer!
文章中虽然应用了夸张的修辞手法,但我们应该始终保持一颗谦逊的心。希望和大家怀着虚怀若谷的心态,共同学习进步。
Given a list of daily temperatures T, return a list such that, for each day in the input, tells you how many days you would have to wait until a warmer temperature. If there is no future day for which this is possible, put 0 instead.
For example, given the list of temperatures T = [73, 74, 75, 71, 69, 72, 76, 73], your output should be [1, 1, 4, 2, 1, 1, 0, 0].
Note: The length of temperatures will be in the range [1, 30000]. Each temperature will be an integer in the range [30, 100].
英文看得不流畅?不怕,贴心的少侠已经帮你们翻译好了。题目大意就是:
给定一个数组,记录了若干天的气温,需要你返回一个数组:该数组需满足,对于原数组中每天的气温,需要计算需要至少经过多少天才能看到气温升高,如果没有则该位置0。
注意有个限制条件是:原数组中每天的气温取值范围是30≤t≤100。
分析题干,我们不难发现,实际上该题核心在于:对于每个值,只需要找到它之后的第一个比它值要大的数,将两个数索引做差即可。
这道题目思路清晰直观,双层for循环依次遍历就行了,看见没,简单粗暴,一气呵成。好了,本篇到这里就要跟大家说再见了。
依次遍历,这个想法很直观,但是时间复杂度也很可观!是O(n2),我们在面试、开发中一定要尽可能的规避平方次的时间复杂度,很可怕,甚至容易导致服务器宕机。
那么有没有什么好的思路呢?当然有啦,且听我娓娓道来。
我们观察到原数组中的数值范围有限([30,100]),于是想到可以维护一个指针数组pointer,将原数组每个值对应的索引保存到pointer中,pointer数组初始化值为一个大于原数组长度的整数。如下表所示:
索引 | 初始值 |
---|---|
0 | Integer.MAX_VALUE |
1 | Integer.MAX_VALUE |
… | … |
100 | Integer.MAX_VALUE |
那么初始化之后呢,数据怎么放进去呢?简单,就用最基本的for循环遍历一次原数组。本节采用的是倒序遍历。假设此时遍历到的原数组的索引为i,对应的值为T[i],在每次把索引i到pointer之前,需要判断pointer中索引大于T[i]的最小值warmerIndex (注意pointer数组中的值就是原数组T的索引)。然后将(warmerIndex -i)
即可得到结果。你看看,这样是不是代码优化了很多,实际上,假设原数组长度为N,pointer指针数组取值范围为[0,100],这样的遍历时间复杂度为O(100N),即O(N)。
class Solution {
public int[] dailyTemperatures(int[] T) {
int upperLimit = 100;
int[] ans = new int[T.length];
int[] pointer = new int[upperLimit + 1];
Arrays.fill(pointer, Integer.MAX_VALUE);
for (int i = T.length - 1; i >= 0; i--) {
int warmerIndex = Integer.MAX_VALUE;
for (int t = T[i] + 1; t <= upperLimit; t++) {
if (pointer[t] < warmerIndex) {
warmerIndex = pointer[t];
}
}
if (warmerIndex < Integer.MAX_VALUE) {
ans[i] = warmerIndex - i;
}
pointer[T[i]] = i;
}
return ans;
}
}
利用栈的先进后出特性,栈中保存的是原数组的索引。同样需要对原数组进行一次遍历,本节方案改用了顺序遍历。每次将原数组的索引i放入栈stack中,然后每次遍历的时候判断T[i]的是是否大于T[stack.peek()]
(stack.peek()方法是查看但不弹出栈顶元素)。如果大于的话说明满足条件,此时将栈顶的值弹出作为返回结果数组的索引index,对应的值为此时原数组的索引i-index
。该方案也是通过开辟了额外的存储空间,即以空间换时间的思路。两种方案都是线性的时间复杂度O(N)。
public class Solution {
public int[] dailyTemperatures(int[] T) {
Stack<Integer> stack = new Stack<>();
int[] ans = new int[T.length];
for(int i = 0; i < T.length; i++) {
while(!stack.isEmpty() && T[i] > T[stack.peek()]) {
int index = stack.pop();
ans[index] = i - index;
}
stack.push(i);
}
return ans;
}
}
正如谚语所说,条条道路通罗马,每个题目的解决方案不会是唯一的,所以拓宽思路,尝试多种解方案,对于数据结构和算法的认知以及使用都是大有裨益的。别人会的你也会,别人不会的你还会,岂不快哉?