1

I have to find the largest sum down a triangle of numbers where 1 is the first row, the second row is 3,2, the third is 4,5,6 and so on for n rows where every other row is reversed. The path only goes from top to bottom and does not go sideways or up. The program must sum across the triangle. My code for this problem works effectively for small numbers, like up to 2000, but takes much too long for anything larger. The program has to work for n = 9,999. My approach was to put each row into a list of lists, called pyramid_list, and then as long as the list is not as long as the final row, append zeroes onto the end, and the sum the first row in pyramid_list with the numbers under it.

E.g. for n = 3, pyramid_list = [[1,0,0], [3,2,0], [4,5,6]]

So the implementation of the addition would look like:

100
320
456

becomes:

430
456

which finally becomes:

8, 9, 9

and the program would output 9. Each number in the above can only be added to the one directly under it and the one under it to the right.

Here is my code:

# get user input for number of rows 
rows = int(input("Enter number of rows: "))
# define number so starting row is always 1
number = 1
# create rows_list
rows_list = []
# total pyramid
pyramid_list = []

if rows == 1:
  print(1)
else:
  
  # making a list for each row, then appending each row to one big list
  for i in range(1, rows+1):
    if i % 2 == 0:
      for j in range(1, i+1):
          rows_list.append(number)
          number += 1
      pyramid_list.append((list(reversed(rows_list))))
      rows_list = []
    else:
      for j in range(1, i+1):
        rows_list.append(number) 
        number += 1
    if rows_list != []:
      pyramid_list.append(rows_list)
    rows_list = [] 
  
  # filling all empty spaces in a row with zeroes
  for i in pyramid_list:
    while len(i) < rows:
      i.append(0)
    
  
  final = False
  while final == False:
    # creating a list for the modified row
    current_step = []
    # where j is the index of each number in the list
    for j in range(rows):
      if j == 0:
        # add first item from current row and the one under it
        current_step.append((pyramid_list[0][j]) + pyramid_list[1][j])
      # If the current item in the list is not the last one before all the zeroes, find the max addition of it and the 2 items below it
      if pyramid_list[0][j+1] != 0:
        current_step.append(max(pyramid_list[0][j] + pyramid_list[1][j+1], pyramid_list[0][j+1] + pyramid_list[1][j + 1]))
        # For end of row (diagonal path on the right), add the number on the bottom right
      if pyramid_list[0][j+1] == 0:
        current_step.append(pyramid_list[0][j] + pyramid_list[1][j+1])
        # Adding zeroes to the final result of that row, take out the two rows that have just been added together, and put the current step onto the front of the list
        while len(current_step) < rows:
          current_step.append(0)
        pyramid_list.remove(pyramid_list[0])
        pyramid_list.remove(pyramid_list[0])
        pyramid_list.insert(0, current_step)
        # If the addition is done, stop the loop
        if len(pyramid_list) == 1:
          final = True
        break

# Printing the greatest number from the final step
  print(max(pyramid_list[0]))

I know that the problem is that the program keeps recomputing numbers it's already computed, but I'm not sure how to change it.

martineau
  • 119,623
  • 25
  • 170
  • 301
Veronika
  • 29
  • 1

1 Answers1

0

Your current solution is O(n^3) in time complexity, so for n=10000, it would require time proportional to 10^12, while space complexity is O(n^2).

Some ways to optimize your existing solution while keeping everything as it is -

  • You are currently padding the pyramid triangle with zeroes, which is not really needed, if you store the pyramid data without padded zeroes, you can cut the space required to store pyramid_list by half.
  • In python, the list.remove() method is O(n), so your solution is actually O(n^3), we can do away without using list.remove() and reduce the time complexity to O(n^2).

But even with time and space complexity being O(n^2), it might still take a long time to solve for n=10000.

Here's a simplified version of your approach -

rows = 10

#     1
#    3 2
#   4 5 6
# 10 9 8 7

