Leetcode Weekly Contest 107

压哨做完4道。
总结下思路吧。

925. Long Pressed Name

https://leetcode.com/problems/long-pressed-name/description/
这道题很快就秒了。一开始字符一定要一样。随后如果出现不一样,我们就看是不是长按造成的,一直去掉重复字符。重复字符都去完了,应该一样了,如果还不一样就FALSE。

public boolean isLongPressedName(String name, String typed) {
        if(name.equals(typed)) return true;
        char[] ns = name.toCharArray();
        char[] ts = typed.toCharArray();
        int j = 0;
        for(int i = 0; i < ns.length; ) {
            if(j == ts.length) return false;
            if(ns[i] != ts[j]) return false;
            else{
                j++;
                i++;
                if(i == ns.length){
                    while(j < ts.length && ts[j] == ts[j-1]) j++;
                    return j == ts.length;
                }
                while(j < ts.length && ts[j] != ns[i] && ts[j] == ts[j-1]) j++;
            }
            
        }
        return true;
    }

926. Flip String to Monotone Increasing

https://leetcode.com/problems/flip-string-to-monotone-increasing/description/
这道题是一维数组加状态机的动态规划。

我们假定DP J,是到J位符合递增的序列. 那么根据J位是0或1,我们会有2个状态。
如果J位是0,我们只能从J-1位是0得过来。同时如果这个是字符串的第J个和最后的状态要求不同,需要翻成对应字符而需要+1。

如果J位是1,我们可以从J-1位是0 或者J-1位是1得过来。同时如果这个是字符串的第J个和最后的状态要求不同,需要翻成对应字符而需要+1。

public int minFlipsMonoIncr(String S) {
        char[] A = S.toCharArray();
        int n = A.length;
        int[] one = new int[n + 1];
        int[] zero = new int[n + 1];

        zero[0] = one[0] = 0;
        
        int i;
        for (i = 1; i <= n; i++) {
            one[i] = A[i - 1] == '1' ? Math.min(one[i - 1], zero[i - 1]) : 
                                      Math.min(one[i - 1], zero[i - 1]) + 1;
            zero[i] = A[i - 1] == '0' ? zero[i - 1] : zero[i - 1] + 1;
        }
        return Math.min(zero[n], one[n]);
    }

927. Three Equal Parts

这道题思考过程是这样,看了数据规模得出 解的时间复杂度应该是ON,那么暴力解法肯定不行。
O N + 分成3段,就想到2个指针。那么2个指针就很容易想到双向指针。
那么我就把第一个指针放在头部,后一个指针放在尾部,开始找能不能指针只会往中间走而不回退的方法。
我发现,如果一开始头尾指针构成的二进制数,如果是一样的。那么只要中间的部分和他们也是一样的就找到答案了。
如果不一样,分为2个情况
如果前面的 比 后面的 小
可以通过右移左指针而起到增大前面的本事。因为3个区间要相等。我们最开始已经贪心的把前后都压缩到最小,所以要补救势必是通过增大的方式。

如果后面比前面小。我们肯定要通过左移右指针去把中间的元素拿到右面,来增大右面。道理同上

如果配平了左右2面,此时一定是前缀和后缀长度最小的配平。根据贪心的思想。
我们就看这个最小的配平下,中间是不是也能配平。配平就找到解了。

如果中间的比右面的大,我们可以通过左移右指针,来试图让中间的区间减少。当然右移左指针也是可以的。为什么要选择左移右指针呢?
通过观察,右移左指针必然会使得前半部分增大。而左移右指针,可能因为是0,加了前缀0等于没加。而依然保持左半和右半配平。
如果中间的区间的值比右面的小,肯定无解了。因为我们已经为了配平左右而用了最小的长度。一旦要去从左右拿出元素,肯定就无法配平左右了。
分析到这里,思路就有了。

落实到代码,我决定用DQ,因为方便2头进出。
同时为了在做DQ compare的时候,加速。
我决定忽略所有前缀0,这增加了编码难度,但是加快了时间。
所有忽略前缀0的DQ,在比较的时候,
如果长度不同,可以直接比出大小。
如果长度相同,就依次遍历每一个值,如果全相同就同,如果一旦不同,直接比较这2个不同的值。

为了达到上述效果,我在发现每个DQ的前缀是0的情况下,就不插进去了。只有1做前缀的时候再插进去,同时需要补之前没插的0.

