Finding all paths climbing stairs in one or two steps.

This is a kind of follow up of question 70. CLimbing Stairs from Leetcode, In that question, you are asked to calc HOW MANY unique paths climbing a stairway, and you are allowed to climb one stair or  two stairs at a time. 


Reference Solutions

Most solutions of that question in the discussion area is based on Fibonacci series. They use the formula f(n) = f(n -1) + f(n - 2) to calc the ways of climbing stairways. 

An example solution using Fibonacci series without Memorization:

public int fibRecur(int x) {
	if (x == 0)
	<span style="white-space:pre">	</span>return 0;
	if (x == 1)
	<span style="white-space:pre">	</span>return 1;
<span style="white-space:pre">	</span>else {
	<span style="white-space:pre">	</span>int f = fibRecur(x - 1) + fibRecur(x - 2);
		return f;
	}
	}

This solution has O(2^n) time complexity, and O(1) space complexity. Since fibRecur splits into to new function calls before reaching root elements, while it does not need any extra space. 

An example solution using Fibonacci series with Memorization:

public class Solution {

public int climbStairs(int n) {
    if(n == 0 || n == 1 || n == 2){return n;}
    int[] mem = new int[n];
    mem[0] = 1;
    mem[1] = 2;
    for(int i = 2; i < n; i++){
        mem[i] = mem[i-1] + mem[i-2];
    }
    return mem[n-1];
}


This solution uses an array to cache results of from function calls and to avoid unnecessary computation cost. Since computing Fibonacci Series contains many overlapping sub-problems, caching previously computed results and returning cached results (when the same inputs occur again) can save a lot of time. Using an array to store sub-problem results reduces the time complexity from O(2^n) to O(n). However, it does requires more space. Since the size of the array is determined by the input, this solution has space complexity of O(n).


Source:https://leetcode.com/discuss/2809/easy-solutions-for-suggestions


At last, an example with optimized space complexity:

public int climbStairs(int n) {
    // base cases
    if(n <= 0) return 0;
    if(n == 1) return 1;
    if(n == 2) return 2;

    int one_step_before = 2;
    int two_steps_before = 1;
    int all_ways = 0;

    for(int i=2; i<n; i++){
        all_ways = one_step_before + two_steps_before;
        one_step_before = two_steps_before;
        two_steps_before = all_ways;
    }
    return all_ways;
}   

This is a refined solution with O(n) time and O(1) space complexity. 

Source:https://leetcode.com/discuss/16866/basically-its-a-fibonacci


Fibonacci series

I followed the idea of Fibonacci series, and submitted my solution. Unfortunately, I didn't fully understand the logic behind. In order to truly understand it, the following graph is needed: 




Two major prop­er­ties of Dynamic programming–

To decide whether prob­lem can be solved by apply­ing Dynamic pro­gram­ming we check for two prop­er­ties. If prob­lem has these two prop­er­ties then we can solve that prob­lem using Dynamic programming.

  1. Over­lap­ping Sub-problems
  2. Opti­mal Substructure.

Source:  http://algorithms.tutorialhorizon.com/introduction-to-dynamic-programming-fibonacci-series/


Print all paths

Dynamic programming is a technique to solve the recursive problems more efficiently. Many times in recursion a large problem is divided into smaller problems which can be solved by running the same sub-algorithm repeatedly. The pic above shows a call sequence of function Fib to calc Fibonacci series. The function Fib is called repeatedly at each level, and the parameter is reduced by one at each time. The function is return when the parameter is down to root elements 1 and 0, and the return value equals to the Fibonacci series. This process emulate the course of climbing stairs one or two steps at a time. Since the algorithm and the its implementation is very short and concise, I immediately memorized it without truly understand the detail. When I was asked to print all the paths of unique ways, I couldn't figure out how to literally compute all the paths. This pic not only helped me to fully understand quesiton 70 from Leetcode, but also helped me to come up a solution to print all the unique paths. 

But, before I dive into details of my solution, I would like to illustrate my idea using the following graph:

Though the illustration of Fibonacci series helped me to better understand how the series is generated, I still need a more specific example to help me design a algorithm and prove its correctness. The pic above also provides an inside to the time complexity of the algorithm. It has a tree like structure, which generally has an O(n^2) complexity traversing all the leaf elements, where n denotes the tree height.


import java.util.ArrayList;
import java.util.List;

public class DynamicProgramming {

