diff --git a/dynamic/burstballoons.go b/dynamic/burstballoons.go new file mode 100644 index 000000000..492b00d8c --- /dev/null +++ b/dynamic/burstballoons.go @@ -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] +} diff --git a/dynamic/burstballoons_test.go b/dynamic/burstballoons_test.go new file mode 100644 index 000000000..c36e08824 --- /dev/null +++ b/dynamic/burstballoons_test.go @@ -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) + } + } + }) +} diff --git a/dynamic/dicethrow.go b/dynamic/dicethrow.go new file mode 100644 index 000000000..69711ca14 --- /dev/null +++ b/dynamic/dicethrow.go @@ -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] +} diff --git a/dynamic/dicethrow_test.go b/dynamic/dicethrow_test.go new file mode 100644 index 000000000..dad25e533 --- /dev/null +++ b/dynamic/dicethrow_test.go @@ -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) + } + } + }) +} diff --git a/dynamic/eggdropping.go b/dynamic/eggdropping.go new file mode 100644 index 000000000..b6d379389 --- /dev/null +++ b/dynamic/eggdropping.go @@ -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] +} diff --git a/dynamic/eggdropping_test.go b/dynamic/eggdropping_test.go new file mode 100644 index 000000000..5e3232f70 --- /dev/null +++ b/dynamic/eggdropping_test.go @@ -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) + } + } + }) +} diff --git a/dynamic/interleavingstrings.go b/dynamic/interleavingstrings.go new file mode 100644 index 000000000..da13840b2 --- /dev/null +++ b/dynamic/interleavingstrings.go @@ -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)] +} diff --git a/dynamic/interleavingstrings_test.go b/dynamic/interleavingstrings_test.go new file mode 100644 index 000000000..a1559e932 --- /dev/null +++ b/dynamic/interleavingstrings_test.go @@ -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) + } + } + }) +} diff --git a/dynamic/longestarithmeticsubsequence.go b/dynamic/longestarithmeticsubsequence.go new file mode 100644 index 000000000..a187b9cea --- /dev/null +++ b/dynamic/longestarithmeticsubsequence.go @@ -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 +} diff --git a/dynamic/longestarithmeticsubsequence_test.go b/dynamic/longestarithmeticsubsequence_test.go new file mode 100644 index 000000000..2a971fbd7 --- /dev/null +++ b/dynamic/longestarithmeticsubsequence_test.go @@ -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) + } + } + }) +} diff --git a/dynamic/longestpalindromicsubstring.go b/dynamic/longestpalindromicsubstring.go new file mode 100644 index 000000000..01d105629 --- /dev/null +++ b/dynamic/longestpalindromicsubstring.go @@ -0,0 +1,43 @@ +// longestpalindromicsubstring.go +// description: Implementation of finding the longest palindromic substring +// reference: https://en.wikipedia.org/wiki/Longest_palindromic_substring +// time complexity: O(n^2) +// space complexity: O(n^2) + +package dynamic + +// LongestPalindromicSubstring returns the longest palindromic substring in the input string +func LongestPalindromicSubstring(s string) string { + n := len(s) + if n == 0 { + return "" + } + + dp := make([][]bool, n) + for i := range dp { + dp[i] = make([]bool, n) + } + + start := 0 + maxLength := 1 + for i := 0; i < n; i++ { + dp[i][i] = true + } + + for length := 2; length <= n; length++ { + for i := 0; i < n-length+1; i++ { + j := i + length - 1 + if length == 2 { + dp[i][j] = (s[i] == s[j]) + } else { + dp[i][j] = (s[i] == s[j]) && dp[i+1][j-1] + } + + if dp[i][j] && length > maxLength { + maxLength = length + start = i + } + } + } + return s[start : start+maxLength] +} diff --git a/dynamic/longestpalindromicsubstring_test.go b/dynamic/longestpalindromicsubstring_test.go new file mode 100644 index 000000000..e8424eabe --- /dev/null +++ b/dynamic/longestpalindromicsubstring_test.go @@ -0,0 +1,37 @@ +package dynamic_test + +import ( + "testing" + + "github.com/TheAlgorithms/Go/dynamic" +) + +type testCaseLongestPalindromicSubstring struct { + s string + expected string +} + +func getLongestPalindromicSubstringTestCases() []testCaseLongestPalindromicSubstring { + return []testCaseLongestPalindromicSubstring{ + {"babad", "bab"}, // Example with multiple palindromes + {"cbbd", "bb"}, // Example with longest even palindrome + {"a", "a"}, // Single character, palindrome is itself + {"", ""}, // Empty string, no palindrome + {"racecar", "racecar"}, // Whole string is a palindrome + {"abcba", "abcba"}, // Palindrome in the middle + {"aabbcc", "aa"}, // Multiple substrings, longest "aa" + {"madam", "madam"}, // Full palindrome string + {"forgeeksskeegfor", "geeksskeeg"}, // Complex palindrome in the middle + } +} + +func TestLongestPalindromicSubstring(t *testing.T) { + t.Run("Longest Palindromic Substring test cases", func(t *testing.T) { + for _, tc := range getLongestPalindromicSubstringTestCases() { + actual := dynamic.LongestPalindromicSubstring(tc.s) + if actual != tc.expected { + t.Errorf("LongestPalindromicSubstring(%q) = %q; expected %q", tc.s, actual, tc.expected) + } + } + }) +} diff --git a/dynamic/maxsubarraysum.go b/dynamic/maxsubarraysum.go new file mode 100644 index 000000000..4e283e1b9 --- /dev/null +++ b/dynamic/maxsubarraysum.go @@ -0,0 +1,22 @@ +// maxsubarraysum.go +// description: Implementation of Kadane's algorithm for Maximum Subarray Sum +// reference: https://en.wikipedia.org/wiki/Maximum_subarray_problem +// time complexity: O(n) +// space complexity: O(1) + +package dynamic + +import "github.com/TheAlgorithms/Go/math/max" + +// MaxSubArraySum returns the sum of the maximum subarray in the input array +func MaxSubArraySum(nums []int) int { + maxSum := nums[0] + currentSum := nums[0] + + for i := 1; i < len(nums); i++ { + currentSum = max.Int(nums[i], currentSum+nums[i]) + maxSum = max.Int(maxSum, currentSum) + } + + return maxSum +} diff --git a/dynamic/maxsubarraysum_test.go b/dynamic/maxsubarraysum_test.go new file mode 100644 index 000000000..20492c3f7 --- /dev/null +++ b/dynamic/maxsubarraysum_test.go @@ -0,0 +1,37 @@ +package dynamic_test + +import ( + "testing" + + "github.com/TheAlgorithms/Go/dynamic" +) + +type testCaseMaxSubArraySum struct { + nums []int + expected int +} + +func getMaxSubArraySumTestCases() []testCaseMaxSubArraySum { + return []testCaseMaxSubArraySum{ + {[]int{-2, -3, 4, -1, -2, 1, 5, -3}, 7}, // Kadane's algorithm example + {[]int{-1, -2, -3, -4}, -1}, // All negative numbers, max single element + {[]int{5, 4, -1, 7, 8}, 23}, // Positive numbers with a large sum + {[]int{-2, 1, -3, 4, -1, 2, 1, -5, 4}, 6}, // Mixed with a maximum subarray of length 4 + {[]int{1, 2, 3, 4, 5}, 15}, // All positive numbers, sum is the entire array + {[]int{-1, -2, -3, -4, -5}, -1}, // Only negative numbers, largest single element + {[]int{0, 0, 0, 0, 0}, 0}, // Array of zeros, maximum subarray is zero + {[]int{3}, 3}, // Single positive number + {[]int{-1}, -1}, // Single negative number + } +} + +func TestMaxSubArraySum(t *testing.T) { + t.Run("Max SubArray Sum test cases", func(t *testing.T) { + for _, tc := range getMaxSubArraySumTestCases() { + actual := dynamic.MaxSubArraySum(tc.nums) + if actual != tc.expected { + t.Errorf("MaxSubArraySum(%v) = %v; expected %v", tc.nums, actual, tc.expected) + } + } + }) +} diff --git a/dynamic/optimalbst.go b/dynamic/optimalbst.go new file mode 100644 index 000000000..b80f2163c --- /dev/null +++ b/dynamic/optimalbst.go @@ -0,0 +1,61 @@ +package dynamic + +import "github.com/TheAlgorithms/Go/math/min" + +// OptimalBST returns the minimum cost of constructing a Binary Search Tree +func OptimalBST(keys []int, freq []int, n int) int { + // Initialize DP table with size n x n + dp := make([][]int, n) + for i := range dp { + dp[i] = make([]int, n) + } + + // Base case: single key cost + for i := 0; i < n; i++ { + dp[i][i] = freq[i] + } + + // Build the DP table for sequences of length 2 to n + for length := 2; length <= n; length++ { + for i := 0; i < n-length+1; i++ { + j := i + length - 1 + dp[i][j] = int(^uint(0) >> 1) // Initialize with a large value + sum := sum(freq, i, j) + + // Try every key as root and compute cost + for k := i; k <= j; k++ { + // Left cost: dp[i][k-1] is valid only if k > i + var leftCost int + if k > i { + leftCost = dp[i][k-1] + } else { + leftCost = 0 + } + + // Right cost: dp[k+1][j] is valid only if k < j + var rightCost int + if k < j { + rightCost = dp[k+1][j] + } else { + rightCost = 0 + } + + // Total cost for root k + cost := sum + leftCost + rightCost + + // Update dp[i][j] with the minimum cost + dp[i][j] = min.Int(dp[i][j], cost) + } + } + } + return dp[0][n-1] +} + +// Helper function to sum the frequencies +func sum(freq []int, i, j int) int { + total := 0 + for k := i; k <= j; k++ { + total += freq[k] + } + return total +} diff --git a/dynamic/optimalbst_test.go b/dynamic/optimalbst_test.go new file mode 100644 index 000000000..362b45449 --- /dev/null +++ b/dynamic/optimalbst_test.go @@ -0,0 +1,35 @@ +package dynamic_test + +import ( + "testing" + + "github.com/TheAlgorithms/Go/dynamic" +) + +type testCaseOptimalBST struct { + keys []int + freq []int + n int + expected int +} + +func getOptimalBSTTestCases() []testCaseOptimalBST { + return []testCaseOptimalBST{ + {[]int{10, 12, 20}, []int{34, 8, 50}, 3, 142}, // Example with 3 keys + {[]int{10, 20, 30, 40, 50}, []int{10, 20, 30, 40, 50}, 5, 300}, // Example with 5 keys + {[]int{10}, []int{100}, 1, 100}, // Single key case + } +} + +func TestOptimalBST(t *testing.T) { + t.Run("Optimal Binary Search Tree test cases", func(t *testing.T) { + for _, tc := range getOptimalBSTTestCases() { + t.Run("testing optimal BST", func(t *testing.T) { + actual := dynamic.OptimalBST(tc.keys, tc.freq, tc.n) + if actual != tc.expected { + t.Errorf("OptimalBST(%v, %v, %d) = %d; expected %d", tc.keys, tc.freq, tc.n, actual, tc.expected) + } + }) + } + }) +} diff --git a/dynamic/partitionproblem.go b/dynamic/partitionproblem.go new file mode 100644 index 000000000..bb3ca5496 --- /dev/null +++ b/dynamic/partitionproblem.go @@ -0,0 +1,30 @@ +// partitionproblem.go +// description: Solves the Partition Problem using dynamic programming +// reference: https://en.wikipedia.org/wiki/Partition_problem +// time complexity: O(n*sum) +// space complexity: O(n*sum) + +package dynamic + +// PartitionProblem checks whether the given set can be partitioned into two subsets +// such that the sum of the elements in both subsets is the same. +func PartitionProblem(nums []int) bool { + sum := 0 + for _, num := range nums { + sum += num + } + if sum%2 != 0 { + return false + } + + target := sum / 2 + dp := make([]bool, target+1) + dp[0] = true + + for _, num := range nums { + for i := target; i >= num; i-- { + dp[i] = dp[i] || dp[i-num] + } + } + return dp[target] +} diff --git a/dynamic/partitionproblem_test.go b/dynamic/partitionproblem_test.go new file mode 100644 index 000000000..c85742ecf --- /dev/null +++ b/dynamic/partitionproblem_test.go @@ -0,0 +1,39 @@ +package dynamic_test + +import ( + "testing" + + "github.com/TheAlgorithms/Go/dynamic" +) + +// testCasePartitionProblem holds the test cases for the Partition Problem +type testCasePartitionProblem struct { + nums []int + expected bool +} + +// getPartitionProblemTestCases returns a list of test cases for the Partition Problem +func getPartitionProblemTestCases() []testCasePartitionProblem { + return []testCasePartitionProblem{ + {[]int{1, 5, 11, 5}, true}, // Example with a partitionable set + {[]int{1, 2, 3, 5}, false}, // Example where partition is not possible + {[]int{1, 2, 5}, false}, // Set cannot be partitioned into two subsets + {[]int{2, 2, 2, 2}, true}, // Even split possible with equal elements + {[]int{7, 3, 2, 1}, false}, // Set cannot be partitioned + {[]int{}, true}, // Empty set, can be partitioned trivially + {[]int{1}, false}, // Single element, cannot be partitioned + {[]int{10, 10, 10, 10}, true}, // Equal elements, partitionable + } +} + +// TestPartitionProblem tests the PartitionProblem function with different test cases +func TestPartitionProblem(t *testing.T) { + t.Run("Partition Problem test cases", func(t *testing.T) { + for _, tc := range getPartitionProblemTestCases() { + actual := dynamic.PartitionProblem(tc.nums) + if actual != tc.expected { + t.Errorf("PartitionProblem(%v) = %v; expected %v", tc.nums, actual, tc.expected) + } + } + }) +} diff --git a/dynamic/tilingproblem.go b/dynamic/tilingproblem.go new file mode 100644 index 000000000..0407f320d --- /dev/null +++ b/dynamic/tilingproblem.go @@ -0,0 +1,22 @@ +// tilingproblem.go +// description: Solves the Tiling Problem using dynamic programming +// reference: https://en.wikipedia.org/wiki/Tiling_problem +// time complexity: O(n) +// space complexity: O(n) + +package dynamic + +// TilingProblem returns the number of ways to tile a 2xN grid using 2x1 dominoes +func TilingProblem(n int) int { + if n <= 1 { + return 1 + } + dp := make([]int, n+1) + dp[0] = 1 + dp[1] = 1 + + for i := 2; i <= n; i++ { + dp[i] = dp[i-1] + dp[i-2] + } + return dp[n] +} diff --git a/dynamic/tilingproblem_test.go b/dynamic/tilingproblem_test.go new file mode 100644 index 000000000..4f103cd21 --- /dev/null +++ b/dynamic/tilingproblem_test.go @@ -0,0 +1,38 @@ +package dynamic_test + +import ( + "testing" + + "github.com/TheAlgorithms/Go/dynamic" +) + +type testCaseTilingProblem struct { + n int + expected int +} + +func getTilingProblemTestCases() []testCaseTilingProblem { + return []testCaseTilingProblem{ + {1, 1}, // Base case: 1 way to tile a 2x1 grid + {2, 2}, // 2 ways to tile a 2x2 grid + {3, 3}, // 3 ways to tile a 2x3 grid + {4, 5}, // 5 ways to tile a 2x4 grid + {5, 8}, // 8 ways to tile a 2x5 grid + {6, 13}, // 13 ways to tile a 2x6 grid + {10, 89}, // 89 ways to tile a 2x10 grid + {0, 1}, // Edge case: 1 way to tile a 2x0 grid (no tiles) + {7, 21}, // 21 ways to tile a 2x7 grid + {8, 34}, // 34 ways to tile a 2x8 grid + } +} + +func TestTilingProblem(t *testing.T) { + t.Run("Tiling Problem test cases", func(t *testing.T) { + for _, tc := range getTilingProblemTestCases() { + actual := dynamic.TilingProblem(tc.n) + if actual != tc.expected { + t.Errorf("TilingProblem(%d) = %d; expected %d", tc.n, actual, tc.expected) + } + } + }) +} diff --git a/dynamic/wildcardmatching.go b/dynamic/wildcardmatching.go new file mode 100644 index 000000000..26b4530af --- /dev/null +++ b/dynamic/wildcardmatching.go @@ -0,0 +1,33 @@ +// wildcardmatching.go +// description: Solves the Wildcard Matching problem using dynamic programming +// reference: https://en.wikipedia.org/wiki/Wildcard_matching +// time complexity: O(m*n) +// space complexity: O(m*n) + +package dynamic + +// IsMatch checks if the string `s` matches the wildcard pattern `p` +func IsMatch(s, p string) bool { + dp := make([][]bool, len(s)+1) + for i := range dp { + dp[i] = make([]bool, len(p)+1) + } + + dp[0][0] = true + for j := 1; j <= len(p); j++ { + if p[j-1] == '*' { + dp[0][j] = dp[0][j-1] + } + } + + for i := 1; i <= len(s); i++ { + for j := 1; j <= len(p); j++ { + if p[j-1] == s[i-1] || p[j-1] == '?' { + dp[i][j] = dp[i-1][j-1] + } else if p[j-1] == '*' { + dp[i][j] = dp[i-1][j] || dp[i][j-1] + } + } + } + return dp[len(s)][len(p)] +} diff --git a/dynamic/wildcardmatching_test.go b/dynamic/wildcardmatching_test.go new file mode 100644 index 000000000..cc6cd4fde --- /dev/null +++ b/dynamic/wildcardmatching_test.go @@ -0,0 +1,44 @@ +package dynamic_test + +import ( + "testing" + + "github.com/TheAlgorithms/Go/dynamic" +) + +// testCaseWildcardMatching holds the test cases for the Wildcard Matching problem +type testCaseWildcardMatching struct { + s string + p string + expected bool +} + +// getWildcardMatchingTestCases returns a list of test cases for the Wildcard Matching problem +func getWildcardMatchingTestCases() []testCaseWildcardMatching { + return []testCaseWildcardMatching{ + {"aa", "a*", true}, // '*' can match zero or more characters + {"aa", "a", false}, // No match due to no wildcard + {"ab", "?*", true}, // '?' matches any single character, '*' matches remaining + {"abcd", "a*d", true}, // '*' matches the characters between 'a' and 'd' + {"abcd", "a*c", false}, // No match as 'c' doesn't match the last character 'd' + {"abc", "*", true}, // '*' matches the entire string + {"abc", "a*c", true}, // '*' matches 'b' + {"abc", "a?c", true}, // '?' matches 'b' + {"abc", "a?d", false}, // '?' cannot match 'd' + {"", "", true}, // Both strings empty, so they match + {"a", "?", true}, // '?' matches any single character + {"a", "*", true}, // '*' matches any number of characters, including one + } +} + +// TestIsMatch tests the IsMatch function with various test cases +func TestIsMatch(t *testing.T) { + t.Run("Wildcard Matching test cases", func(t *testing.T) { + for _, tc := range getWildcardMatchingTestCases() { + actual := dynamic.IsMatch(tc.s, tc.p) + if actual != tc.expected { + t.Errorf("IsMatch(%q, %q) = %v; expected %v", tc.s, tc.p, actual, tc.expected) + } + } + }) +} diff --git a/dynamic/wordbreak.go b/dynamic/wordbreak.go new file mode 100644 index 000000000..6d7ad5d58 --- /dev/null +++ b/dynamic/wordbreak.go @@ -0,0 +1,28 @@ +// wordbreak.go +// description: Solves the Word Break Problem using dynamic programming +// reference: https://en.wikipedia.org/wiki/Word_break_problem +// time complexity: O(n^2) +// space complexity: O(n) + +package dynamic + +// WordBreak checks if the input string can be segmented into words from a dictionary +func WordBreak(s string, wordDict []string) bool { + wordSet := make(map[string]bool) + for _, word := range wordDict { + wordSet[word] = true + } + + dp := make([]bool, len(s)+1) + dp[0] = true + + for i := 1; i <= len(s); i++ { + for j := 0; j < i; j++ { + if dp[j] && wordSet[s[j:i]] { + dp[i] = true + break + } + } + } + return dp[len(s)] +} diff --git a/dynamic/wordbreak_test.go b/dynamic/wordbreak_test.go new file mode 100644 index 000000000..afcf62cb6 --- /dev/null +++ b/dynamic/wordbreak_test.go @@ -0,0 +1,40 @@ +package dynamic_test + +import ( + "testing" + + "github.com/TheAlgorithms/Go/dynamic" +) + +type testCaseWordBreak struct { + s string + wordDict []string + expected bool +} + +func getWordBreakTestCases() []testCaseWordBreak { + return []testCaseWordBreak{ + {"leetcode", []string{"leet", "code"}, true}, // "leetcode" can be segmented into "leet" and "code" + {"applepenapple", []string{"apple", "pen"}, true}, // "applepenapple" can be segmented into "apple", "pen", "apple" + {"catsanddog", []string{"cats", "dog", "sand", "and", "cat"}, true}, // "catsanddog" can be segmented into "cats", "and", "dog" + {"bb", []string{"a", "b", "bbb", "aaaa", "aaa"}, true}, // "bb" can be segmented into "b" and "b" + {"", []string{"cat", "dog", "sand", "and"}, true}, // Empty string can always be segmented (empty words) + {"applepie", []string{"apple", "pie"}, true}, // "applepie" can be segmented into "apple" and "pie" + {"catsandog", []string{"cats", "dog", "sand", "and", "cat"}, false}, // "catsandog" cannot be segmented + {"ilovecoding", []string{"i", "love", "coding"}, true}, // "ilovecoding" can be segmented into "i", "love", "coding" + {"cars", []string{"car", "ca", "rs"}, true}, // "cars" can be segmented into "car" and "s" + {"pen", []string{"pen", "pencil"}, true}, // "pen" is a direct match + {"apple", []string{"orange", "banana"}, false}, // "apple" is not in the word dictionary + } +} + +func TestWordBreak(t *testing.T) { + t.Run("Word Break test cases", func(t *testing.T) { + for _, tc := range getWordBreakTestCases() { + actual := dynamic.WordBreak(tc.s, tc.wordDict) + if actual != tc.expected { + t.Errorf("WordBreak(%q, %v) = %v; expected %v", tc.s, tc.wordDict, actual, tc.expected) + } + } + }) +}