时间复杂度和空间复杂度是对立的关系,也就是只能保证其一,以空间换时间或者以时间换空间。
比如数组和链表就是很好的例子,数组在使用前需要开辟足够大的空间,防止数据量大的情况,空间效率低,但数组开辟的是内存中一块连续的空间,在取数据时,只要按顺序依次往后取即可,时间效率高。
而,链表不是在创建的时候一次性开辟内存的,而是每添加一个节点,开辟一次内存,没有空闲的内存,空间效率高,但由于链表开辟的内存不是连续的,它是依靠指针去寻下一个节点的,所以取数据时,寻址的时间效率低。
在优化代码的时候,我们要看需要改进哪个方面,是内存有限制,还是时间有限制,以一换一。
面试题3:找出数组中重复的数。
描述:给定一个长度为n的数组,数组中的数全部都在0~n-1。输出重复的数字。
解法1:(扫描到每个数时,以O(1)的时间判断它是否重复)
public class Main
{
public static void main(String args[])
{
int[] a = {2,3,1,0,2,5,3};
int[] read = new int[a.length]; //开辟了一个同等大小的数组
for(int i=0;i<a.length;i++)
{
int index = a[i];
if(read[index]==0) read[index]++;
else if(read[index]==1) System.out.print(index+" ");
}
}
}
上面的代码开辟了同等大小的数组read[],空间效率为O(n)
但没扫描到一个数,如果read[]中以该数为索引的位置==1,说明该数是重复的,输出,以时间效率O(1)即可对扫描到的数进行判断。
如果本题限制不让开辟新内存,只能在原数组上操作,也就是空间效率为O(1)的解法
解法2:
n个不大于n的数,排序后若没有重复,那数组第x为一定是x。
那么可以这样操作,从索引0开始扫描数组,若索引为x的位置的元素不是x,而是y,那么就把y换到索引y的位置(放到它本来改在的位置),但若索引y上本来就是y(在换之前判断),那么本次扫描到的y一定是重复的多余的,就把它输出。
public class Main
{
public static void main(String args[])
{
int[] a = {2,3,1,0,2,5,3};
int x = 0;
while(x<a.length)
{
int index = a[x];
if(index!=x) //本应该x==a[x]
{
if(a[index]==index)
{
System.out.print(a[x]+" ");
x++;
}
else
{
int t = a[x];
a[x] = a[index];
a[index] = t;
}
}
else x++; //x==a[x],跳过
}
}
}
对于一般改进时间效率的,都是选择开辟内存,建立哈希表等,把搜索过的状态,过去的情况记录下来,下次再搜到相同情况,就可以直接从表中读取,以节省时间。对于改进空间效率就需要思考思考了,尽量少开辟空间,尽量在原始数据内存空间上操作。