简体   繁体   中英

Is the runtime of my code O(n) or O(n * m)?

 public List<List<Integer>> groupThePeople(int[] groupSizes) {
        List<List<Integer>> groups = new ArrayList<List<Integer>>();
        for(int i = 0; i < groupSizes.length; i++) {
            boolean added = false;
            int index = 0;
            
            while(index < groups.size() && !added) {
                List<Integer> temp = groups.get(index);
                int size = groupSizes[temp.get(0)];
                if(size == groupSizes[i] && temp.size() < groupSizes[i]) {
                    temp.add(i);
                    added = true;
                }
                else {
                    index++;
                }
            }
            
            if(!added) {
                ArrayList<Integer> temp = new ArrayList<Integer>();
                temp.add(i);
                groups.add(temp);
            }
        }
        return groups;
    }

I am thinking that this code is running at O(n) because I only iterate through the array once, but can it also be O(n * m) because of the inner loop?

It seems that in best case the complexity may be O(N) - when the input array groupSizes contains N identical elements not less than N like {5, 5, 5, 5, 5}, {10, 10,... 10} (10 elements), etc. .

If all N elements are different, the complexity is O(N^2) (actually, ~N^2/2 ) - this is the worst case which is also possible when groupSize contains N zeros or ones (N > 1).

Update
This code may be refactored to provide O(N) complexity with the help of using a map to store the numbers in the input array as keys and indexes in the input array collected into lists (2D lists) as values.

It's better to use LinkedHashMap because it provides O(1) for insertion and get operations and maintains the order of insertion.

public static List<List<Integer>> groupThePeople(int... groupSizes) {
    // System.out.println("plain: " + Arrays.toString(groupSizes));
    Map<Integer, List<List<Integer>>> map = new LinkedHashMap<>();
    
    for (int i = 0; i < groupSizes.length; i++) {
        // create a 2D list as a default value for the given map
        List<List<Integer>> value = map.computeIfAbsent(groupSizes[i], k -> new ArrayList<>(Arrays.asList(new ArrayList<Integer>())));

        // new list needed if no size left in the last available inner list
        if (!value.get(value.size() - 1).isEmpty() && groupSizes[i] <= value.get(value.size() - 1).size()) {
            value.add(new ArrayList<>());
        }

        // add index to the inner list
        value.get(value.size() - 1).add(i);
    }

    return map.values()  // convert Collection<List<List<Integer>>>
              .stream()
              .flatMap(x -> x.stream())
              .collect(Collectors.toList());
}

Test

groupThePeople(0, 1, -1, -1, 3, 3, 3, 1, 2, 3, 0).forEach(System.out::println);
    
groupThePeople(10, 10, 10, 10, 10, 10, 10, 10, 10, 10).forEach(System.out::println); // all numbers the same
    
groupThePeople(10, 15, 15, 15, 10, 10, 10, 10, 10, 10).forEach(System.out::println); // without splitting nested lists

Output:

plain: [0, 1, -1, -1, 3, 3, 3, 1, 2, 3, 0]
[0]
[10]
[1]
[7]
[2]
[3]
[4, 5, 6]
[9]
[8]

plain: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

plain: [10, 15, 15, 15, 10, 10, 10, 10, 10, 10]
[0, 4, 5, 6, 7, 8, 9]
[1, 2, 3]

Similar approach may be implemented using Stream API, though it seems more verbose.

Also, this implementation is two-pass, the second pass is needed to split the inner lists of indexes into sublists with the size less than the key number.

public static List<List<Integer>> groupThePeopleStream(int... groupSizes) {
    
    // System.out.println("stream: " + Arrays.toString(groupSizes));
    
    return IntStream
            .range(0, groupSizes.length)
            .mapToObj(i -> Map.entry(groupSizes[i], i)) // entry: number -> index
            .collect(Collectors.groupingBy(Map.Entry::getKey, LinkedHashMap::new, // convert to map: number -> list of indexes
                        Collectors.mapping(Map.Entry::getValue, Collectors.toList())))
            .entrySet()
            .stream()
            //.peek(e -> System.out.println(e.getKey() + ":"))
            .flatMap(e -> e.getKey() >= e.getValue().size() // need to split list of indexes?
                    ? Stream.of(e.getValue())       // no, store a single index
                    : IntStream.iterate(0, x -> x < e.getValue().size(), x -> x + Math.max(1, e.getKey())) // yes, preparing indexes: 0, N, 2N..
                               .mapToObj(x -> e.getValue().subList(x, Math.min(x + Math.max(1, e.getKey()), e.getValue().size())))
            )
            //.peek(x -> System.out.println("\t" + x))
            .collect(Collectors.toList());
}

Output for the same test data (including debug prints):

stream: [0, 1, -1, -1, 3, 3, 3, 1, 2, 3, 0]
0:
    [0]
    [10]
1:
    [1]
    [7]
-1:
    [2]
    [3]
3:
    [4, 5, 6]
    [9]
2:
    [8]

stream: [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
10:
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

stream: [10, 15, 15, 15, 10, 10, 10, 10, 10, 10]
10:
    [0, 4, 5, 6, 7, 8, 9]
15:
    [1, 2, 3]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM