input: 
  def optimal_knapsack_solution(items: List[Tuple[int, int]], capacity: int) -> int:
      """
      Find the optimal solution to a 0-1 knapsack problem using dynamic programming.
      >>> optimal_knapsack_solution([(60, 10), (100, 20), (120, 30)], 50)
      220
      """
      
      # Function to return the maximum value that can be put in a knapsack of given capacity
      def knapSack(W, wt, val, n):
          # Base Case
          if n == 0 or W == 0:
              return 0
          
          # If weight of the nth item is more than Knapsack capacity W, then
          # this item cannot be included in the optimal solution
          if (wt[n-1] > W):
              return knapSack(W, wt, val, n-1)
          
          # return the maximum of two cases:
          # (1) nth item included
          # (2) not included
          else:
              return max(val[n-1] + knapSack(W-wt[n-1], wt, val, n-1),
                         knapSack(W, wt, val, n-1))
      
      val = [item[0] for item in items]
      wt = [item[1] for item in items]
      n = len(val)
      return knapSack(capacity, wt, val, n)
  
output:     
      # Function to return the maximum value that can be put in a knapsack of given capacity
      def knapSack(W, wt, val, n):
          # Base Case
          if n == 0 or W == 0:
              return 0
          
          # If weight of the nth item is more than Knapsack capacity W, then
          # this item cannot be included in the optimal solution
          if (wt[n-1] > W):
              return knapSack(W, wt, val, n-1)
          
          # return the maximum of two cases:
          # (1) nth item included
          # (2) not included
          else:
              return max(val[n-1] + knapSack(W-wt[n-1], wt, val, n-1),
                         knapSack(W, wt, val, n-1))
      
      val = [item[0] for item in items]
      wt = [item[1] for item in items]
      n = len(val)
      return knapSack(capacity, wt, val, n)
  