2

Given an array of numbers, arrange them in a way that yields the largest value. For example, if the given numbers are {54, 546, 548, 60}, the arrangement 6054854654 gives the largest value. And if the given numbers are {1, 34, 3, 98, 9, 76, 45, 4}, then the arrangement 998764543431 gives the largest value.

So, the provided function declaration is

string printLargest(vector<string> &arr)

The solution that I wrote is provided below.

string printLargest(vector<string> &arr) {
    for (int i=0; i<arr.size()-1; i++) {
        for (int j=i+1; j<arr.size(); j++) {
            string y = arr[i] + arr.at(j);
            string z = arr[j] + arr[i];
            if (z>y) swap(arr[j], arr[i]);
        }
    }
    string y="";
    for(string x:arr) y +=x;
    return y;
}

The online compiler says "Time Limit Exceeded" Please optimize your code and submit again. I think my solution take O(n^2). Expected Time Complexity: O(NlogN), How can I optimize my code?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Roushan
  • 45
  • 4
  • 3
    Use a standard sorting algorithm? E.g `std::sort` – Alan Birtles Mar 21 '21 at 10:05
  • 1
    Wouldn't algorithmically speaking the "highest" numbers going first, sorted, produce the highest number? Like sort by their string versions, "99" > "989" etc. – tadman Mar 21 '21 at 10:05
  • 1
    sort strings in descending order however you still need a brute force to chose between strings with the same prefix as for example `54,546 -> 54654` and `54,543 -> 54543` you can probably encounter similar case with combinations with next prefix ... – Spektre Mar 21 '21 at 10:32
  • You can't analyze an algorithm complexity for this problem on the only prism of the vector size. The size of integers contained by the vector matter – TUI lover Mar 21 '21 at 11:01

4 Answers4

2

This can be done using std::map


struct classcomp{ // The comparison class that allow std::map to do comparison between keys of type std::string
    bool operator() (const std::string &a, const std::string &b)const {return (a+b>b+a);}
    };
̀
std::string printLargest(std::vector<std::string> &arr) {
    std::map<std::string, unsigned int, classcomp> orderedString;  // a map of the form string : number of occurance in the vector
    for (auto i = arr.begin(); i != arr.end(); i++){  // O(n)
        if (orderedString.count(*i)) orderedString[*i]++;  // O(log(n)) or O(1) depending of the implementation of std::map
        else orderedString.insert(std::pair<std::string, unsigned int>(*i, 1));  // O(log(n)) or O(1) depending of the implementation of std::map
        }
    std::string r="";
    for (auto i = orderedString.begin(); i != orderedString.end(); i++){  //this works since our map container is such that the first element is the highest
        for (unsigned j=0; j < i->second; j++){  //The overall complexity is O(n)
            r+=i->first;
            }
        }
    return r;
    }
̀

The overall complexity is O(mnlog(n)) where m is the maximum length of a string in your vector and n the size of the vector itself

TUI lover
  • 542
  • 4
  • 16
1

Solving this by hand, what I'd do is to take the numbers which start with the highest digits in their sequences, right? So, in other words, what I'd do by hand is to sort them by this criterion and then appending them.
As soon as you are able to describe the criterion, this becomes nothing more than a sorting algorithm, with the criterion as a custom comparator.

So basically, in the end, the code can look somewhat like:

inline bool better_digits(const string& a, const string& b);

string print_largest(vector<string> data)
{
    std::sort(data.begin(), data.end(), better_digits); // sort
    string result = std::accumulate(data.begin(), data.end(), std::string{}); // append
    return result;
}

In other words, I did the same as you already did, but with a better sorting algorithm, simply trusting that std::sort is efficient (which it is). No need to reinvent the wheel.

Note: the line with std::accumulate requires C++20. Otherwise, simply append by using a loop like you did in your own code.
Also, I removed the reference from the input to avoid the function having a side effect, but if it is allowed to, do that by all means.

The only thing left to do is to define better_digits. For that, we can use what you already did and what TUI lover also used:

inline bool better_digits(const string& a, const string& b)
{
    return a+b > b+a;
}

Note that I haven't compared my variant with that of that of TUI lover. That would prove to be quite interesting. I posted mine because I think it is more readable, but TUI lovers variant might easily be more efficient. (Both are Θ(nlogn), but the overall factor also matters.)

