Skip to content

Commit

Permalink
Add Dynamic Programming Algorithms (#776)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ganeshvenkatasai authored Jan 23, 2025
1 parent e8cbbce commit 5ba447e
Show file tree
Hide file tree
Showing 24 changed files with 876 additions and 0 deletions.
31 changes: 31 additions & 0 deletions dynamic/burstballoons.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dynamic

import "github.com/TheAlgorithms/Go/math/max"

// MaxCoins returns the maximum coins we can collect by bursting the balloons
func MaxCoins(nums []int) int {
n := len(nums)
if n == 0 {
return 0
}

nums = append([]int{1}, nums...)
nums = append(nums, 1)

dp := make([][]int, n+2)
for i := range dp {
dp[i] = make([]int, n+2)
}

for length := 1; length <= n; length++ {
for left := 1; left+length-1 <= n; left++ {
right := left + length - 1
for k := left; k <= right; k++ {
coins := nums[left-1] * nums[k] * nums[right+1]
dp[left][right] = max.Int(dp[left][right], dp[left][k-1]+dp[k+1][right]+coins)
}
}
}

return dp[1][n]
}
33 changes: 33 additions & 0 deletions dynamic/burstballoons_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dynamic_test

import (
"testing"

"github.com/TheAlgorithms/Go/dynamic"
)

type testCaseBurstBalloons struct {
nums []int
expected int
}

func getBurstBalloonsTestCases() []testCaseBurstBalloons {
return []testCaseBurstBalloons{
{[]int{3, 1, 5, 8}, 167}, // Maximum coins from [3,1,5,8]
{[]int{1, 5}, 10}, // Maximum coins from [1,5]
{[]int{1}, 1}, // Single balloon
{[]int{}, 0}, // No balloons
}

}

func TestMaxCoins(t *testing.T) {
t.Run("Burst Balloons test cases", func(t *testing.T) {
for _, tc := range getBurstBalloonsTestCases() {
actual := dynamic.MaxCoins(tc.nums)
if actual != tc.expected {
t.Errorf("MaxCoins(%v) = %d; expected %d", tc.nums, actual, tc.expected)
}
}
})
}
33 changes: 33 additions & 0 deletions dynamic/dicethrow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// dicethrow.go
// description: Solves the Dice Throw Problem using dynamic programming
// reference: https://www.geeksforgeeks.org/dice-throw-problem/
// time complexity: O(m * n)
// space complexity: O(m * n)

package dynamic

// DiceThrow returns the number of ways to get sum `sum` using `m` dice with `n` faces
func DiceThrow(m, n, sum int) int {
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, sum+1)
}

for i := 1; i <= n; i++ {
if i <= sum {
dp[1][i] = 1
}
}

for i := 2; i <= m; i++ {
for j := 1; j <= sum; j++ {
for k := 1; k <= n; k++ {
if j-k >= 0 {
dp[i][j] += dp[i-1][j-k]
}
}
}
}

return dp[m][sum]
}
42 changes: 42 additions & 0 deletions dynamic/dicethrow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dynamic_test

import (
"testing"

"github.com/TheAlgorithms/Go/dynamic"
)

type testCaseDiceThrow struct {
numDice int
numFaces int
targetSum int
expected int
}

// getDiceThrowTestCases provides the test cases for DiceThrow
func getDiceThrowTestCases() []testCaseDiceThrow {
return []testCaseDiceThrow{
{2, 6, 7, 6}, // Two dice, six faces each, sum = 7
{1, 6, 3, 1}, // One die, six faces, sum = 3
{3, 4, 5, 6}, // Three dice, four faces each, sum = 5
{1, 6, 1, 1}, // One die, six faces, sum = 1
{2, 6, 12, 1}, // Two dice, six faces each, sum = 12
{3, 6, 18, 1}, // Three dice, six faces each, sum = 18
{2, 6, 20, 0}, // Two dice, six faces each, sum = 20 (impossible)
{1, 1, 1, 1}, // One die, one face, sum = 1
{1, 1, 2, 0}, // One die, one face, sum = 2 (impossible)
{2, 1, 2, 1}, // Two dice, one face each, sum = 2
}
}

