3 minute read

Problem Statement

problem-1395

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