	//dealing with corner cases and doing preparation 
	public List<List<Integer>> stairsClimbing(int n) {
		
		List<List<Integer>> res = new ArrayList<>();
		if(n == 0) return res;
		if(n == 1)
		{
			List<Integer> tmp = new ArrayList<>();
			tmp.add(1);
			res.add(tmp);
			return  res;
		}
		List<Integer> path = new ArrayList<Integer>();
				path.add(n);
		pathFinder(res,n,path);
		return res;
	}
	
	private void pathFinder(List<List<Integer>> res, int index, List<Integer> path){
		if(index == 0 || index == 1){ 
			res.add(new ArrayList<Integer>(path));
			return;
		}
		
			List<Integer> go_1 = new ArrayList<>(path);
			go_1.add(index -1);
			pathFinder(res,index -1,go_1);
		
			List<Integer> go_2 = new ArrayList<>(path);
			go_2.add(index -2);
			pathFinder(res,index -2,go_2);
		
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		DynamicProgramming dp = new DynamicProgramming();
		List<List<Integer>> res = dp.stairsClimbing(5);
		for(List<Integer> it : res){
			for(Integer i : it)
				System.out.print("  i : " + i);
			System.out.println();
		}
	}

}


This algorithm has a two parts. The function "stairClimbing" is used to detect corner cases and to initialize parameters. Pathfinder is the recursive function used to generate paths from a given number n. Each time pathfinder splits into two recursive calls for one step and two steps respectively.  When the algorithm reaches the root elements 0 or 1, the path will be added to the result arraylist. I made several mistake while designing and implementing the solution. The worst one is adding the path to the result arraylist too early. I realized that bug when I saw "4,3" in the result. I first tried to remove these "byproduct" before the function is returned. This is pretty much the same why of using solving subset problem using back tracing. However, it results in incomplete paths which also contains duplication. Then I decided to only adding new paths when it reaches root elements, which is also used solving combination sum to a target number. I also include an interesting solution. This is a improved and more general solution to this question. It uses a for-loop over a so called "strives" variable to adapt to non-fixed step length.

The key part of his solution :

private static void recurseRoute(final int remaining, final int[] strides,
            final int[] combination, final int comblength, final List<int[]> results) {
        if (remaining < 0) {
            // this combination takes us too far.
            return;
        }
        if (remaining == 0) {
            // this combination is just right.
            results.add(Arrays.copyOf(combination, comblength));
            return;
        }
        // need to go further.
        for (int s : strides) {
            combination[comblength] = s;
            recurseRoute(remaining - s, strides, combination, comblength + 1, results);
        }

    }

 Source: http://codereview.stackexchange.com/questions/41448/print-routes-on-the-stairway-when-only-1-or-2-steps-are-possible


By using a variable to account the current length of step, this algorithm can adapt to any combination of steps, 1/2, 1/2/3, or 2/3/4.

The time complexity of this algorithm is O(2^n). Each time the recursion splits into two, one for one stair per step, another for two stairs per step. It results in a binary tree like structure which has a complexity depends on the height. The same idea applies to our algorithm, its time complexity is a function to the number of stairs of the stair way. Since the recursion splits into two new method calls at each time, the complexity bounds to 2 to the power of n, where n means the number of stairs to climb. 


Although this algorithm can be used to calc how many unique ways to climb stairs, it is way too slow compare to Fibonacci series. It exceeded the time limitation of Leetcode on line judge when the number of stairs is 35.


The bad idea

private void pathFinder(List<List<Integer>> res, int index, List<Integer> path){
		if(index == 0 || index == 1) return;
		
		List<Integer> go_1 = new ArrayList<>(path);
		if(index -1 != 0){
		go_1.add(index -1);
		res.add(go_1);
		pathFinder(res,index -1,go_1);
		}
		if(index -2 != 0) {
		List<Integer> go_2 = new ArrayList<>(path);//forgot to change the variable name after copy paste 
		go_2.add(index -2);
		res.add(go_2);
		pathFinder(res,index -2,go_2);
		res.remove(path);//failed attempt to delete the byproduct
		}
	}

It is a pretty bad idea trying to delete all the byproduct at the end of each function. After all, it is not back tracing. Fibonacci doesn't have result like 4,1,0 which is possible in subset.


Colusion

Dynamic Programming is a technique to solve complex problems. It breaks down a large problem into smaller and simpler subproblems, solve the subproblems, caches the results, and retrieves the results for overlapping subproblems in later computation.


I tried to find a polynomial solution without much success.  While I was searching, I found two interesting web sites "http://codereview.stackexchange.com/" and "https://open.kattis.com/problems/walrusweights".   Codereview focus more on API and specific techniques such as Javascript, while kattis is more like coding Olympics. 



Source:https://leetcode.com/discuss/2809/easy-solutions-for-suggestions
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值