// TestDiceThrow tests the DiceThrow function with basic test cases
func TestDiceThrow(t *testing.T) {
t.Run("Basic test cases", func(t *testing.T) {
for _, tc := range getDiceThrowTestCases() {
actual := dynamic.DiceThrow(tc.numDice, tc.numFaces, tc.targetSum)
if actual != tc.expected {
t.Errorf("DiceThrow(%d, %d, %d) = %d; expected %d", tc.numDice, tc.numFaces, tc.targetSum, actual, tc.expected)
}
}
})
}
47 changes: 47 additions & 0 deletions dynamic/eggdropping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package dynamic

import (
"github.com/TheAlgorithms/Go/math/max"
"github.com/TheAlgorithms/Go/math/min"
)

// EggDropping finds the minimum number of attempts needed to find the critical floor
// with `eggs` number of eggs and `floors` number of floors
func EggDropping(eggs, floors int) int {
// Edge case: If there are no floors, no attempts needed
if floors == 0 {
return 0
}
// Edge case: If there is one floor, one attempt needed
if floors == 1 {
return 1
}
// Edge case: If there is one egg, need to test all floors one by one
if eggs == 1 {
return floors
}

// Initialize DP table
dp := make([][]int, eggs+1)
for i := range dp {
dp[i] = make([]int, floors+1)
}

// Fill the DP table for 1 egg
for j := 1; j <= floors; j++ {
dp[1][j] = j
}

// Fill the DP table for more than 1 egg
for i := 2; i <= eggs; i++ {
for j := 2; j <= floors; j++ {
dp[i][j] = int(^uint(0) >> 1) // initialize with a large number
for x := 1; x <= j; x++ {
// Recurrence relation to fill the DP table
res := max.Int(dp[i-1][x-1], dp[i][j-x]) + 1
dp[i][j] = min.Int(dp[i][j], res)
}
}
}
return dp[eggs][floors]
}
35 changes: 35 additions & 0 deletions dynamic/eggdropping_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package dynamic_test

import (
"testing"

"github.com/TheAlgorithms/Go/dynamic"
)

type testCaseEggDropping struct {
eggs int
floors int
expected int
}

func getEggDroppingTestCases() []testCaseEggDropping {
return []testCaseEggDropping{
{1, 10, 10}, // One egg, need to test all floors
{2, 10, 4}, // Two eggs and ten floors
{3, 14, 4}, // Three eggs and fourteen floors
{2, 36, 8}, // Two eggs and thirty-six floors
{2, 0, 0}, // Two eggs, zero floors
}

}

func TestEggDropping(t *testing.T) {
t.Run("Egg Dropping test cases", func(t *testing.T) {
for _, tc := range getEggDroppingTestCases() {
actual := dynamic.EggDropping(tc.eggs, tc.floors)
if actual != tc.expected {
t.Errorf("EggDropping(%d, %d) = %d; expected %d", tc.eggs, tc.floors, actual, tc.expected)
}
}
})
}
36 changes: 36 additions & 0 deletions dynamic/interleavingstrings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// interleavingstrings.go
// description: Solves the Interleaving Strings problem using dynamic programming
// reference: https://en.wikipedia.org/wiki/Interleaving_strings
// time complexity: O(m*n)
// space complexity: O(m*n)

package dynamic

// IsInterleave checks if string `s1` and `s2` can be interleaved to form string `s3`
func IsInterleave(s1, s2, s3 string) bool {
if len(s1)+len(s2) != len(s3) {
return false
}

dp := make([][]bool, len(s1)+1)
for i := range dp {
dp[i] = make([]bool, len(s2)+1)
}

dp[0][0] = true
for i := 1; i <= len(s1); i++ {
dp[i][0] = dp[i-1][0] && s1[i-1] == s3[i-1]
}

for j := 1; j <= len(s2); j++ {
dp[0][j] = dp[0][j-1] && s2[j-1] == s3[j-1]
}

for i := 1; i <= len(s1); i++ {
for j := 1; j <= len(s2); j++ {
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])
}
}

return dp[len(s1)][len(s2)]
}
38 changes: 38 additions & 0 deletions dynamic/interleavingstrings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package dynamic_test

import (
"testing"

"github.com/TheAlgorithms/Go/dynamic"
)

