public class Binomial {


	/*This is correct in theory, but for all but small n and r,
	  n! and r! very quickly overflow the size of a long,
	  let alone int. 
	*/
	public static int impractical(int n, int r) {
		
		return factorial(n)/(factorial(n-r)*factorial(r));
	}

	//Can make this iterative but it fits with the theme
	private static int factorial(int n) {
		if(n<=1)
			return 1;
		else
			return n*factorial(n-1);
	}

	/*Here's a recursive idea: 
	Suppose I am one of the n. You can either pick me or not.
	If you decide you like me, there is one way to pick me and now
	your task reduces to choosing r-1 from n-1. If you don't pick 
	me, then you'd still need to pick r people from n-1 people, since
	you've dismissed me (yeeted me from consideration, in modern parlance)
	
	Base cases are that there is only one way to choose no people or 
	n people from n.

	Mathematically, binom(n,r) = 1                               if r=0 or r=n
				     binom(n-1, r-1) + binom(n, r-1) otherwise

	*/

	public static int naiveRecursive(int n, int r) {

		if(r==0 || r==n)
			return 1;
		else
			return naiveRecursive(n-1, r-1) +
			       naiveRecursive(n-1, r);
	}


	/*While mathematically sound, this is very slow = exponential time.
	 Exponential time is really bad. We need to solve this in
	 polynomial time. 
	 Suppose we want to find binom(6,4). We get the tree:
			
			        (6, 4)
	     (5, 3)                                 (5, 4)

    (4,2)          (4,3)                       (4,3)       (4,4)=1
(3,1)    (3,2)    (3,2)  (3,3)=1

          Notice that we try to calculate (4,3), and (3,1) more than once.
	  In a general setup, this can happen a lot. 
	  Solution is to build the solution bottom up.
	*/


	public static int efficient(int n, int r) {

		int[][] binom = new int[n+1][];
		for(int row=0; row<n; row++)
			binom[row] = new int[row+1];
		binom[n] = new int[r+1];
		

		for(int row=0; row<=n; row++) 
			binom[row][0] = 1; //binom(i,0) = 1 for all i
		
		for(int row=0; row<=n; row++) {
			for(int col = 1; col<=(row==n?r:row); col++) {
				if(col==row) //binom(i,i) = 1
				   binom[row][col]=1;
				else 
				   binom[row][col] = binom[row-1][col-1] +
						  binom[row-1][col];
			}
		}

		return binom[n][r];
	/*This is much faster. It's appoximately (n+1)(n+2)/2 ish steps.
	  If the last row has n+1 columns, then it's exactly the number of
	  steps. If last row stops at r like I did, it's slightly less,
	  but if n is large, these small differences matter little. 
	  We say that this algorithm runs in time O(n^2).
	*/  
	}

	public static void main(String[] args) {
	  for(int i=0; i<=10; i++)
	    for(int j=0; j<=i; j++)
	      System.out.println("("+i+","+j+")"+efficient(i,j));
	}
}

				 
