|
1 | 1 | # [Problem 1395: Count Number of Teams](https://leetcode.com/problems/count-number-of-teams/description/?envType=daily-question)
|
2 | 2 |
|
3 | 3 | ## Initial thoughts (stream-of-consciousness)
|
| 4 | +- Enumerating every *possible* team will take $O(n^3)$ time. That seems long. |
| 5 | +- I think we could do this using two sets of queues (BFS) or stacks (DFS)-- but let's try queues: |
| 6 | + - Initialize `nTeams = 0`. |
| 7 | + - One queue stores *increasing* teams and the other stores *decreasing* teams |
| 8 | + - First, enqueue each soldier in both queues as a single item list of indices |
| 9 | + - Next, for each queue type, loop until the queue is empty: |
| 10 | + - Dequeue the next list (at the front of the queue). Let's say the last item in the list has index $i$. |
| 11 | + - For indices $i + 1$ to the end of the list of soldiers, add them to the teams if they satisfy the correct ordering. E.g., for the increasing queue, ensure that the next item (at index $j$) is greater than the current last item at index $i$. If the new team has length 3, increment `nTeams`. Otherwise enqueue any newly formed "valid" teams. |
| 12 | + - Finally, return `nTeams`. |
4 | 13 |
|
5 | 14 | ## Refining the problem, round 2 thoughts
|
| 15 | +- Any edge cases to consider? |
| 16 | + - If there are fewer than 3 soldiers, no teams are possible...but we're given that $3 \leq n \leq 1000$, so we don't need to consider this case |
| 17 | + - Strange `rating` values? We're given that `ratings` are all integers. |
| 18 | + - We're also told that the integers in `ratings` are unique-- we could probably leverage this to implement some sort of sorting-based solution 🤔...but let's see if the queue idea works first. |
| 19 | +- I think we can try this... |
6 | 20 |
|
7 | 21 | ## Attempted solution(s)
|
8 | 22 | ```python
|
9 |
| -class Solution: # paste your code here! |
10 |
| - ... |
| 23 | +from collections import deque |
| 24 | + |
| 25 | +class Solution: |
| 26 | + def numTeams(self, rating: List[int]) -> int: |
| 27 | + nTeams = 0 |
| 28 | + increasing_queue = deque([[i] for i in range(len(rating))]) |
| 29 | + decreasing_queue = increasing_queue.copy() |
| 30 | + |
| 31 | + # look for increasing teams (where rating[i] < rating[j] < rating[k]) |
| 32 | + while len(increasing_queue) > 0: |
| 33 | + next_team = increasing_queue.popleft() |
| 34 | + for i in range(next_team[-1], len(rating)): |
| 35 | + if rating[i] > rating[next_team[-1]]: |
| 36 | + if len(next_team) == 2: |
| 37 | + nTeams += 1 |
| 38 | + else: |
| 39 | + new_team = next_team.copy() |
| 40 | + new_team.append(i) |
| 41 | + increasing_queue.append(new_team) |
| 42 | + |
| 43 | + # now look for decreasing teams (where rating[i] > rating[j] > rating[k]) |
| 44 | + while len(decreasing_queue) > 0: |
| 45 | + next_team = decreasing_queue.popleft() |
| 46 | + for i in range(next_team[-1], len(rating)): |
| 47 | + if rating[i] < rating[next_team[-1]]: |
| 48 | + if len(next_team) == 2: |
| 49 | + nTeams += 1 |
| 50 | + else: |
| 51 | + new_team = next_team.copy() |
| 52 | + new_team.append(i) |
| 53 | + decreasing_queue.append(new_team) |
| 54 | + |
| 55 | + return nTeams |
| 56 | +``` |
| 57 | +- Given test cases pass |
| 58 | +- New test cases: |
| 59 | + - `rating = [4, 7, 1, 9, 10, 14, 3, 29, 1000, 950, 26, 44]`: pass |
| 60 | + - `rating = [62, 2, 35, 32, 4, 75, 48, 38, 28, 92, 8, 100, 68, 95, 63, 40, 42, 21, 47, 43, 89, 79, 14, 58, 85, 80, 15, 41, 10, 37, 30, 31, 24, 1, 23, 45, 53, 83, 65, 3, 49, 66, 6, 54, 34, 72, 29, 71, 52, 81, 98, 82, 18, 36, 88, 20, 12, 99, 22, 77, 97, 60, 17, 61, 27, 16, 76, 33, 69, 51, 19, 25, 46, 39, 74, 94, 67, 55, 96, 90, 93, 64, 26, 73, 87, 91, 78, 11, 5, 9, 57, 56, 50, 70, 44, 84, 86, 59, 13, 7]`: pass (note: this is a random permutation of the numbers 1...100) |
| 61 | +- Seems ok; submitting... |
| 62 | + |
| 63 | + |
| 64 | + |
| 65 | +Uh oh...time limit exceeded! So: the algorithm seems correct (and even for this "failed" test case we get the right answer if I run it through "use as test case"). But this algorithm is still $O(n^3)$, which is apparently not good enough. Let's go back to the drawing board... |
| 66 | + |
| 67 | +## Revised thoughts (back to stream of consciousness...) |
| 68 | +- I wonder if the sorting idea might work...or...hmmm... 🤔 |
| 69 | +- The algorithm I implemented also requires us to re-compare elements several times. E.g., suppose we're considering building a team that includes index $i$. If we want to build an increasing team, then *any* item from index $0...(i - 1)$ that is less than `ratings[i]` could serve as the first element, and any item from index $(i + 1)...n$ that is greater than `ratings[i]` could serve as the third element. So if there are $a$ elements less than `ratings[i]` with indices less than `i` and $b$ elements greater than `ratings[i]` with indices greater than `i`, the total number of increasing teams is $ab$. We can use an analogous approach to track the number of decreasing teams (just flipping the greater than/less than signs). |
| 70 | +- Let's try something like: |
| 71 | + - For each element in turn (at index `i`): |
| 72 | + - track how many items to the left are bigger/smaller, and how many items to the right are bigger/smaller |
| 73 | + - the number of increasing teams we can make with element `i` as the second team member is `left_smaller * right_bigger + left_bigger * right_smaller` |
| 74 | + |
| 75 | +New solution: |
| 76 | +```python |
| 77 | +class Solution: |
| 78 | + def numTeams(self, rating: List[int]) -> int: |
| 79 | + nTeams = 0 |
| 80 | + for i in range(len(rating)): |
| 81 | + left_smaller, left_bigger, right_smaller, right_bigger = 0, 0, 0, 0 |
| 82 | + for j in range(i): # j is to the left of i |
| 83 | + if rating[i] > rating[j]: |
| 84 | + left_smaller += 1 |
| 85 | + else: |
| 86 | + left_bigger += 1 |
| 87 | + |
| 88 | + for j in range(i + 1, len(rating)): # j is to the right of i |
| 89 | + if rating[i] > rating[j]: |
| 90 | + right_smaller += 1 |
| 91 | + else: |
| 92 | + right_bigger += 1 |
| 93 | + |
| 94 | + nTeams += left_smaller * right_bigger + left_bigger * right_smaller |
| 95 | + |
| 96 | + return nTeams |
11 | 97 | ```
|
| 98 | +- Ok: all of the test cases, including the ones that exceeded the time limit previously are now passing |
| 99 | +- With this algorithm I think we've gotten the runtime down to $O(n^2)$ (for filling in `left_smaller`, `left_bigger`, `right_smaller`, and `right_bigger` inside the main loop; the lefts and rights each take $O(n)$ steps to fill in, and those loops run for each element in turn (of which there are $n$). |
| 100 | +- Submitting... |
| 101 | + |
| 102 | + |
| 103 | + |
| 104 | +🎉! |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | + |
| 109 | + |
| 110 | + |
0 commit comments