LeetCode 56, Merge Intervals 从 TLE 到 AC
这个题再次告诉我,光照着API或者java docs 写代码是靠不住的,还是要去看底层的源代码。前人封装好的黑盒子给我们用,并不是照着说明书做就可以了,有时候还需要知道黑盒子里大概装的是什么。
https://leetcode.com/problems/merge-intervals/
Given a collection of intervals, merge all overlapping intervals.
For example,
Given [1,3],[2,6],[8,10],[15,18],
return [1,6],[8,10],[15,18].
/**
* Definition for an interval.
* public class Interval {
* int start;
* int end;
* Interval() { start = 0; end = 0; }
* Interval(int s, int e) { start = s; end = e; }
* }
*/
public class Solution {
public List<Interval> merge(List<Interval> intervals) {
}
}
这道题本身的思路很明确。先按照Interval.start 排序,然后合并重叠的部分。排序的时间复杂度是O(n*log(n)), 合并是O(N)。 List类下面有一个 sort 方法,既然是做leetCode, 我觉的这题的难点也就是排序了,于是就像自己再写一遍quickSort算法。另外,出于节省空间的目的,我的想法是,在传入的List〈Intervals〉上进行sort 和 merge 而返回新的List对象。
于是,我写了sort 方法,并在 sort 之后对 传入的List参数进行merge 操作,代码如下:
public void quickSort(List<Interval> intervals,int h, int t){
//print(intervals);
int i=h+1;
int ref=intervals.get(h).start;
for(int j=h+1;j<t;j++){
//System.out.println(j);
if(intervals.get(j).start < ref){
swap(intervals,j,i);
i++;
}
}
swap(intervals,h,i-1);
if(i>h+1) quickSort(intervals,h,i);
if(t>i+1) quickSort(intervals,i,t);
}
然后,我对sort 后的代码,进行merge 操作:
quickSort(intervals,0,size);
//merge
Interval cur=intervals.get(0);
int start=cur.start, end=cur.end;
for(int i=1;i<size;i++){
Interval it=intervals.get(i);
//System.out.println("size:"+size+" i:"+i);
//print(intervals);
if(end>=it.start){
intervals.remove(i-1);
intervals.remove(i-1);
end=Integer.max(end,it.end);
Interval tmp=new Interval(start,end);
intervals.add(i-1,tmp);
i--;
size--;
}else{
start=it.start;
end=it.end;
}
}
以上代码虽然不够clean,草草写出来的,但是我想O(n*log(n))的算法,肯定是可以通过所有test cases 的,结果恰恰出乎我意料,没有AC,而是TLE了。这题既然要排序,就肯定是这个O(n*log(n))的复杂度,一定是我的算法复杂度计算错了。那么哪里不对呢?
很快便想到,是java.util.List 下的add 方法和 remove 方法本身的复杂度并不为O(1)。
util.ArrayList 相关源码如下:
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
这里可以看到,引用了lang.System.arraycopy
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
很遗憾,arraycopy是一个native方法,应该是C/C++ 实现的,看不到进一步的源码了。但是没关系,我在注释里找到了这句话:
A subsequence of array components are copied from the source
* array referenced by <code>src</code> to the destination array
* referenced by <code>dest</code>. The number of components copied is
* equal to the <code>length</code> argument.
这说明,arraycopy其实是把每一个element 往前copy一位,也就是说,这个操作的复杂度是O(n)而不是常数,这就印证了我的想法。
这个题再次告诉我,光照着API或者java docs 写代码是靠不住的,还是要去看底层的源代码。前人封装好的黑盒子给我们用,并不是照着说明书做就可以了,有时候还需要知道黑盒子里大概装的是什么。