Skip to content

Commit 5ba447e

Browse files
Add Dynamic Programming Algorithms (#776)
* Add dynamic programming problem implementations and their tests * Fix bugs and improve test cases for dynamic programming algorithms * Fix linters * Modify Dice Throw Test Function Name
1 parent e8cbbce commit 5ba447e

24 files changed

+876
-0
lines changed

dynamic/burstballoons.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package dynamic
2+
3+
import "github.com/TheAlgorithms/Go/math/max"
4+
5+
// MaxCoins returns the maximum coins we can collect by bursting the balloons
6+
func MaxCoins(nums []int) int {
7+
n := len(nums)
8+
if n == 0 {
9+
return 0
10+
}
11+
12+
nums = append([]int{1}, nums...)
13+
nums = append(nums, 1)
14+
15+
dp := make([][]int, n+2)
16+
for i := range dp {
17+
dp[i] = make([]int, n+2)
18+
}
19+
20+
for length := 1; length <= n; length++ {
21+
for left := 1; left+length-1 <= n; left++ {
22+
right := left + length - 1
23+
for k := left; k <= right; k++ {
24+
coins := nums[left-1] * nums[k] * nums[right+1]
25+
dp[left][right] = max.Int(dp[left][right], dp[left][k-1]+dp[k+1][right]+coins)
26+
}
27+
}
28+
}
29+
30+
return dp[1][n]
31+
}

dynamic/burstballoons_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package dynamic_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/TheAlgorithms/Go/dynamic"
7+
)
8+
9+
type testCaseBurstBalloons struct {
10+
nums []int
11+
expected int
12+
}
13+
14+
func getBurstBalloonsTestCases() []testCaseBurstBalloons {
15+
return []testCaseBurstBalloons{
16+
{[]int{3, 1, 5, 8}, 167}, // Maximum coins from [3,1,5,8]
17+
{[]int{1, 5}, 10}, // Maximum coins from [1,5]
18+
{[]int{1}, 1}, // Single balloon
19+
{[]int{}, 0}, // No balloons
20+
}
21+
22+
}
23+
24+
func TestMaxCoins(t *testing.T) {
25+
t.Run("Burst Balloons test cases", func(t *testing.T) {
26+
for _, tc := range getBurstBalloonsTestCases() {
27+
actual := dynamic.MaxCoins(tc.nums)
28+
if actual != tc.expected {
29+
t.Errorf("MaxCoins(%v) = %d; expected %d", tc.nums, actual, tc.expected)
30+
}
31+
}
32+
})
33+
}

dynamic/dicethrow.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// dicethrow.go
2+
// description: Solves the Dice Throw Problem using dynamic programming
3+
// reference: https://www.geeksforgeeks.org/dice-throw-problem/
4+
// time complexity: O(m * n)
5+
// space complexity: O(m * n)
6+
7+
package dynamic
8+
9+
// DiceThrow returns the number of ways to get sum `sum` using `m` dice with `n` faces
10+
func DiceThrow(m, n, sum int) int {
11+
dp := make([][]int, m+1)
12+
for i := range dp {
13+
dp[i] = make([]int, sum+1)
14+
}
15+
16+
for i := 1; i <= n; i++ {
17+
if i <= sum {
18+
dp[1][i] = 1
19+
}
20+
}
21+
22+
for i := 2; i <= m; i++ {
23+
for j := 1; j <= sum; j++ {
24+
for k := 1; k <= n; k++ {
25+
if j-k >= 0 {
26+
dp[i][j] += dp[i-1][j-k]
27+
}
28+
}
29+
}
30+
}
31+
32+
return dp[m][sum]
33+
}

dynamic/dicethrow_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dynamic_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/TheAlgorithms/Go/dynamic"
7+
)
8+
9+
type testCaseDiceThrow struct {
10+
numDice int
11+
numFaces int
12+
targetSum int
13+
expected int
14+
}
15+
16+
// getDiceThrowTestCases provides the test cases for DiceThrow
17+
func getDiceThrowTestCases() []testCaseDiceThrow {
18+
return []testCaseDiceThrow{
19+
{2, 6, 7, 6}, // Two dice, six faces each, sum = 7
20+
{1, 6, 3, 1}, // One die, six faces, sum = 3
21+
{3, 4, 5, 6}, // Three dice, four faces each, sum = 5
22+
{1, 6, 1, 1}, // One die, six faces, sum = 1
23+
{2, 6, 12, 1}, // Two dice, six faces each, sum = 12
24+
{3, 6, 18, 1}, // Three dice, six faces each, sum = 18
25+
{2, 6, 20, 0}, // Two dice, six faces each, sum = 20 (impossible)
26+
{1, 1, 1, 1}, // One die, one face, sum = 1
27+
{1, 1, 2, 0}, // One die, one face, sum = 2 (impossible)
28+
{2, 1, 2, 1}, // Two dice, one face each, sum = 2
29+
}
30+
}
31+
32+
// TestDiceThrow tests the DiceThrow function with basic test cases
33+
func TestDiceThrow(t *testing.T) {
34+
t.Run("Basic test cases", func(t *testing.T) {
35+
for _, tc := range getDiceThrowTestCases() {
36+
actual := dynamic.DiceThrow(tc.numDice, tc.numFaces, tc.targetSum)
37+
if actual != tc.expected {
38+
t.Errorf("DiceThrow(%d, %d, %d) = %d; expected %d", tc.numDice, tc.numFaces, tc.targetSum, actual, tc.expected)
39+
}
40+
}
41+
})
42+
}