Aziuth
  • 3,652
  • 3
  • 18
  • 36
  • have you tried the `54,546 -> 54654` vs. `54,543 -> 54543` case ? – Spektre Mar 21 '21 at 12:28
  • @Spektre Shouldn't that automatically be done by `a+b > b+a`? Thus already been done by the code in the question? – Aziuth Mar 21 '21 at 12:31
  • Didnt test your solution so I do not know for sure ... it is just test case which breaks naive sorting based solutions... there might be also consequent prefix issues (if exist) which your solution will not handle at all – Spektre Mar 21 '21 at 12:35
  • @Spektre Pretty sure that this specific case is handled. But can you elaborate on what you mean by "consequent prefix issues"? You mean something that we have three values and our greedy approach would work with two but breaks with the third? – Aziuth Mar 21 '21 at 12:38
  • Not really ... its just a fealing that there might be combinations of inputs that by choosing one of the number by greedy algo from more of the same prefixes will cause wrong result with combination of number with different prefix ... But cant think of any such input ... not even sure of exist. If not then your solution is fine – Spektre Mar 21 '21 at 12:40
  • @Spektre Hmm. I think what we'd need to do to prove it (or disprove it) is to show that the comparison operator is transitive. I think then the overall proof would follow by induction. I'll think about that, but yeah, not trivial. – Aziuth Mar 21 '21 at 12:49
  • OK I got the correct heuristics (I think)... 1. sort the array descending 2. if the same prefix encountered like `99,99x???? .... , y???` then chose `99` if `y>x` ... if `y – Spektre Mar 21 '21 at 12:49
  • @Spektre Why do you initially sort it descending? In the end we will undo that anyhow. Note that 11111111111 > 9. On the other hand, 111111111119 < 911111111111. Or did you mean something different with descending? – Aziuth Mar 21 '21 at 12:57
  • but I still got feeling it will need some tweaking for the same prefixes and multiple lenghts like `99,99x,99x,99xxx,,99xxx,...` ... Descendingly sorted array is almost the same as solution as we want largest value... only the prefixes needs fixing – Spektre Mar 21 '21 at 12:57
  • @Spektre Having done some searches, I came to https://stackoverflow.com/a/34633398/5114342 . Basically everything to do to prove this overall is to show that the custom operator defines a total order, in particular is transitive. I think this can be done by `a+b > b+a <-> exists i:(b+c)[i] > (c+b)[i], (b+c)[j] = (c+b)[j], forall j < i`, and then do this for a chain of three values. But frankly I don't want to execute this further, especially not on a sunday. – Aziuth Mar 21 '21 at 13:17
  • 1
    I used map since I don’t fully trust the std::sort. Still I think it is highly probable that your solution is faster in the end – TUI lover Mar 21 '21 at 16:01
  • 1
    @TUIlover Might even be compiler-dependend, or dependend on the STL version. – Aziuth Mar 21 '21 at 18:12
1

Here is an algorithm in O(ns) where n is the length of the array and s the maximum length of the strings. It uses the trie method. Instead of padding with spaces or zeros, it pads with fake numbers.

If the numbers 544, 54 are present in a group then 54 is equivalent to 545, and should go in front (we pad 54 with a fake 5 in its last digit).

To compare [5, 554, 45],

  • first round (most significant digit), splits it into [5, 554], [45]
  • second round [5, 554], [45]
  • third round [5], [554], [45] (because 5 are padded with fake 5s)

    
    def pad_with_fake(L):
        outL = []
        for x in L:
            while len(str(x)) < maxlen:
                lead = x[0]
                if lead not in fake_digits:
                    x = x + fake_digits[int(lead)]
                else:
                    x = x + lead
            outL.append(x)
        return outL
    
    def arrange_number_at_digit_position(pos,inL):
        outL = []
        for digit in digits:
            subL = []
            for x in inL:
                if str(x)[pos] == digit:
                    subL.append(x)
            if subL != []:
                outL.append(subL)
        return outL
            
    def arrange(inL):
        maxlen = max([len(l) for l in inL])
        i = 0
        outLs = [[inL]]
        while i < maxlen:
            outL = []
            for l in outLs[i]:
                outL = outL + (arrange_number_at_digit_position(i,l))
            outLs = outLs + [outL]
            i = i+1
        return outLs
    
    def main():
        inL = [559, 5, 55, 59, 549, 544, 54]
        L = [str(l) for l in inL]
        digits = [0,1,2,3,4,5,6,7,8,9]
        fake_digits = ['a','b','c','d','e','f','g','h','i','j']
        digits = ['9','j','8','i','7','h','6','g','5','f','4','e','3','d','2','c','1','b','a','0']
        L = pad_with_fake(L)
        outLs = arrange(L)
        for l in outLs:
            print(l)
        final_string = ""
        for l in outLs[-1]:
            final_string = final_string + "|" + l[0]
        for i in range(0,10):
            final_string = final_string.replace(fake_digits[i],"")
        print("input", inL, "--> output", final_string)
    main()

Example

[['559', '5ff', '55f', '59f', '549', '544', '54f']]
[['559', '5ff', '55f', '59f', '549', '544', '54f']]
[['59f'], ['559', '55f'], ['5ff'], ['549', '544', '54f']]
[['59f'], ['559'], ['55f'], ['5ff'], ['549'], ['54f'], ['544']]
input [559, 5, 55, 59, 549, 544, 54] --> output |59|559|55|5|549|54|544
sfx
  • 103
  • 9
0

You can try to use a faster sorting algorithm like merge sort which is O(n log n).

Alternatively, by using a trie this problem could be solved in O(s), where s is the sum of character counts across all strings.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Paras Gupta
  • 174
  • 4
  • 1
    I do not think `O(n)` is possible ... simple sorting will not lead to valid answers `O(n.log(n))` might be possible however I see it more like `O(m^2 + n.log(n))` where `m < n` ... and or a lot of heuristics – Spektre Mar 21 '21 at 10:33
  • you can try to insert all strings in a trie, and do a tree traversal. Insertion will take number of characters across all strings. Traversal will take number of nodes in trie. It will give O(n) complexity. – Paras Gupta Mar 21 '21 at 10:36
  • That will enhance only the sorting but that is just a "minor" part of the problem – Spektre Mar 21 '21 at 10:37
  • 1
    can you elaborate more on the `O(n)` solution using trie? what exactly will be the trie topology? and how would you obtain answer from it? have you account also the trie creation into complexity (as this is online judge it will be measured along with the rest of code too)? – Spektre Mar 21 '21 at 10:48