Problem of The Day: Count Number of Teams
Problem Statement

Brute Force - TLE
class Solution:
    def dfs(self, i, curr, rating, visited, desc=False):
        if len(curr) == 3 and curr not in visited:
            visited.append(curr[:])
            return 1
        if i >= len(rating):
            return 0
        res = 0
        for j in range(i, len(rating)):
            if desc:
                if rating[j] < curr[-1]:
                    res += self.dfs(j + 1, curr + [rating[j]], rating, visited, desc=True)
            else:
                if rating[j] > curr[-1]:
                    res += self.dfs(j + 1, curr + [rating[j]], rating, visited)
        return res
    def numTeams(self, rating: List[int]) -> int:
        N = len(rating)
        res = 0
        visited = []
        for i in range(N):
            res += (self.dfs(i, [rating[i]], rating, visited, desc=True) + self.dfs(i, [rating[i]], rating, visited))
        return res
Improved Algorithm
Inspired from the Editorial solution, the idea is to use cache to remove the redundant computation for overlapped sub-solution.
class Solution:
    def dfs(self, i, curr, rating, visited, size, desc=False):
        if size == 3:
            return 1
        if i >= len(rating):
            return 0
        if visited[i][size] != -1:
            return visited[i][size]
        res = 0
        for j in range(i, len(rating)):
            if desc:
                if rating[j] < curr[-1]:
                    res += self.dfs(j + 1, curr + [rating[j]], rating, visited,size + 1, desc=True)
            else:
                if rating[j] > curr[-1]:
                    res += self.dfs(j + 1, curr + [rating[j]], rating, visited, size + 1)
        visited[i][size] = res
        return res
    def numTeams(self, rating: List[int]) -> int:
        N = len(rating)
        res = 0
        asc_cache = [[-1] * 4 for _ in range(N)]
        desc_cache = [[-1] * 4 for _ in range(N)]
        for i in range(N):
            res += (self.dfs(i, [rating[i]], rating, desc_cache, 1, desc=True) + self.dfs(i, [rating[i]], rating, asc_cache, 1))
        return res
Editorial
Approach 1: Dynamic Programming (Memoization)
class Solution:
    def numTeams(self, rating: List[int]) -> int:
        n = len(rating)
        teams = 0
        increasing_cache = [[-1] * 4 for _ in range(n)]
        decreasing_cache = [[-1] * 4 for _ in range(n)]
        # Calculate total teams by considering each soldier as a starting point
        for start_index in range(n):
            teams += self._count_increasing_teams(
                rating, start_index, 1, increasing_cache
            ) + self._count_decreasing_teams(
                rating, start_index, 1, decreasing_cache
            )
        return teams
    def _count_increasing_teams(
        self,
        rating: List[int],
        current_index: int,
        team_size: int,
        increasing_cache: List[List[int]],
    ) -> int:
        n = len(rating)
        # Base case: reached end of array
        if current_index == n:
            return 0
        # Base case: found a valid team of size 3
        if team_size == 3:
            return 1
        # Return cached result if available
        if increasing_cache[current_index][team_size] != -1:
            return increasing_cache[current_index][team_size]
        valid_teams = 0
        # Recursively count teams with increasing ratings
        for next_index in range(current_index + 1, n):
            if rating[next_index] > rating[current_index]:
                valid_teams += self._count_increasing_teams(
                    rating, next_index, team_size + 1, increasing_cache
                )
        # Cache and return the result
        increasing_cache[current_index][team_size] = valid_teams
        return valid_teams
    def _count_decreasing_teams(
        self,
        rating: List[int],
        current_index: int,
        team_size: int,
        decreasing_cache: List[List[int]],
    ) -> int:
        n = len(rating)
        # Base case: reached end of array
        if current_index == n:
            return 0
        # Base case: found a valid team of size 3
        if team_size == 3:
            return 1
        # Return cached result if available
        if decreasing_cache[current_index][team_size] != -1:
            return decreasing_cache[current_index][team_size]
        valid_teams = 0
        # Recursively count teams with decreasing ratings
        for next_index in range(current_index + 1, n):
            if rating[next_index] < rating[current_index]:
                valid_teams += self._count_decreasing_teams(
                    rating, next_index, team_size + 1, decreasing_cache
                )
        # Cache and return the result
        decreasing_cache[current_index][team_size] = valid_teams
        return valid_teams
Approach 2: Dynamic Programming (Tabulation)
class Solution:
    def numTeams(self, rating: List[int]) -> int:
        n = len(rating)
        teams = 0
        # Tables for increasing and decreasing sequences
        increasing_teams = [[0] * 4 for _ in range(n)]
        decreasing_teams = [[0] * 4 for _ in range(n)]
        # Fill the base cases. (Each soldier is a sequence of length 1)
        for i in range(n):
            increasing_teams[i][1] = 1
            decreasing_teams[i][1] = 1
        # Fill the tables
        for count in range(2, 4):
            for i in range(n):
                for j in range(i + 1, n):
                    if rating[j] > rating[i]:
                        increasing_teams[j][count] += increasing_teams[i][
                            count - 1
                        ]
                    if rating[j] < rating[i]:
                        decreasing_teams[j][count] += decreasing_teams[i][
                            count - 1
                        ]
        # Sum up the results (All sequences of length 3)
        for i in range(n):
            teams += increasing_teams[i][3] + decreasing_teams[i][3]
        return teams