type testCaseInterleaving struct {
s1, s2, s3 string
expected bool
}

func getInterleavingTestCases() []testCaseInterleaving {
return []testCaseInterleaving{
{"aab", "axy", "aaxaby", true}, // Valid interleaving
{"aab", "axy", "abaaxy", false}, // Invalid interleaving
{"", "", "", true}, // All empty strings
{"abc", "", "abc", true}, // Only s1 matches s3
{"", "xyz", "xyz", true}, // Only s2 matches s3
{"abc", "xyz", "abxcyz", true}, // Valid interleaving
{"aaa", "aaa", "aaaaaa", true}, // Identical strings
{"aaa", "aaa", "aaaaaaa", false}, // Extra character
{"abc", "def", "abcdef", true}, // Concatenation order
{"abc", "def", "adbcef", true}, // Valid mixed interleaving
}
}

func TestIsInterleave(t *testing.T) {
t.Run("Interleaving Strings test cases", func(t *testing.T) {
for _, tc := range getInterleavingTestCases() {
actual := dynamic.IsInterleave(tc.s1, tc.s2, tc.s3)
if actual != tc.expected {
t.Errorf("IsInterleave(%q, %q, %q) = %v; expected %v", tc.s1, tc.s2, tc.s3, actual, tc.expected)
}
}
})
}
34 changes: 34 additions & 0 deletions dynamic/longestarithmeticsubsequence.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// longestarithmeticsubsequence.go
// description: Implementation of the Longest Arithmetic Subsequence problem
// reference: https://en.wikipedia.org/wiki/Longest_arithmetic_progression
// time complexity: O(n^2)
// space complexity: O(n^2)

package dynamic

// LongestArithmeticSubsequence returns the length of the longest arithmetic subsequence
func LongestArithmeticSubsequence(nums []int) int {
n := len(nums)
if n <= 1 {
return n
}

dp := make([]map[int]int, n)
for i := range dp {
dp[i] = make(map[int]int)
}

maxLength := 1

for i := 1; i < n; i++ {
for j := 0; j < i; j++ {
diff := nums[i] - nums[j]
dp[i][diff] = dp[j][diff] + 1
if dp[i][diff]+1 > maxLength {
maxLength = dp[i][diff] + 1
}
}
}

return maxLength
}
38 changes: 38 additions & 0 deletions dynamic/longestarithmeticsubsequence_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package dynamic_test

import (
"testing"

"github.com/TheAlgorithms/Go/dynamic"
)

type testCaseLongestArithmeticSubsequence struct {
nums []int
expected int
}

func getLongestArithmeticSubsequenceTestCases() []testCaseLongestArithmeticSubsequence {
return []testCaseLongestArithmeticSubsequence{
{[]int{3, 6, 9, 12}, 4}, // Arithmetic sequence of length 4
{[]int{9, 4, 7, 2, 10}, 3}, // Arithmetic sequence of length 3
{[]int{20, 1, 15, 3, 10, 5, 8}, 4}, // Arithmetic sequence of length 4
{[]int{1, 2, 3, 4, 5}, 5}, // Arithmetic sequence of length 5
{[]int{10, 7, 4, 1}, 4}, // Arithmetic sequence of length 4
{[]int{1, 5, 7, 8, 5, 3, 4, 3, 1, 2}, 4}, // Arithmetic sequence of length 4
{[]int{1, 3, 5, 7, 9}, 5}, // Arithmetic sequence of length 5
{[]int{5, 10, 15, 20}, 4}, // Arithmetic sequence of length 4
{[]int{1}, 1}, // Single element, length is 1
{[]int{}, 0}, // Empty array, length is 0
}
}

func TestLongestArithmeticSubsequence(t *testing.T) {
t.Run("Longest Arithmetic Subsequence test cases", func(t *testing.T) {
for _, tc := range getLongestArithmeticSubsequenceTestCases() {
actual := dynamic.LongestArithmeticSubsequence(tc.nums)
if actual != tc.expected {
t.Errorf("LongestArithmeticSubsequence(%v) = %v; expected %v", tc.nums, actual, tc.expected)
}
}
})
}
Loading

0 comments on commit 5ba447e

Please sign in to comment.