# generate pyramid e.g. for rows=4 -> [[1], [3, 2], [4, 5, 6], [10, 9, 8, 7]]
pyramid = []
start = 1
for x in range(1, rows + 1):
    if x % 2:
        pyramid.append(list(range(start, start+x)))
    else:
        pyramid.append(list(range(start + x - 1, start - 1, -1)))
    start += x

prev_sum = pyramid[0]
for x in range(1, rows):
    current_row = pyramid[x]
    new_sum = [0]*(x+1)
    for i in range(1, len(current_row) - 1):
        new_sum[i] = max(current_row[i] + prev_sum[i], current_row[i] + prev_sum[i-1])
    # update first and last element
    new_sum[0] = prev_sum[0] + current_row[0]
    new_sum[-1] = prev_sum[-1] + current_row[-1]
    prev_sum = new_sum

print(max(prev_sum))

The above code takes ~35 secs to calculate the answer for n=10000, which is quite long.


Can we do better? (O(n) Solution)

We already know all the numbers in the pyramid in advance. The last row in the pyramid is always full. If we print the prev_sum in the first solution we can see the following pattern -

[1]
[4, 3]
[8, 9, 9]
[18, 18, 17, 16]
[29, 30, 31, 31, 31]
[50, 50, 50, 49, 48, 47]
[72, 73, 74, 75, 75, 75, 75]
[108, 108, 108, 108, 107, 106, 105, 104]
[145, 146, 147, 148, 149, 149, 149, 149, 149]
[200, 200, 200, 200, 200, 199, 198, 197, 196, 195]
[256, 257, 258, 259, 260, 261, 261, 261, 261, 261, 261]
[334, 334, 334, 334, 334, 334, 333, 332, 331, 330, 329, 328]
[413, 414, 415, 416, 417, 418, 419, 419, 419, 419, 419, 419, 419]
[518, 518, 518, 518, 518, 518, 518, 517, 516, 515, 514, 513, 512, 511]
[624, 625, 626, 627, 628, 629, 630, 631, 631, 631, 631, 631, 631, 631, 631]
[760, 760, 760, 760, 760, 760, 760, 760, 759, 758, 757, 756, 755, 754, 753, 752]
[897, 898, 899, 900, 901, 902, 903, 904, 905, 905, 905, 905, 905, 905, 905, 905, 905]
[1068, 1068, 1068, 1068, 1068, 1068, 1068, 1068, 1068, 1067, 1066, 1065, 1064, 1063, 1062, 1061, 1060, 1059]
[1240, 1241, 1242, 1243, 1244, 1245, 1246, 1247, 1248, 1249, 1249, 1249, 1249, 1249, 1249, 1249, 1249, 1249, 1249]
[1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1449, 1448, 1447, 1446, 1445, 1444, 1443, 1442, 1441, 1440]

For each row, the max sum is either the first element in the list(sum of left edge) or the last element in the list(sum of right edge). Also, note the pattern in the difference between first and last element in the list and its increase as value of rows increase. - I will leave proof for this to the OP.

So, we can directly sum up right and left edges of the pyramid and whatever is bigger would be the answer. Here's a corresponding code -

rows = 1000000
start = 1
edge_sum = [0, 0]
for x in range(1, rows + 1):
    if x % 2:
        edge_sum[0] += start
        edge_sum[1] += start + x - 1
    else:
        edge_sum[0] += start + x - 1
        edge_sum[1] += start
    start += x

print(max(edge_sum))

If we print the edge_sum in the loop, we can see the corresponding first and last element in the prev_sum list of the first solution.

The second solution can calculate the answer for n=10^6 in under 1 sec on the same cpu.


Can we do even better? (O(1))

We should be able to calculate the sum of left and right edges using a mathetical formula for a given n. The second solution can help with getting the first and last element of each row of the pyramid, how can we sum it up efficiently? - will leave it to OP

Jay
  • 2,431
  • 1
  • 10
  • 21