Problem 1: Climbing Stairs
Explanation:
You are climbing a staircase. It takes n steps to reach the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
Approach:
- Dynamic Programming:
- Use
dp[i] = dp[i-1] + dp[i-2], as the number of ways to reachiis the sum of the ways to reachi-1andi-2.
- Use
- Optimized Space Approach:
- Instead of using an array, use two variables to track previous two steps.
Time & Space Complexity:
- Time Complexity: O(n), since we compute for each step once.
- Space Complexity: O(1), since we use only two variables.
Java Implementation:
public class ClimbingStairs {
/**
* Calculates the number of distinct ways to climb n stairs.
* @param n - number of steps
* @return number of ways to reach the top
*/
public static int climbStairs(int n) {
if (n <= 2) return n;
int first = 1, second = 2;
for (int i = 3; i <= n; i++) {
int third = first + second;
first = second;
second = third;
}
return second;
}
public static void main(String[] args) {
int n = 5;
System.out.println("Output: " + climbStairs(n));
}
}
Example:
Input:
n = 5
Output:
8
Edge Cases Considered:
- n = 1 → Should return
1. - n = 2 → Should return
2. - Large
n→ Should efficiently compute values.
Problem 2: House Robber
Explanation:
Given an integer array nums representing the amount of money in houses, return the maximum amount you can rob without robbing adjacent houses.
Approach:
- Dynamic Programming:
- Use
dp[i] = max(dp[i-1], dp[i-2] + nums[i]), wheredp[i]represents the maximum money up to housei.
- Use
- Optimized Space Approach:
- Track only the last two computed values instead of using an array.
Time & Space Complexity:
- Time Complexity: O(n), since we process each house once.
- Space Complexity: O(1), using only two variables.
Java Implementation:
public class HouseRobber {
/**
* Computes the maximum amount that can be robbed without robbing adjacent houses.
* @param nums - array of house values
* @return maximum amount that can be robbed
*/
public static int rob(int[] nums) {
if (nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
int prev1 = 0, prev2 = 0;
for (int num : nums) {
int temp = Math.max(prev1, prev2 + num);
prev2 = prev1;
prev1 = temp;
}
return prev1;
}
public static void main(String[] args) {
int[] nums = {2, 7, 9, 3, 1};
System.out.println("Output: " + rob(nums));
}
}
Example:
Input:
nums = [2,7,9,3,1]
Output:
12
Edge Cases Considered:
- Empty array → Should return
0. - Single house → Should return its value.
- Multiple houses with different values → Should select non-adjacent ones optimally.
Problem 3: Coin Change 2
Explanation:
Given coins of different denominations and a total amount, return the number of ways to make that amount.
Approach:
- Dynamic Programming:
- Use
dp[i][j] = dp[i-1][j] + dp[i][j-coins[i]], wheredp[i][j]is the number of ways to make amountjusing the firsticoins.
- Use
- Optimized Space Approach:
- Use a 1D array
dp[j], wheredp[j] += dp[j - coin].
- Use a 1D array
Time & Space Complexity:
- Time Complexity: O(n * m), where
nis the number of coins andmis the target amount. - Space Complexity: O(m), using a 1D array.
Java Implementation:
public class CoinChange2 {
/**
* Computes the number of ways to make amount using given coins.
* @param amount - total amount
* @param coins - array of coin denominations
* @return number of ways to make the amount
*/
public static int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int coin : coins) {
for (int j = coin; j <= amount; j++) {
dp[j] += dp[j - coin];
}
}
return dp[amount];
}
public static void main(String[] args) {
int amount = 5;
int[] coins = {1, 2, 5};
System.out.println("Output: " + change(amount, coins));
}
}
Example:
Input:
amount = 5, coins = [1, 2, 5]
Output:
4
Edge Cases Considered:
- No coins available → Should return
0. - Amount
0→ Should return1. - Multiple ways to make the amount → Should compute all possible combinations.
Problem 4: Unique Paths
Explanation:
A robot is located at the top-left corner of an m x n grid. The robot can only move either down or right at any point in time. Find the number of unique paths to the bottom-right corner.
Approach:
- Dynamic Programming:
- Use
dp[i][j] = dp[i-1][j] + dp[i][j-1], wheredp[i][j]represents the number of unique paths to(i, j).
- Use
- Optimized Space Approach:
- Use a 1D array to store previous row computations.
Time & Space Complexity:
- Time Complexity: O(m * n), since we traverse the grid once.
- Space Complexity: O(n), using a 1D array.
Java Implementation:
public class UniquePaths {
/**
* Computes the number of unique paths in an m x n grid.
* @param m - number of rows
* @param n - number of columns
* @return number of unique paths
*/
public static int uniquePaths(int m, int n) {
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[j] += dp[j - 1];
}
}
return dp[n - 1];
}
public static void main(String[] args) {
int m = 3, n = 7;
System.out.println("Output: " + uniquePaths(m, n));
}
}
Example:
Input:
m = 3, n = 7
Output:
28
Edge Cases Considered:
- Single row or column → Should return
1. - Small and large grids → Should compute efficiently.
Problem 5: Longest Increasing Subsequence
Explanation:
Given an array nums, return the length of the longest increasing subsequence.
Approach:
- Dynamic Programming:
- Use
dp[i] = max(dp[i], dp[j] + 1), iterating over previous valuesj < i.
- Use
- Binary Search Optimization:
- Use a list to keep track of increasing elements and update using binary search.
Time & Space Complexity:
- Time Complexity: O(n log n), using binary search.
- Space Complexity: O(n), for tracking elements.
Java Implementation:
import java.util.*;
public class LongestIncreasingSubsequence {
/**
* Computes the length of the longest increasing subsequence.
* @param nums - input array
* @return length of longest subsequence
*/
public static int lengthOfLIS(int[] nums) {
List<Integer> lis = new ArrayList<>();
for (int num : nums) {
int pos = Collections.binarySearch(lis, num);
if (pos < 0) pos = -(pos + 1);
if (pos < lis.size()) lis.set(pos, num);
else lis.add(num);
}
return lis.size();
}
public static void main(String[] args) {
int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};
System.out.println("Output: " + lengthOfLIS(nums));
}
}
Example:
Input:
nums = [10,9,2,5,3,7,101,18]
Output:
4
Edge Cases Considered:
- Single element array → Should return
1. - All elements equal → Should return
1. - Already sorted array → Should return
n.
Problem 6: Edit Distance
Explanation:
Given two strings word1 and word2, return the minimum number of operations required to convert word1 to word2 using insertions, deletions, and substitutions.
Approach:
- Dynamic Programming:
- Use
dp[i][j], wheredp[i][j]represents the minimum edits to convertword1[0...i]toword2[0...j]. - Recurrence: If characters match,
dp[i][j] = dp[i-1][j-1], otherwise1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]).
- Use
Time & Space Complexity:
- Time Complexity: O(m * n), where
mandnare string lengths. - Space Complexity: O(min(m, n)), using optimized space.
Java Implementation:
public class EditDistance {
/**
* Computes the minimum edit distance between two words.
* @param word1 - first word
* @param word2 - second word
* @return minimum number of edits required
*/
public static int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[] dp = new int[n + 1];
for (int j = 0; j <= n; j++) dp[j] = j;
for (int i = 1; i <= m; i++) {
int prev = dp[0];
dp[0] = i;
for (int j = 1; j <= n; j++) {
int temp = dp[j];
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[j] = prev;
} else {
dp[j] = 1 + Math.min(prev, Math.min(dp[j - 1], dp[j]));
}
prev = temp;
}
}
return dp[n];
}
public static void main(String[] args) {
String word1 = "horse", word2 = "ros";
System.out.println("Output: " + minDistance(word1, word2));
}
}
Example:
Input:
word1 = "horse", word2 = "ros"
Output:
3
Edge Cases Considered:
- Identical words → Should return
0. - One word empty → Should return the length of the other word.
- Similar words with minor differences → Should correctly compute edits.