input: Write a function to find the optimal solution to solve a knapsack problem using dynamic programming. The function should return the maximum value that can be obtained with the given items and capacity. If the list of items or the capacity is empty, return 0.
  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))
  
  # Testing the function
  items = [(60, 10), (100, 20), (120, 30)]
  values = [x[0] for x in items]
  weights = [x[1] for x in items]
  capacity = 50
  n = len(items)
  
  assert knapSack(capacity, weights, values, n) == 220
  
  items = []
  values = [x[0] for x in items]
  weights = [x[1] for x in items]
  capacity = 50
  n = len(items)
  
  assert knapSack(capacity, weights, values, n) == 0
  
  items = [(10, 5), (40, 4), (30, 6)]
  values = [x[0] for x in items]
  weights = [x[1] for x in items]
  capacity = 10
  n = len(items)
  
  assert knapSack(capacity, weights, values, n) == 50
  
  items = [(10, 5)]
  values = [x[0] for x in items]
  weights = [x[1] for x in items]
  capacity = 3
  n = len(items)
  
  assert knapSack(capacity, weights, values, n) == 0
output: def knapSack(W, wt, val, n):
      dp = [[0 for _ in range(W + 1)] for _ in range(n + 1)]
      
      for i in range(1, n + 1):
          for w in range(1, W + 1):
              if wt[i-1] <= w:
                  dp[i][w] = max(val[i-1] + dp[i-1][w-wt[i-1]], dp[i-1][w])
              else:
                  dp[i][w] = dp[i-1][w]
      
      return dp[n][W]