zoukankan      html  css  js  c++  java
  • [LeetCode 321] Create Maximum Number

    Given two arrays of length m and n with digits 0-9 representing two numbers. Create the maximum number of length k <= m + n from digits of the two. The relative order of the digits from the same array must be preserved. Return an array of the k digits.

    Note: You should try to optimize your time and space complexity.

    Example 1:

    Input:
    nums1 = [3, 4, 6, 5]
    nums2 = [9, 1, 2, 5, 8, 3]
    k = 5
    Output:
    [9, 8, 6, 5, 3]

    Example 2:

    Input:
    nums1 = [6, 7]
    nums2 = [6, 0, 4]
    k = 5
    Output:
    [6, 7, 6, 0, 4]

    Example 3:

    Input:
    nums1 = [3, 9]
    nums2 = [8, 9]
    k = 3
    Output:
    [9, 8, 9]



    First of all, this is a GREAT problem in that it requires converting the original task into subtasks, which is an important part of problem solving skill. Now let's divide into it. I actually came up with the right general idea: get maximum array of length L1 from nums1 and maximum array of length L2 from nums2 such that L1 + L2 == k. Try all different (L1, L2) and take the best as the final answer. 

    So there are 2 subtasks: 

    1. Given an array of length N and a length L, return the max subsequence of length L;

    2. Given two arrays, merge them and keep the relative orders for numbers from the same array, return the max result.

    First Incorrect Attempt: Dynamic programming with StringBuilder

    If we can solve the subtask: given a length L and an array A of total length N, return the max subsequence of length L, then we can solve this problem. This can be solved using dp in O(N^2) time. Since the array can be pretty long and to avoid integer overflow, I decided to use a 2D StringBuilder dp[i][j] to represent the max sequence of length j out of A[0, i], in string representation. 

    The DP initialization and state transition are as follows:

    Init: when subsequence's length is 0, dp[i][0] is just an empty stringbuilder;

    State transition: for each new number A[i], we update dp[i][j], for j in [1, i + 1].(There are i + 1 numbers so far, so subsequence's length is up to i + 1). Either we use A[i] and append it to the end of dp[i - 1][j - 1]; or we do not use A[i] but inherit the previous result dp[i - 1][j]. 

    Now we need to merge two max arrays. The first algorithm I came up is to piggy-back the merge sort by always picking the larger number. If there is a tie, pick either. As smart as it sounds, this merge alogrithm is actually incorrect. Consider the following counter example:

    nums1 = [2,5,6,4,4,0], nums2 =  [7,3,8,0,6,5,7,6,2],  k = 15

    Everything is ok until we hit the tie between the two 0s from nums1 and nums2. The right next step is to pick the 0 from nums2, then the rest of nums2 before picking the last 0 in nums1. To generalize the right next step here, we need to break a tie by picking the lexicographically bigger remaing array. There is no easy way to find which one to choose without comparing the two remaining arrays. 

    class Solution {
        public int[] maxNumber(int[] nums1, int[] nums2, int k) {
            int m = nums1.length, n = nums2.length;
            StringBuilder[][] dp1 = new StringBuilder[m][m + 1];
            StringBuilder[][] dp2 = new StringBuilder[n][n + 1];
            
            for(int i = 0; i < m; i++) {
                dp1[i][0] = new StringBuilder();
            }
            for(int i = 0; i < m; i++) {
                for(int j = 1; j <= i + 1; j++) {
                    StringBuilder buffer = new StringBuilder();
                    if(i > 0) {
                        buffer.append(dp1[i - 1][j - 1]);
                    }                
                    buffer.append(nums1[i]);
                    if(i == 0 || j == i + 1 || compare(buffer, dp1[i - 1][j]) > 0) {
                        dp1[i][j] = buffer;
                    }
                    else {
                        dp1[i][j] = dp1[i - 1][j];
                    }
                }
            }
            
            for(int i = 0; i < n; i++) {
                dp2[i][0] = new StringBuilder();
            }
            for(int i = 0; i < n; i++) {
                for(int j = 1; j <= i + 1; j++) {
                    StringBuilder buffer = new StringBuilder();
                    if(i > 0) {
                        buffer.append(dp2[i - 1][j - 1]);
                    }                
                    buffer.append(nums2[i]);
                    if(i == 0 || j == i + 1 || compare(buffer, dp2[i - 1][j]) > 0) {
                        dp2[i][j] = buffer;
                    }
                    else {
                        dp2[i][j] = dp2[i - 1][j];
                    }
                }
            }
            
            StringBuilder maxS = new StringBuilder();
            for(int i = 0; i < k; i++) {
                maxS.append('0');
            }
            for(int i = 0; i <= Math.min(m, k); i++) {
                if(k - i > n) {
                    continue;
                }
                StringBuilder b1 = m > 0 ? dp1[m - 1][i] : new StringBuilder();
                StringBuilder b2 = n > 0 ? dp2[n - 1][k - i] : new StringBuilder();
                StringBuilder curr = build(b1, b2);
                if(compare(curr, maxS) > 0) {
                    maxS = curr;
                } 
            }
            int[] ans = new int[k];
            for(int i = 0; i < k; i++) {
                ans[i] = maxS.charAt(i) - '0';
            }
            return ans;
        }
        
        private StringBuilder build(StringBuilder b1, StringBuilder b2) {
            StringBuilder b = new StringBuilder();
            int i1 = 0, i2 = 0;
            while(i1 < b1.length() && i2 < b2.length()) {
                if(b1.charAt(i1) >= b2.charAt(i2)) {
                    b.append(b1.charAt(i1));
                    i1++;
                }
                else {
                    b.append(b2.charAt(i2));
                    i2++;
                }
            }
            while(i1 < b1.length()) {
                b.append(b1.charAt(i1));
                i1++;            
            }
            while(i2 < b2.length()) {
                b.append(b2.charAt(i2));
                i2++;            
            }
            return b;
        }
        
        private int compare(StringBuilder buffer1, StringBuilder buffer2) {
            if(buffer1.length() < buffer2.length()) {
                return -1;
            }
            else if(buffer1.length() > buffer2.length()) {
                return 1;
            }
            for(int i = 0; i < buffer1.length(); i++) {
                if(buffer1.charAt(i) < buffer2.charAt(i)) {
                    return -1;
                }
                else if(buffer1.charAt(i) > buffer2.charAt(i)) {
                    return 1;
                }
            }
            return 0;
        }
    }

    Second correct but TLE attempt: Dynamic programming with StringBuilder, then DP/Memoization to get the maximum merged array.

    Since the merge algorithm in my 1st attempt is wrong, I started to generalize this merge subtask: Given two arrays, merge them such that the relative orders of numbers from the same array are kept and the result is maximum. It is obvious we always should pick the bigger number greedily. The problem is how to break a tie. Well, we can try both and pick the better one. i.e, for index i and index j such that nums1[i] == nums2[j], first pick nums1[i], then solve the smaller subproblem of merging nums1[i + 1, m - 1] and nums2[j, n - 1]; Then pick nums2[j], then solve the smaller subproblem of merging nums1[i, m - 1] and nums2[j + 1, n - 1]. Depending how many ties we'll run into, there will be over-lapping subproblems, so I tried recursion with memoization here: dp[i][j] is the max sequence we get from nums1[i, m - 1] and nums2[j, n - 1]. 

    The base case is dp[m][n] as empty stringbuilder. Two empty arrays yields an empty result. 

    Although at this point I have a right algorithm, it is too slow and gets the TLE verdict. The reason is that it takes O(M^2) + O(N^2) + O(K * M* N) operations to compute all dp tables. And since I am using stringbuilder as dp entry, each such dp operation does not take O(1) time. It takes O(max(M, N)) overhead time to copy string. 

    
    
        private StringBuilder build(StringBuilder b1, StringBuilder b2) {
            //dp[i][j]: the max number we get from b1[i, m - 1] and b2[j, n - 1]
            int m = b1.length(), n = b2.length();
            StringBuilder[][] dp = new StringBuilder[m + 1][n + 1];
            dp[m][n] = new StringBuilder();
            return builderHelper(dp, b1, 0, b2, 0);
        }
        
        private StringBuilder builderHelper(StringBuilder[][] dp, StringBuilder b1, int i1, StringBuilder b2, int i2) {
            if(dp[i1][i2] != null) {
                return dp[i1][i2];
            }    
            StringBuilder buffer = new StringBuilder();
            if(i1 < b1.length() && i2 < b2.length()) {
                if(b1.charAt(i1) > b2.charAt(i2)) {
                    buffer.append(b1.charAt(i1));
                    buffer.append(builderHelper(dp, b1, i1 + 1, b2, i2));
                }
                else if(b1.charAt(i1) < b2.charAt(i2)) {
                    buffer.append(b2.charAt(i2));
                    buffer.append(builderHelper(dp, b1, i1, b2, i2 + 1));                
                }
                else {
                    StringBuilder buffer1 = builderHelper(dp, b1, i1 + 1, b2, i2);
                    StringBuilder buffer2 = builderHelper(dp, b1, i1, b2, i2 + 1);
                    buffer.append(b1.charAt(i1));
                    if(compare(buffer1, buffer2) >= 0) {
                        buffer.append(buffer1);
                    }
                    else {
                        buffer.append(buffer2);
                    }
                }
            }
            else if(i1 < b1.length()) {
                buffer.append(b1.charAt(i1));
                buffer.append(builderHelper(dp, b1, i1 + 1, b2, i2));
            }
            else {
                buffer.append(b2.charAt(i2));
                buffer.append(builderHelper(dp, b1, i1, b2, i2 + 1));            
            }
            dp[i1][i2] = buffer;
            return buffer;
        }
    
    
    

    Third Accepted Attempt: Greedy with Stack to generate max subsequence of given length from given array. O(M * N) greedy method to merge two arrays. Use array of integers instead of strings to avoid integer overflow and achieve minimum runtime overhead.

    Using dp to precompute all possible length of max subsequences for a given array is definitely correct. And on average for each given length, it takes linear time to generate such a max sequence. But maintaining a 2D dp with strings has too much runtime overhead. The following simple greedy idea works for subtask 1. 

    For a given number A[i], as long as it is bigger than last included numbers and we have enough remaining numbers to get a required length subsequence,  get ride of the last included smaller numbers. This can be done using a stack. 

    For subtask 2, we again use a simple O(M * N) greedy algorithm: always pick bigger first; To break ties, we do a linear comparison of the two remaining subarrays to make a decision. There is a linear solution that involves suffix array to solve subtask 2, but it is very complicated.

    The runtime is O(M + N)^3, cubic. 

    class Solution {
        public int[] maxNumber(int[] nums1, int[] nums2, int k) {
            int[] ans = new int[k];
            for(int l = Math.max(0, k - nums2.length); l <= Math.min(nums1.length, k); l++) {
                int[] seq1 = getMaxSeq(nums1, l);
                int[] seq2 = getMaxSeq(nums2, k - l);
                int[] seq = merge(seq1, seq2);
                if(compare(seq, ans) > 0) {
                    ans = seq;
                }
            }
            return ans;
        }
        private int[] getMaxSeq(int[] a, int len) {
            Deque<Integer> dq = new ArrayDeque<>();
            for(int i = 0; i < a.length; i++) {
                while(dq.size() > 0 && dq.size() + a.length - i > len 
                      && dq.peekLast() < a[i]) {
                    dq.pollLast();
                }
                if(dq.size() < len) {
                    dq.addLast(a[i]);
                }            
            }
            int[] seq = new int[len];
            int j = 0;
            while(dq.size() > 0) {
                seq[j] = dq.pollFirst();
                j++;
            }
            return seq;
        }
        private int[] merge(int[] a, int[] b) {
            int[] combine = new int[a.length + b.length];
            int i = 0, j = 0, t = 0;
            while(i < a.length && j < b.length) {
                if(a[i] > b[j]) {
                    combine[t] = a[i];
                    i++;
                }
                else if(a[i] < b[j]) {
                    combine[t] = b[j];
                    j++;
                }
                else {
                    int ii = i, jj = j;
                    while(ii < a.length && jj < b.length && a[ii] == b[jj]) {
                        ii++;
                        jj++;
                    }
                    if(jj == b.length) {
                        combine[t] = a[i];
                        i++;
                    }
                    else if(ii == a.length) {
                        combine[t] = b[j];
                        j++;
                    }
                    else if(a[ii] > b[jj]) {
                        combine[t] = a[i];
                        i++;
                    }
                    else {
                        combine[t] = b[j];
                        j++;
                    }
                }
                t++;
            }
            while(i < a.length) {
                combine[t] = a[i];
                i++;
                t++;
            }
            while(j < b.length) {
                combine[t] = b[j];
                j++;
                t++;
            }
            return combine;
        }
        private int compare(int[] a, int[] b) {
            for(int i = 0; i < a.length; i++) {
                if(a[i] > b[i]) {
                    return 1;
                }
                else if(a[i] < b[i]) {
                    return -1;
                }
            }
            return 0;
        }
    }

    Finally a more concise implementaion of the above accepted solution: Use array to simulate stack.

    The runtime is still cubic but it is a bit faster because of array over stack, and the code logic is also crystal clear!

    class Solution {
        public int[] maxNumber(int[] nums1, int[] nums2, int k) {
            int[] ans = new int[k];
            for(int i = Math.max(0, k - nums2.length); i <= Math.min(nums1.length, k); i++) {
                int[] candidate = merge(maxArray(nums1, i), maxArray(nums2, k - i));
                if(compare(candidate, 0, ans, 0) > 0) {
                    ans = candidate;
                }
            }
            return ans;
        }
        private int compare(int[] a, int i, int[] b, int j) {
            while(i < a.length && j < b.length && a[i] == b[j]) {
                i++;
                j++;
            }
            int d1 = (i == a.length ? -1 : a[i]);
            int d2 = (j == b.length ? -1 : b[j]);
            return d1 - d2;
        }
        private int[] maxArray(int[] a, int len) {
            int[] seq = new int[len];
            for(int i = 0, j = 0; i < a.length; i++) {
                while(j > 0 && j + a.length - i > len && seq[j - 1] < a[i]) {
                    j--;
                }
                if(j < len) {
                    seq[j] = a[i];
                    j++;
                }
            }
            return seq;
        }
        private int[] merge(int[] a, int[] b) {
            int[] combine = new int[a.length + b.length];
            for(int i = 0, j = 0, k = 0; k < combine.length; k++) {
                combine[k] = compare(a, i, b, j) >= 0 ? a[i++] : b[j++];
            }
            return combine;
        }
    }

    What a problem!!  ^^

  • 相关阅读:
    Cocos2d-x学习之---自定义图标(带触摸事件)
    Cocos2d-x关于ScrollView
    学习实战三:基于Cocos2d-x引擎模仿微信打飞机游戏
    补算法相关知识一:蚂蚁算法
    避免Cocos2d-x编写的游戏在用eclipse生成安卓包时繁琐的写Android.mk文件
    Cocos2d-x学习之---模仿微信打飞机游戏敌机层设计初想
    Cocos2d-x学习之---2013年10月11日小记
    有时候真怕,时间会说出真心话。
    NO2:设置RedHat Linux下的samba开机启动
    NO1:在Windows端安装SecureCRT来连接Linux
  • 原文地址:https://www.cnblogs.com/lz87/p/13538875.html
Copyright © 2011-2022 走看看