• [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;
        }
    }
  • 相关阅读:
    swift 学习笔记
    collection view 开发笔记
    代码片段
    childViewController 小计
    iOS 二维码扫描
    statusbarhidden stuff 状态栏的各种特性
    AFNetworking 3.0 断点续传 使用记录
    scrollview 图片放大 捏合 瓦片地图 相关注意事项
    iOS 9 强制横屏
    简单的JS运动封装实例---侧栏分享到
  • 原文地址:https://www.cnblogs.com/lz87/p/13378905.html
Copyright © 2020-2023  润新知