public int[] threeEqualParts(int[] A) {
        int i = 0, j = A.length-1;
        Deque pre = new ArrayDeque<>();
        Deque mid = new ArrayDeque<>();
        Deque last = new ArrayDeque<>();
        if(A[0]!=0) pre.offerLast(1);
        if(A[j]!=0) last.offerLast(1);
        for(int k = 1; k < j; k++){
            if(mid.isEmpty() && A[k] == 0) continue;
            mid.offerLast(A[k]);
        } 
        while(i < j){
            int cp = compare(pre,last);
            if(cp<0){
                i++;
                if(A[i] == 0){
                    if(!pre.isEmpty()) pre.offerLast(0);
                } 
                else{
                    if(mid.isEmpty()) break;
                    pre.offerLast(mid.pollFirst());
                    while(!mid.isEmpty() && mid.peekFirst()==0) mid.pollFirst();
                }
            }else if(cp>0){
                j--;
                if(mid.isEmpty()) break;
                if(A[j] == 0) mid.pollLast();
                else moveMidToLast(A,mid,last,j);
            }else{
                int cp2 = compare(mid,last);
                
                if(cp2 == 0) return new int[]{i,j};
                if(cp2<0) break;
                else{
                    j--;
                    if(mid.isEmpty()) break;
                    if(A[j] == 0) mid.pollLast();
                    else moveMidToLast(A,mid,last,j);
                }
            }
        }
        return new int[]{-1,-1};
    }
    private void moveMidToLast(int[] A,Deque mid,Deque last,int j){
        int idx = j+1;
        while(idx a, Deque b){
        if(a.size() < b.size()) return -1;
        if(a.size() == b.size()){
            Iterator itr = a.iterator();
            Iterator itr2 = b.iterator();
            while(itr.hasNext()){
                int fa = itr.next();
                int fb = itr2.next();
                if(fa == fb) continue;
                return fa-fb;
            }

            return 0;
        }
        return 1;
    }

928. Minimize Malware Spread II

最后这道题的思考过程,需要从上周比赛Minimize Malware Spread来。
区别就在于抹掉一个点,就抹掉了所有的连边。
1连2,2连3. 病毒有1,2. 直接这个局面是没救的。但是再这道题里,通过抹掉JOINT POINT 2,就能盘活局面。
但是并查集不是很好处理断开的情况。
那我就想既然如果,我索引遍历所有的病毒,依次抹掉这个病毒的情况下,重新生成一个图,然后算INFECTS的点的数量。
随后取最小值就可以了。
那么构成图的时候,其实就是在UNION,发现有一个点是抹除的病毒点,就CONTINUE就好。

int[] par;
    int[] cnt;
    int find(int i){
        if(i == par[i]) return i;
        return par[i] = find(par[i]);
    }
    boolean union(int i,int j){
        int pi = find(i);
        int pj = find(j);
        if(pi == pj) return false;
        par[pi] = pj;
        cnt[pj] += cnt[pi];
        return true;
    }
    public int minMalwareSpread(int[][] graph, int[] initial) {
        Arrays.sort(initial);
        int res = initial[0];
        int min = Integer.MAX_VALUE;
        for(int i = 0; i < initial.length; i++){
            int tmp = initial[i];
            initial[i] = -1;
            int infects = minMalwareSpread2(graph,initial,tmp);
            if(infects < min){
                min = infects;
                res = tmp;
            }
            initial[i] = tmp;
        }
        return res;
    }
    public int minMalwareSpread2(int[][] graph, int[] initial,int tar) {
        int l = graph.length;
        par = new int[l];
        cnt = new int[l];
        Arrays.fill(cnt,1);
        for(int i = 0; i < l; i++) par[i] = i;
        
        for(int i = l-2; i >= 0; i--){
            for(int j = i+1; j < l; j++){
                if(graph[i][j] == 0 || i==tar || j==tar) continue;
                union(i,j);
            }
        }
        int infects = 0;
        boolean[] seen = new boolean[l];
        for(int i : initial){
            if(i == -1) continue;
            int pi = find(i);
            if(seen[pi]) continue;
            seen[pi] = true;
            infects += cnt[pi];
        }
        
        return infects;
    }

你可能感兴趣的:(Leetcode Weekly Contest 107)