dynamic/eggdropping.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package dynamic
2+
3+
import (
4+
"github.com/TheAlgorithms/Go/math/max"
5+
"github.com/TheAlgorithms/Go/math/min"
6+
)
7+
8+
// EggDropping finds the minimum number of attempts needed to find the critical floor
9+
// with `eggs` number of eggs and `floors` number of floors
10+
func EggDropping(eggs, floors int) int {
11+
// Edge case: If there are no floors, no attempts needed
12+
if floors == 0 {
13+
return 0
14+
}
15+
// Edge case: If there is one floor, one attempt needed
16+
if floors == 1 {
17+
return 1
18+
}
19+
// Edge case: If there is one egg, need to test all floors one by one
20+
if eggs == 1 {
21+
return floors
22+
}
23+
24+
// Initialize DP table
25+
dp := make([][]int, eggs+1)
26+
for i := range dp {
27+
dp[i] = make([]int, floors+1)
28+
}
29+
30+
// Fill the DP table for 1 egg
31+
for j := 1; j <= floors; j++ {
32+
dp[1][j] = j
33+
}
34+
35+
// Fill the DP table for more than 1 egg
36+
for i := 2; i <= eggs; i++ {
37+
for j := 2; j <= floors; j++ {
38+
dp[i][j] = int(^uint(0) >> 1) // initialize with a large number
39+
for x := 1; x <= j; x++ {
40+
// Recurrence relation to fill the DP table
41+
res := max.Int(dp[i-1][x-1], dp[i][j-x]) + 1
42+
dp[i][j] = min.Int(dp[i][j], res)
43+
}
44+
}
45+
}
46+
return dp[eggs][floors]
47+
}

dynamic/eggdropping_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package dynamic_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/TheAlgorithms/Go/dynamic"
7+
)
8+
9+
type testCaseEggDropping struct {
10+
eggs int
11+
floors int
12+
expected int
13+
}
14+
15+
func getEggDroppingTestCases() []testCaseEggDropping {
16+
return []testCaseEggDropping{
17+
{1, 10, 10}, // One egg, need to test all floors
18+
{2, 10, 4}, // Two eggs and ten floors
19+
{3, 14, 4}, // Three eggs and fourteen floors
20+
{2, 36, 8}, // Two eggs and thirty-six floors
21+
{2, 0, 0}, // Two eggs, zero floors
22+
}
23+
24+
}
25+
26+
func TestEggDropping(t *testing.T) {
27+
t.Run("Egg Dropping test cases", func(t *testing.T) {
28+
for _, tc := range getEggDroppingTestCases() {
29+
actual := dynamic.EggDropping(tc.eggs, tc.floors)
30+
if actual != tc.expected {
31+
t.Errorf("EggDropping(%d, %d) = %d; expected %d", tc.eggs, tc.floors, actual, tc.expected)
32+
}
33+
}
34+
})
35+
}

dynamic/interleavingstrings.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// interleavingstrings.go
2+
// description: Solves the Interleaving Strings problem using dynamic programming
3+
// reference: https://en.wikipedia.org/wiki/Interleaving_strings
4+
// time complexity: O(m*n)
5+
// space complexity: O(m*n)
6+
7+
package dynamic
8+
9+
// IsInterleave checks if string `s1` and `s2` can be interleaved to form string `s3`
10+
func IsInterleave(s1, s2, s3 string) bool {
11+
if len(s1)+len(s2) != len(s3) {
12+
return false
13+
}
14+
15+
dp := make([][]bool, len(s1)+1)
16+
for i := range dp {
17+
dp[i] = make([]bool, len(s2)+1)
18+
}
19+
20+
dp[0][0] = true
21+
for i := 1; i <= len(s1); i++ {
22+
dp[i][0] = dp[i-1][0] && s1[i-1] == s3[i-1]
23+
}
24+
25+
for j := 1; j <= len(s2); j++ {
26+
dp[0][j] = dp[0][j-1] && s2[j-1] == s3[j-1]
27+
}
28+
29+
for i := 1; i <= len(s1); i++ {
30+
for j := 1; j <= len(s2); j++ {
31+
dp[i][j] = (dp[i-1][j] && s1[i-1] == s3[i+j-1]) || (dp[i][j-1] && s2[j-1] == s3[i+j-1])
32+
}
33+
}
34+
35+
return dp[len(s1)][len(s2)]
36+
}

