zoukankan      html  css  js  c++  java
  • [LeetCode 1562] 1562. Find Latest Group of Size M

    Given an array arr that represents a permutation of numbers from 1 to n. You have a binary string of size n that initially has all its bits set to zero.

    At each step i (assuming both the binary string and arr are 1-indexed) from 1 to n, the bit at position arr[i] is set to 1. You are given an integer m and you need to find the latest step at which there exists a group of ones of length m. A group of ones is a contiguous substring of 1s such that it cannot be extended in either direction.

    Return the latest step at which there exists a group of ones of length exactly mIf no such group exists, return -1.

     

    Example 1:

    Input: arr = [3,5,1,2,4], m = 1
    Output: 4
    Explanation:
    Step 1: "00100", groups: ["1"]
    Step 2: "00101", groups: ["1", "1"]
    Step 3: "10101", groups: ["1", "1", "1"]
    Step 4: "11101", groups: ["111", "1"]
    Step 5: "11111", groups: ["11111"]
    The latest step at which there exists a group of size 1 is step 4.

    Example 2:

    Input: arr = [3,1,5,4,2], m = 2
    Output: -1
    Explanation:
    Step 1: "00100", groups: ["1"]
    Step 2: "10100", groups: ["1", "1"]
    Step 3: "10101", groups: ["1", "1", "1"]
    Step 4: "10111", groups: ["1", "111"]
    Step 5: "11111", groups: ["11111"]
    No group of size 2 exists during any step.
    

    Example 3:

    Input: arr = [1], m = 1
    Output: 1
    

    Example 4:

    Input: arr = [2,1], m = 2
    Output: 2
    

     

    Constraints:

    • n == arr.length
    • 1 <= n <= 10^5
    • 1 <= arr[i] <= n
    • All integers in arr are distinct.
    • 1 <= m <= arr.length

    The key to solve this problem is how to efficiently keep and update the current groups of 1 dynamically. Notice that we can use an interval [start, end] to represent a group as there will be no over-lapping intervals. If we can find an update an interval in O(1) or O(logN) time, we can solve this problem within the time limit. 

    Solution 1. TreeMap + HashMap

    During contest, I came up with this solution: using a tree map to maintain and update intervals dynamically. A map entry's key is the start index and value is the end index. Then I maintain another map to track the count of all different lengths of groups. This way, if I loop the input array forward and do the following I'll get the right answer:

    1. for a given position p, find the interval prev that is right before p and the interval next that is right after p.

    2. if prev exists and its end index is adjacent with p, connect prev with p; if next exists and its start index is adjacent with p, connect next with p.

    3. insert the new interval and update cnt map. 

    4. if cnt of length m > 0, update answer.

    The runtime is O(N * logN), space is O(N).

    class Solution {
        public int findLatestStep(int[] arr, int m) {
            TreeMap<Integer, Integer> tm = new TreeMap<>();
            int[] cnt = new int[arr.length + 1];
            int ans = -1;
            for(int i = 0; i < arr.length; i++) {
                Map.Entry<Integer, Integer> prev = tm.lowerEntry(arr[i]);
                Map.Entry<Integer, Integer> next = tm.higherEntry(arr[i]);
                int start = arr[i], end = arr[i];
                if(prev != null && prev.getValue() + 1 == arr[i]) {
                    start = prev.getKey();
                    int len = prev.getValue() - prev.getKey() + 1;
                    cnt[len]--;
                    tm.remove(prev.getKey());
                }
                if(next != null && next.getKey() - 1 == arr[i]) {
                    end = next.getValue();
                    int len = next.getValue() - next.getKey() + 1;
                    cnt[len]--;
                    tm.remove(next.getKey());
                }
                cnt[end - start + 1]++;
                tm.put(start, end);
                if(cnt[m] > 0) {
                    ans = i + 1;
                }
            }
            return ans;
        }
    }

    Alternatively, since we are looking for the latest step that generates group of size m, we can loop backward using the same idea above. This way, we do not need to maintain cnt map. As soon as we find a group of length m, we return that step. At each step, we try to split a bigger length group into two smaller length groups. If at least one of these two smaller groups has length m, it means that right after the previous step, we had length m groups! But all steps afterwards provides no such groups, hence the previous step is the correct answer.

    class Solution {
        public int findLatestStep(int[] arr, int m) {
            if(m == arr.length) {
                return m;
            }
            TreeMap<Integer, Integer> tm = new TreeMap<>();
            tm.put(1, arr.length);
            int ans = -1;
            for(int i = arr.length - 1; i >= 0; i--) {
                Map.Entry<Integer, Integer> curr = tm.floorEntry(arr[i]);
                int ll = curr.getKey(), lr = arr[i] - 1;
                int rl = arr[i] + 1, rr = curr.getValue();
                if(lr - ll + 1 == m || rr - rl + 1 == m) {
                    ans = i;
                    break;
                }
                tm.remove(ll);
                if(ll <= lr) {
                    tm.put(ll, lr);
                }
                if(rl <= rr) {
                    tm.put(rl, rr);
                }
            }
            return ans;
        }
    }

    Solution 2. O(1) update on intervals using only array

    For any position p that we are about to change from 0 to 1, we only care if p - 1 has 1 and p + 1 has 1, meaning we only care if the previous interval's end index and the next interval's start index. And because there are no over-lapping intervals at any time, so we never care about what happens inside an interval as long as we correctly update the boundaries of each interval every time. 

    So we can just maintain an array called intervals[i] to achieve the same goal with solution 1. Each entry represents the length of a group that starts at or ends at position i. Looping forward for each position p and do the following:

    1. get the length of the group that ends at p - 1 and that starts at p + 1. 

    2. if the previous group that ends at p - 1 already has length m, then the current step is going to make it longer. Same for the next group that ends at p + 1. 

    3. connect previous with the current 1 at p then with next. if the new group has length m, update counter. Then update this new interval's start position p - prev's length and end position p + next's length to have the new length. 

    4. if after the current step we still have at least 1 group of length m, update answer.

    Why does overwriting intervals[i]'s start and end index value work? It works because we do not have overlapping intervals. And after creating a new interval [left, right], we'll never have to process positions inside [left, right] again. Only positions that are before left or after right will show up. Every positions inside have been processed before we create this new interval. For positions that are before left, we only need the updated next interval length; For positions that are after right, we only need the updated previous interval length. 

    Both the runtime and space is O(N)

    class Solution {
        public int findLatestStep(int[] arr, int m) {
            int n = arr.length, cnt = 0, ans = -1;
            int[] intervals = new int[n + 2];
            
            for(int i = 0; i < n; i++) {
                int prev = intervals[arr[i] - 1];
                int next = intervals[arr[i] + 1]; 
                int len = prev + 1 + next;
                if(prev == m) {
                    cnt--;
                }
                if(next == m) {
                    cnt--;
                }
                if(len == m) {
                    cnt++;
                }
                intervals[arr[i] - prev] = len;
                intervals[arr[i] + next] = len;
                if(cnt > 0) {
                    ans = i + 1;
                }
            }
            return ans;
        }
    }

    UnionFind solution practice. 

    There were some contestants that solved this problem using UnionFind, which is good practice. 

    The idea is simliar with the above 2 solutions and pretty simple: for each position p, check if it can connect with the component that includes position p - 1, then check if it can connect with the component that includes position p + 1. If it can connect with either, do it and update the cnt map for group lengths accordingly. 

    class Solution {
        class UnionFind {
            int[] root;
            int[] sz;
            
            UnionFind(int n) {
                root = new int[n];
                sz = new int[n];
                for(int i = 0; i < n; i++) {
                    root[i] = i;
                    sz[i] = 1;
                }
            }
            
            int findRoot(int p) {
                if(root[p] != p) {
                    root[p] = findRoot(root[p]);
                }
                return root[p];
            }
            
            void union(int p, int q) {
                int rp = findRoot(p);
                int rq = findRoot(q);
                if(rp != rq) {
                    if(sz[rp] >= sz[rq]) {
                        root[rq] = rp;
                        sz[rp] += sz[rq];
                    }
                    else {
                        root[rp] = rq;
                        sz[rq] += sz[rp];                    
                    }
                }
            }
        }
        public int findLatestStep(int[] arr, int m) {
            int n = arr.length, ans = -1;
            UnionFind uf = new UnionFind(n + 1);
            boolean[] set = new boolean[n + 1];
            int[] cnt = new int[n + 1];
            for(int i = 0; i < n; i++) {
                if(arr[i] > 0 && set[arr[i] - 1]) {
                    int prevRoot = uf.findRoot(arr[i] - 1);
                    cnt[uf.sz[prevRoot]]--;
                    uf.union(arr[i], prevRoot);
                }
                if(arr[i] < n && set[arr[i] + 1]) {
                    int nextRoot = uf.findRoot(arr[i] + 1);
                    cnt[uf.sz[nextRoot]]--;
                    uf.union(arr[i], nextRoot);
                }
                int currRoot = uf.findRoot(arr[i]);
                cnt[uf.sz[currRoot]]++;
                if(cnt[m] > 0) {
                    ans = i + 1;
                }
                set[arr[i]] = true;
            }
            return ans;
        }
    }
  • 相关阅读:
    课程详情页之前台
    课程详情页之后台
    java虚拟机原理图解6--class文件中的字段集合,field字段在class文件中是怎样组织的
    java虚拟机原理图解5--class文件中的访问标志,类索引,父类索引,接口索引集合
    java虚拟机原理图解4--class文件中的常量池详解(下)
    java虚拟机原理图解3--class文件中的常量池详解(上)
    java虚拟机原理图解2--class文件中的常量池
    JVM虚拟机原理图解1--class文件基本组织结构
    Http协议中GET和POST的区别
    SpringCloud插件之ribbon和feign的使用
  • 原文地址:https://www.cnblogs.com/lz87/p/13557275.html
Copyright © 2011-2022 走看看