[LeetCode] 25倍速度被完虐的 Sum3

来源:互联网 发布:夏洛克妹妹欧洛丝 知乎 编辑:程序博客网 时间:2024/05/02 04:54

渣用了两个hash,结果速度是神的25倍


渣代码,hash存第三个数字附带统计出现次数,hash结果去重复,附带练了一下qsort所以98行

 int[] n;    public int partition(int start,int end){        int x=end;        int i=start-1;        int tmp;        for(int j=start;j<end;j++){            if(n[j]<=n[x]){                i++;                tmp=n[i];                n[i]=n[j];                n[j]=tmp;            }        }        i++;        tmp=n[x];        n[x]=n[i];        n[i]=tmp;        return i;    }    public void qsort(int start, int end){        if(start > end)            return;        int i=partition(start,end);        qsort(start,i-1);        qsort(i+1,end);            }    HashSet<String> results=new HashSet<String>();    HashMap<Integer,Integer> nmap=new HashMap<Integer,Integer>();        public void addRes(int i, int j){        int l=0-i-j;        if(l<=i){            int tmp=l;            l=j;            j=i;            i=tmp;        }        if(i<l&&l<=j){            int tmp=j;            j=l;            l=tmp;        }        if(i==j&&j==l){        Integer m=nmap.get(i);        if(m<3)        return;        }        if(i==j||j==l){        Integer m=nmap.get(j);        if(m<2)        return;        }        String s=""+i+","+j+","+l;        results.add(s);    }    public void genRes(ArrayList<ArrayList<Integer>> res){        Iterator<String> it=results.iterator();        while(it.hasNext()){            ArrayList<Integer> list=new ArrayList<Integer>();            String[] ss=it.next().split(",");            for(String ts:ss)                list.add(Integer.parseInt(ts));            res.add(list);        }    }    public ArrayList<ArrayList<Integer>> threeSum(int[] num) {        n=num;        qsort(0,n.length-1);        for(int i=0;i<n.length;i++){        Integer m=nmap.get(n[i]);        if(m==null)         m=1;        else        m++;            nmap.put(n[i], m);        }        for(int i=0;i<n.length;i++)            for(int j=i;j<n.length;j++){                int v=0-n[i]-n[j];                if(nmap.containsKey(v)){              //  System.out.println("n[i]="+n[i]+" n[j]="+n[j]);                    addRes(n[i],n[j]);                }            }        ArrayList<ArrayList<Integer>> reslist=new ArrayList<ArrayList<Integer>>();        genRes(reslist);        return reslist;    }

先看我仿照神代码写的再解释


public class Solution {    int[] n;public int partition(int start, int end) {int x = end;int i = start - 1;int tmp;for (int j = start; j < end; j++) {if (n[j] <= n[x]) {i++;tmp = n[i];n[i] = n[j];n[j] = tmp;}}i++;tmp = n[x];n[x] = n[i];n[i] = tmp;return i;}public void qsort(int start, int end) {if (start > end)return;int i = partition(start, end);qsort(start, i - 1);qsort(i + 1, end);}ArrayList<ArrayList<Integer>> reslist = new ArrayList<ArrayList<Integer>>();public ArrayList<ArrayList<Integer>> threeSum(int[] num) {n = num;qsort(0, n.length - 1);Integer li=null;for (int i = 0; i < n.length; i++) {if(li!=null&&li==n[i])continue;li=n[i];int target = -n[i];int start = i + 1;int end = n.length - 1;while (start < end) {//find target from current start,endwhile (n[start] + n[end] != target&&start<end) {if (n[start] + n[end] < target)start++;elseend--;}//System.out.println("i= "+i+"  start="+start+" end="+end);// equals to target once but maybe moreif(start>=end)break;addRes(n[i], n[start], n[end]);//remove the replicated datawhile(start<end&&start<n.length-1&&n[start]==n[start+1])start++;start++;while(start<end&&end>1&&n[end]==n[end-1])end--;end--;}}return reslist;}private void addRes(int i, int j, int k) {ArrayList<Integer> list = new ArrayList<Integer>();list.add(i);list.add(j);list.add(k);reslist.add(list);}}

这道题据说很常考,我觉得主要是它的优化空间真的很大。

最笨解显然是O(N3),主要问题有两个,肯定是先排序 没得说

1. 去重复问题,这个问题实在太蛋疼了,我的耗时主要就在这里

2. 双指针到底怎么用


分析一下神接法,先说双指针问题。我一开始想第一个外圈i肯定是从0到len,里面两个指针只用从i+1 len 就可以了。到这里还是没有错的,下一步,两个指针怎么走?


二分? 你输了。我也觉得是二分,也就是两个指针中,一个从i+1 到len遍历,一个飞着找对应解,算算复杂度,外圈n 第二圈也是n 里面是logn,所以复杂度是O(N2lgN),完蛋了。


其实是可以两个指针一步一步走的,我一开始也以为这样会丢解,后来自己推了一下,能证明这样是不会丢解的。假设遍历队列是 a1 a2 a3... an 从小到大排序好的,假设两个指针分别指在start 和 end的位置上,目标是target

如下遍历即可:

if start+end<target,start++;

if start+end >target, end--; 且这样不会丢解

证明:丢解的原因无非是在左边指针为start值时,end跳过了某个值。反之亦可,所以只看一组

当<target,对于这个end值,start之前的值一定无法匹配,因为是排序好的,之前的值加上end一定更小于target。所以只能尝试下一个start 如果这时候start++了,且start+end>target了,则新start之后的值一定也无法符合=target,因为都大,即这个end无解。这个end就可以跳过了。


所以双指针最后就是只是加起来遍历了i+1~len一遍.


再看重复问题。神处理的依然很绝。

我只想到了hash判重复,但其实可以在双指针里跳过重复,太绝了。



rewrite:

ArrayList<ArrayList<Integer>> reslist=new  ArrayList<ArrayList<Integer>>(); int r1=Integer.MAX_VALUE; int r2=Integer.MAX_VALUE; public ArrayList<ArrayList<Integer>> threeSum(int[] num) { Arrays.sort(num); for(int i=0;i<num.length;i++){ if(i>0&&num[i]==num[i-1]) continue; int head=i+1; int tail=num.length-1; while(true){ if(head>=tail) break; int v=num[i]+num[head]+num[tail]; if(v==0){ genRes(num[i],num[head],num[tail]); head++; }else{ if(v<0){ head++; }else tail--; }  } } return reslist; }private void genRes(int i, int j, int k) { if(r1==j&&r2==k) return; r1=j; r2=k; ArrayList<Integer> list=new ArrayList<Integer>(); list.add(i); list.add(j); list.add(k); reslist.add(list);}



0 0