dynamic/interleavingstrings_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package dynamic_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/TheAlgorithms/Go/dynamic"
7+
)
8+
9+
type testCaseInterleaving struct {
10+
s1, s2, s3 string
11+
expected bool
12+
}
13+
14+
func getInterleavingTestCases() []testCaseInterleaving {
15+
return []testCaseInterleaving{
16+
{"aab", "axy", "aaxaby", true}, // Valid interleaving
17+
{"aab", "axy", "abaaxy", false}, // Invalid interleaving
18+
{"", "", "", true}, // All empty strings
19+
{"abc", "", "abc", true}, // Only s1 matches s3
20+
{"", "xyz", "xyz", true}, // Only s2 matches s3
21+
{"abc", "xyz", "abxcyz", true}, // Valid interleaving
22+
{"aaa", "aaa", "aaaaaa", true}, // Identical strings
23+
{"aaa", "aaa", "aaaaaaa", false}, // Extra character
24+
{"abc", "def", "abcdef", true}, // Concatenation order
25+
{"abc", "def", "adbcef", true}, // Valid mixed interleaving
26+
}
27+
}
28+
29+
func TestIsInterleave(t *testing.T) {
30+
t.Run("Interleaving Strings test cases", func(t *testing.T) {
31+
for _, tc := range getInterleavingTestCases() {
32+
actual := dynamic.IsInterleave(tc.s1, tc.s2, tc.s3)
33+
if actual != tc.expected {
34+
t.Errorf("IsInterleave(%q, %q, %q) = %v; expected %v", tc.s1, tc.s2, tc.s3, actual, tc.expected)
35+
}
36+
}
37+
})
38+
}
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// longestarithmeticsubsequence.go
2+
// description: Implementation of the Longest Arithmetic Subsequence problem
3+
// reference: https://en.wikipedia.org/wiki/Longest_arithmetic_progression
4+
// time complexity: O(n^2)
5+
// space complexity: O(n^2)
6+
7+
package dynamic
8+
9+
// LongestArithmeticSubsequence returns the length of the longest arithmetic subsequence
10+
func LongestArithmeticSubsequence(nums []int) int {
11+
n := len(nums)
12+
if n <= 1 {
13+
return n
14+
}
15+
16+
dp := make([]map[int]int, n)
17+
for i := range dp {
18+
dp[i] = make(map[int]int)
19+
}
20+
21+
maxLength := 1
22+
23+
for i := 1; i < n; i++ {
24+
for j := 0; j < i; j++ {
25+
diff := nums[i] - nums[j]
26+
dp[i][diff] = dp[j][diff] + 1
27+
if dp[i][diff]+1 > maxLength {
28+
maxLength = dp[i][diff] + 1
29+
}
30+
}
31+
}
32+
33+
return maxLength
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package dynamic_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/TheAlgorithms/Go/dynamic"
7+
)
8+
9+
type testCaseLongestArithmeticSubsequence struct {
10+
nums []int
11+
expected int
12+
}
13+
14+
func getLongestArithmeticSubsequenceTestCases() []testCaseLongestArithmeticSubsequence {
15+
return []testCaseLongestArithmeticSubsequence{
16+
{[]int{3, 6, 9, 12}, 4}, // Arithmetic sequence of length 4
17+
{[]int{9, 4, 7, 2, 10}, 3}, // Arithmetic sequence of length 3
18+
{[]int{20, 1, 15, 3, 10, 5, 8}, 4}, // Arithmetic sequence of length 4
19+
{[]int{1, 2, 3, 4, 5}, 5}, // Arithmetic sequence of length 5
20+
{[]int{10, 7, 4, 1}, 4}, // Arithmetic sequence of length 4
21+
{[]int{1, 5, 7, 8, 5, 3, 4, 3, 1, 2}, 4}, // Arithmetic sequence of length 4
22+
{[]int{1, 3, 5, 7, 9}, 5}, // Arithmetic sequence of length 5
23+
{[]int{5, 10, 15, 20}, 4}, // Arithmetic sequence of length 4
24+
{[]int{1}, 1}, // Single element, length is 1
25+
{[]int{}, 0}, // Empty array, length is 0
26+
}
27+
}
28+
29+
func TestLongestArithmeticSubsequence(t *testing.T) {
30+
t.Run("Longest Arithmetic Subsequence test cases", func(t *testing.T) {
31+
for _, tc := range getLongestArithmeticSubsequenceTestCases() {
32+
actual := dynamic.LongestArithmeticSubsequence(tc.nums)
33+
if actual != tc.expected {
34+
t.Errorf("LongestArithmeticSubsequence(%v) = %v; expected %v", tc.nums, actual, tc.expected)
35+
}
36+
}
37+
})
38+
}

0 commit comments

Comments
 (0)