zoukankan      html  css  js  c++  java
  • [LeetCode 1526] Minimum Number of Increments on Subarrays to Form a Target Array

    Given an array of positive integers target and an array initial of same size with all zeros.

    Return the minimum number of operations to form a target array from initial if you are allowed to do the following operation:

    • Choose any subarray from initial and increment each value by one.

    The answer is guaranteed to fit within the range of a 32-bit signed integer.

     

    Example 1:

    Input: target = [1,2,3,2,1]
    Output: 3
    Explanation: We need at least 3 operations to form the target array from the initial array.
    [0,0,0,0,0] increment 1 from index 0 to 4 (inclusive).
    [1,1,1,1,1] increment 1 from index 1 to 3 (inclusive).
    [1,2,2,2,1] increment 1 at index 2.
    [1,2,3,2,1] target array is formed.
    

    Example 2:

    Input: target = [3,1,1,2]
    Output: 4
    Explanation: (initial)[0,0,0,0] -> [1,1,1,1] -> [1,1,1,2] -> [2,1,1,2] -> [3,1,1,2] (target).
    

    Example 3:

    Input: target = [3,1,5,4,2]
    Output: 7
    Explanation: (initial)[0,0,0,0,0] -> [1,1,1,1,1] -> [2,1,1,1,1] -> [3,1,1,1,1] 
                                      -> [3,1,2,2,2] -> [3,1,3,3,2] -> [3,1,4,4,2] -> [3,1,5,4,2] (target).
    

    Example 4:

    Input: target = [1,1,1,1]
    Output: 1
    

     

    Constraints:

    • 1 <= target.length <= 10^5
    • 1 <= target[i] <= 10^5

    Solution 1. O(N^2) TLE

    We'll start to choose the entire array and increase each by 1 until we reach some minimum values. At this point, we can not do operations on them anymore. These minimum values essentially split the original problem into multiple smaller sub-problems. So we can solve this problem recursively.

    1. start from base value 0, and the entire array range, find minV and add minV - base operations. 

    2. recursively solve each smaller subproblems with new base value minV.

    The following implementation does a linear scan to find all minV. This makes the overall runtime to be O(N^2). 

    class Solution {
        public int minNumberOperations(int[] target) {
            return helper(target, 0, target.length - 1, 0);
        }
        private int helper(int[] target, int left, int right, int base) {
            if(left > right) {
                return 0;
            }
            int op = 0;
            int min = Integer.MAX_VALUE;
            List<Integer> minIdx = new ArrayList<>();
            for(int i = left; i <= right; i++) {
                if(target[i] < min) {
                    min = target[i];
                    minIdx = new ArrayList<>();
                    minIdx.add(i);
                }
                else if(target[i] == min) {
                    minIdx.add(i);
                }
            }
            op += (min - base);
            op += helper(target, left, minIdx.get(0) - 1, min);
            for(int i = 1; i < minIdx.size(); i++) {
                op += helper(target, minIdx.get(i - 1) + 1, minIdx.get(i) - 1, min);
            }
            op += helper(target, minIdx.get(minIdx.size() - 1) + 1, right, min);
            return op;
        }
    }

    Solution 2. O(N * logN) with segment tree

    The bottleneck in solution 1 is that it takes O(N) time to find a minimum value in a given range. We can use segment tree to acheive O(logN) minimum range query at the cost of O(N) preprocessing the target array into a segment tree. 

    Q: But segment tree range minimum query only returns a minimum value. We need to find all the indices of such minimum value within a subproblem's range in order to recur on smaller subproblems. How do we achieve this without using linear scan? 

    A: We can preprocess the target array to save each unique value's indices in sorted lists. The in each subproblem with range [left, right], we first do a binary search to find the smallest minimum value index L such that L >= left; Then do another binary search to find the biggest minimum value index R such that R <= right. Now we have the valid index range for splitting into smaller subproblems. 

    Each subproblem is independent with other subproblems and only solved once. So we have O(N) subproblems. Each subproblem takes O(log N) to find a range minimum and do 2 binary search. So it takes O(N * logN) time. Preprocessing of segment tree and index mapping takes O(N) time.

    class Solution {
        private Map<Integer, List<Integer>> idxMap = new HashMap<>();
        private SegmentTree st = null;
        public int minNumberOperations(int[] target) {
            for(int i = 0; i < target.length; i++) {
                idxMap.computeIfAbsent(target[i], k -> new ArrayList<>()).add(i);
            }
            st = new SegmentTree(target);
            return helper(0, target.length - 1, 0);
        }
        private int helper(int left, int right, int base) {
            if(left > right) {
                return 0;
            }
            int op = 0;
            int min = st.getMinimumInRange(left, right);
            op += (min - base);
            List<Integer> minIdx = idxMap.get(min);
            int l = bs1(minIdx, left);
            int r = bs2(minIdx, right);
            if(l >= 0 && r >= 0) {
                op += helper(left, minIdx.get(l) - 1, min);
                for(int i = l + 1; i <= r; i++) {
                    op += helper(minIdx.get(i - 1) + 1, minIdx.get(i) - 1, min);
                }
                op += helper(minIdx.get(r) + 1, right, min);            
            }
            return op;
        }
        private int bs1(List<Integer> list, int t) {
            int l = 0, r = list.size() - 1;
            while(l < r - 1) {
                int mid = l + (r - l) / 2;
                if(list.get(mid) < t) {
                    l = mid + 1;
                }
                else {
                    r = mid;
                }
            }
            if(list.get(l) >= t) {
                return l;
            }
            else if(list.get(r) >= t) {
                return r;
            }
            return -1;
        }
        private int bs2(List<Integer> list, int t) {
            int l = 0, r = list.size() - 1;
            while(l < r - 1) {
                int mid = l + (r - l) / 2;
                if(list.get(mid) > t) {
                    r = mid - 1;
                }
                else {
                    l = mid;
                }
            }
            if(list.get(r) <= t) {
                return r;
            }
            else if(list.get(l) <= t) {
                return l;
            }
            return -1;
        }
    }
    class SegmentTree {
        private int[] st;
        private int leafNodeNum;
        private int expandedSize;
        private int height;
    
        public SegmentTree(int[] nums) {
            leafNodeNum = nums.length;
            height = (int)(Math.ceil(Math.log(nums.length) / Math.log(2)));
            expandedSize = (int)Math.pow(2, height);
            int max_size = expandedSize * 2 - 1;
            st = new int[max_size];
    
            for(int i = 0; i < expandedSize; i++) {
                st[expandedSize - 1 + i] = i >= leafNodeNum ? Integer.MAX_VALUE : nums[i];
            }
            for(int i = expandedSize - 2; i >= 0; i--) {
                st[i] = Math.min(st[i * 2 + 1], st[i * 2 + 2]);
            }
        }
    
        /*
        @params: [left, right] is a range in the original input array nums
        */
        public int getMinimumInRange(int left, int right) {
            // convert range of the original array to segment tree range
            left += (expandedSize - 1);
            right += (expandedSize - 1);
    
            int min = Integer.MAX_VALUE;
    
            while(left <= right) {
                if(left % 2 == 0) {
                    min = Math.min(min, st[left]);
                    left++;
                }
                if(right % 2 != 0) {
                    min = Math.min(min, st[right]);
                    right--;
                }
                left = (left - 1) / 2;
                right = (right - 2) /2;
            }
            return min;
        }
    }

    Solution 3. O(N) solution that is unbelivably simple!

    Well, it turns out the optimal solution is very very simple.  The key observation is that for two adjacent values in target array prev and curr, we have the following 2 cases.

    1. prev >= curr, this means to reach prev, we've already past curr, so when we add the operations needed for prev, we've already taken the operations needed for curr into account;

    2. prev < curr, it takes at least curr - prev to go from prev to curr, so when visiting curr, we need to add curr - prev operations.

    Basically, if we are seeing an increasing sequence, we need to add the adjacent difference; otherwise previous operations already take care of all the <= values in a non-increasing sequence.

    class Solution {
        public int minNumberOperations(int[] target) {
            int op = 0, prev = 0;
            for(int v : target) {
                op += Math.max(v - prev, 0);
                prev = v;
            }
            return op;
        }
    }
  • 相关阅读:
    Google资深工程师深度讲解Go语言测试与性能调优(八)
    linux命令 对日志文件的IP出现的次数进行统计 并显示次数最多的前六名
    Java之不允许变量重定义
    Java之数组
    ATL之STDTHUNK
    Java之访问控制
    Java之线程初步II
    Android之Activating Components
    ATL之如何聚合一个组件
    WTL中对话框数据交换
  • 原文地址:https://www.cnblogs.com/lz87/p/13378905.html
Copyright © 2011-2022 走看看