As a senior software engineer with expertise in a wide range of programming languages and technologies, I‘m excited to share my insights on the Painting Fence Algorithm, a classic problem in computer science that showcases the power of dynamic programming.
Understanding the Painting Fence Problem
The Painting Fence problem can be stated as follows: Given a fence with n posts and k colors, find the number of ways to paint the fence such that no more than two consecutive posts have the same color.
This problem has a wide range of applications in various domains, from software engineering and data analysis to problem-solving and algorithm design. By understanding the Painting Fence Algorithm, you‘ll gain valuable insights into the principles of dynamic programming, which is a fundamental technique for solving complex problems efficiently.
Recursive Approach
The most straightforward approach to solving the Painting Fence problem is to use a recursive solution. The idea is to define the solution in terms of two choices: painting the last post a different color from the previous one or painting the last two posts the same color.
The recurrence relation for the recursive solution can be expressed as:
countWays(n) = countWays(n-1)*(k-1) + countWays(n-2)*(k-1)Here, countWays(n) represents the number of ways to paint a fence with n posts and k colors.
The base cases for this recursive solution are:
- If
n == 1, there arekways to paint the first post. - If
n == 2, there arek * kways to paint the two posts.
Let‘s take a look at the implementation of the recursive solution in various programming languages:
C++
#include <bits/stdc++.h>
using namespace std;
// Returns count of ways to color k posts
int countWays(int n, int k) {
// base cases
if (n == 1) return k;
if (n == 2) return k * k;
// Ways in which last fence is of different color.
int cnt1 = countWays(n - 1, k) * (k - 1);
// Ways in which last 2 fences are of same color.
int cnt2 = countWays(n - 2, k) * (k - 1);
return cnt1 + cnt2;
}
int main() {
int n = 3, k = 2;
cout << countWays(n, k) << endl;
return 0;
}Java
class GfG {
// Returns count of ways to color k posts
static int countWays(int n, int k) {
// base cases
if (n == 1) return k;
if (n == 2) return k * k;
// Ways in which last fence is of different color.
int cnt1 = countWays(n - 1, k) * (k - 1);
// Ways in which last 2 fences are of same color.
int cnt2 = countWays(n - 2, k) * (k - 1);
return cnt1 + cnt2;
}
public static void main(String[] args) {
int n = 3, k = 2;
System.out.println(countWays(n, k));
}
}Python
# Returns count of ways to color k posts
def countWays(n, k):
# base cases
if n == 1:
return k
if n == 2:
return k * k
# Ways in which last fence is of different color.
cnt1 = countWays(n - 1, k) * (k - 1)
# Ways in which last 2 fences are of same color.
cnt2 = countWays(n - 2, k) * (k - 1)
return cnt1 + cnt2
if __name__ == "__main__":
n = 3
k = 2
print(countWays(n, k))The time complexity of the recursive solution is O(2^n), as the algorithm makes an exponential number of recursive calls. The space complexity is O(n) due to the recursive call stack.
While the recursive solution is straightforward, it suffers from the problem of overlapping subproblems, where certain subproblems are computed multiple times, leading to inefficient computation. To optimize this, we can use a dynamic programming approach.
Top-Down Dynamic Programming (Memoization)
The top-down dynamic programming, also known as memoization, is a technique that helps to avoid redundant computations by storing the results of previously computed subproblems in a data structure, such as an array or a hash table, and reusing them whenever the same subproblem is encountered again.
The memoization approach can be implemented as follows:
C++
#include <bits/stdc++.h>
using namespace std;
int countWaysRecur(int n, int k, vector<int> &memo) {
// base cases
if (n == 1) return k;
if (n == 2) return k * k;
if (memo[n] != -1) return memo[n];
// Ways in which last fence is of different color.
int cnt1 = countWaysRecur(n - 1, k, memo) * (k - 1);
// Ways in which last 2 fences are of same color.
int cnt2 = countWaysRecur(n - 2, k, memo) * (k - 1);
return memo[n] = cnt1 + cnt2;
}
// Returns count of ways to color k posts
int countWays(int n, int k) {
vector<int> memo(n + 1, -1);
return countWaysRecur(n, k, memo);
}
int main() {
int n = 3, k = 2;
cout << countWays(n, k) << endl;
return 0;
}Java
class GfG {
static int countWaysRecur(int n, int k, int[] memo) {
// base cases
if (n == 1) return k;
if (n == 2) return k * k;
if (memo[n] != -1) return memo[n];
// Ways in which last fence is of different color.
int cnt1 = countWaysRecur(n - 1, k, memo) * (k - 1);
// Ways in which last 2 fences are of same color.
int cnt2 = countWaysRecur(n - 2, k, memo) * (k - 1);
return memo[n] = cnt1 + cnt2;
}
// Returns count of ways to color k posts
static int countWays(int n, int k) {
int[] memo = new int[n + 1];
Arrays.fill(memo, -1);
return countWaysRecur(n, k, memo);
}
public static void main(String[] args) {
int n = 3, k = 2;
System.out.println(countWays(n, k));
}
}Python
def countWaysRecur(n, k, memo):
# base cases
if n == 1:
return k
if n == 2:
return k * k
if memo[n] != -1:
return memo[n]
# Ways in which last fence is of different color.
cnt1 = countWaysRecur(n - 1, k, memo) * (k - 1)
# Ways in which last 2 fences are of same color.
cnt2 = countWaysRecur(n - 2, k, memo) * (k - 1)
memo[n] = cnt1 + cnt2
return memo[n]
# Returns count of ways to color k posts
def countWays(n, k):
memo = [-1] * (n + 1)
return countWaysRecur(n, k, memo)
if __name__ == "__main__":
n = 3
k = 2
print(countWays(n, k))The memoization approach has a time complexity of O(n) and a space complexity of O(n), as it stores the results of previously computed subproblems in a 1D array.
Bottom-Up Dynamic Programming (Tabulation)
Another way to solve the Painting Fence problem is to use a bottom-up dynamic programming approach, also known as tabulation. In this approach, we start with the base cases and gradually build up the solution for larger subproblems.
The tabulation solution can be implemented as follows:
C++
#include <bits/stdc++.h>
using namespace std;
// Returns count of ways to color k posts
int countWays(int n, int k) {
// base cases
if (n == 1) return k;
if (n == 2) return k * k;
vector<int> dp(n + 1);
// Fill value for 1 and 2 fences
dp[1] = k;
dp[2] = k * k;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] * (k - 1) + dp[i - 2] * (k - 1);
}
return dp[n];
}
int main() {
int n = 3, k = 2;
cout << countWays(n, k) << endl;
return 0;
}Java
class GfG {
// Returns count of ways to color k posts
static int countWays(int n, int k) {
// base cases
if (n == 1) return k;
if (n == 2) return k * k;
int[] dp = new int[n + 1];
// Fill value for 1 and 2 fences
dp[1] = k;
dp[2] = k * k;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] * (k - 1) + dp[i - 2] * (k - 1);
}
return dp[n];
}
public static void main(String[] args) {
int n = 3, k = 2;
System.out.println(countWays(n, k));
}
}Python
def countWays(n, k):
# base cases
if n == 1:
return k
if n == 2:
return k * k
dp = [0] * (n + 1)
# Fill value for 1 and 2 fences
dp[1] = k
dp[2] = k * k
for i in range(3, n + 1):
dp[i] = dp[i - 1] * (k - 1) + dp[i - 2] * (k - 1)
return dp[n]
if __name__ == "__main__":
n = 3
k = 2
print(countWays(n, k))The tabulation approach has a time complexity of O(n) and a space complexity of O(n), as it uses a 1D array to store the intermediate results.
Space-Optimized Dynamic Programming
While the tabulation approach is more efficient than the recursive solution, it still requires O(n) space to store the intermediate results. We can further optimize the space complexity by observing that we only need to keep track of the last two computed values to calculate the current value.
The space-optimized solution can be implemented as follows:
C++
#include <bits/stdc++.h>
using namespace std;
// Returns count of ways to color k posts
int countWays(int n, int k) {
// base cases
if (n == 1) return k;
if (n == 2) return k * k;
// Fill value for 1 and 2 fences
int prev2 = k;
int prev1 = k * k;
for (int i = 3; i <= n; i++) {
int curr = prev1 * (k - 1) + prev2 * (k - 1);
// update the values
prev2 = prev1;
prev1 = curr;
}
return prev1;
}
int main() {
int n = 3, k = 2;
cout << countWays(n, k) << endl;
return 0;
}Java
class GfG {
// Returns count of ways to color k posts
static int countWays(int n, int k) {
// base cases
if (n == 1) return k;
if (n == 2) return k * k;
// Fill value for 1 and 2 fences
int prev2 = k;
int prev1 = k * k;
for (int i = 3; i <= n; i++) {
int curr = prev1 * (k - 1) + prev2 * (k - 1);
// update the values
prev2 = prev1;
prev1 = curr;
}
return prev1;
}
public static void main(String[] args) {
int n = 3, k = 2;
System.out.println(countWays(n, k));
}
}Python
def countWays(n, k):
# base cases
if n == 1:
return k
if n == 2:
return k *