diff --git a/dsa-solutions/lc-solutions/0300-0399/0312-Burst-ballons.md b/dsa-solutions/lc-solutions/0300-0399/0312-Burst-ballons.md
new file mode 100644
index 000000000..646b41ed6
--- /dev/null
+++ b/dsa-solutions/lc-solutions/0300-0399/0312-Burst-ballons.md
@@ -0,0 +1,337 @@
+---
+id: burst-ballons
+title: Burst-Ballons
+sidebar_label: 0312 - Burst Ballons
+tags:
+- Array
+- Dynamic Programming
+
+description: "This is a solution to the Burst Ballons problem on LeetCode."
+---
+
+## Problem Description
+
+You are given `n` balloons, indexed from `0` to `n - 1`. Each balloon is painted with a number on it represented by an array nums. You are asked to burst all the balloons.
+
+If you burst the ith balloon, you will get `nums[i - 1] * nums[i] * nums[i + 1]` coins. If `i - 1` or `i + 1` goes out of bounds of the array, then treat it as if there is a balloon with a 1 painted on it.
+
+Return the maximum coins you can collect by bursting the balloons wisely.
+
+### Examples
+
+**Example 1:**
+
+```
+Input: nums = [3,1,5,8]
+Output: 167
+Explanation:
+nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
+coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
+
+```
+
+**Example 2:**
+
+```
+Input: nums = [1,5]
+Output: 10
+
+```
+
+### Constraints
+
+- `n == nums.length`
+- `1 <= n <= 300`
+- `0 <= nums[i] <= 100`
+
+## Solution for Burst Ballons Problem
+
+
+### Brute Force - Recursion
+
+#### Intuition
+When I first get this problem, it is far from dynamic programming to me. I started with the most naive idea the backtracking.
+
+We have n balloons to burst, which mean we have n steps in the game. In the i th step we have `n-i` balloons to burst, `i = 0~n-1`. Therefore we are looking at an algorithm of $O(n!)$. Well, it is slow, probably works for `n < 12` only.
+
+Of course this is not the point to implement it. We need to identify the redundant works we did in it and try to optimize.
+
+Well, we can find that for any balloons left the maxCoins does not depends on the balloons already bursted. This indicate that we can use memorization (top down) or dynamic programming (bottom up) for all the cases from small numbers of balloon until n balloons. How many cases are there? For k balloons there are `C(n, k)` cases and for each case it need to scan the k balloons to compare. The sum is quite big still. It is better than $O(n!)$ but worse than $O(2^n)$.
+
+
+#### Solution Using Recursion
+
+
+
+#### Implementation
+
+```jsx live
+function coinChangeWrapper(arr) {
+ class Solution {
+ int solve(vectornums){
+ if(nums.size()==0){
+ return 0;
+ }
+ int maxi=-1e9;
+ for(int i=0;i=0){
+ product=product*nums[i-1];
+ }
+ if(i+1& nums) {
+ return solve(nums);
+ }
+};
+
+
+ const input = [3,1,5,8]
+ const sum = 167
+ const output = maxCoins(input)
+ return (
+
+
+ Input: { JSON.stringify(input)}
+
+
+ Output: {output.toString()}
+
+
+ );
+}
+```
+
+#### Code in Different Languages
+
+
+
+
+ ```python
+ class Solution:
+ def solve(self, nums):
+ if len(nums) == 0:
+ return 0
+
+ maxi = -1e9
+ for i in range(len(nums)):
+ product = nums[i]
+ k = nums[i]
+ if i - 1 >= 0:
+ product *= nums[i - 1]
+ if i + 1 < len(nums):
+ product *= nums[i + 1]
+ nums.pop(i)
+ maxi = max(maxi, product + self.solve(nums))
+ nums.insert(i, k)
+ return maxi
+
+ def maxCoins(self, nums):
+ return self.solve(nums)
+
+```
+
+
+
+
+
+```
+import java.util.ArrayList;
+import java.util.List;
+
+class Solution {
+ private int solve(List nums) {
+ if (nums.size() == 0) {
+ return 0;
+ }
+
+ int maxi = Integer.MIN_VALUE;
+ for (int i = 0; i < nums.size(); i++) {
+ int product = nums.get(i);
+ int k = nums.get(i);
+
+ if (i - 1 >= 0) {
+ product *= nums.get(i - 1);
+ }
+ if (i + 1 < nums.size()) {
+ product *= nums.get(i + 1);
+ }
+
+ nums.remove(i);
+ maxi = Math.max(maxi, product + solve(new ArrayList<>(nums)));
+ nums.add(i, k);
+ }
+ return maxi;
+ }
+
+ public int maxCoins(int[] nums) {
+ List numList = new ArrayList<>();
+ for (int num : nums) {
+ numList.add(num);
+ }
+ return solve(numList);
+ }
+}
+
+
+```
+
+
+
+
+ ```cpp
+
+ class Solution {
+ int solve(vectornums){
+ if(nums.size()==0){
+ return 0;
+ }
+ int maxi=-1e9;
+ for(int i=0;i=0){
+ product=product*nums[i-1];
+ }
+ if(i+1& nums) {
+ return solve(nums);
+ }
+};
+ ```
+
+
+
+
+#### Complexity Analysis
+
+- Time Complexity: $ O(2^n) $ is the time complexity, where n is the number of coins , because it has Overlapping subproblems
+- Space Complexity: This approach doesn't need any auxilary space, but it maintains a recusion stack internally.
+If we consider the recursion tree, there is a repetition of a few sub-trees.
+
+### Optimized Approach - Memoization
+#### Intuition
+ We then think can we apply the divide and conquer technique? After all there seems to be many self similar sub problems from the previous analysis.
+
+Well, the nature way to divide the problem is burst one balloon and separate the balloons into `2` sub sections one on the left and one one the right. However, in this problem the left and right become adjacent and have effects on the maxCoins in the future.
+
+Then another interesting idea come up. Which is quite often seen in dp problem analysis. That is reverse thinking. Like I said the coins you get for a balloon does not depend on the balloons already burst. Therefore
+instead of divide the problem by the first balloon to burst, we divide the problem by the last balloon to burst.
+
+Why is that? Because only the first and last balloons we are sure of their adjacent balloons before hand!
+
+For the first we have `nums[i-1]*nums[i]*nums[i+1]` for the last we have `nums[-1]*nums[i]*nums[n]`.
+
+OK. Think about n balloons if i is the last one to burst, what now?
+
+We can see that the balloons is again separated into 2 sections. But this time since the balloon i is the last balloon of all to burst, the left and right section now has well defined boundary and do not affect each other! Therefore we can do either recursive method with memoization or dp.
+
+
+
+
+
+ ```cpp
+ int solve(vector&nums,int low,int high,vector>&dp){
+ if(low>high){
+ return 0;
+ }
+ if(dp[low][high]!=-1){
+ return dp[low][high];
+ }
+ int maxi=INT_MIN;
+ for(int i=low;i<=high;i++){
+ int cost=nums[i]*nums[low-1]*nums[high+1]+solve(nums,low,i-1,dp)+solve(nums,i+1,high,dp);
+ maxi=max(maxi,cost);
+ }
+ return dp[low][high]=maxi;
+ }
+public:
+ int maxCoins(vector& nums) {
+ int n=nums.size();
+ nums.insert(nums.begin(),1);
+ nums.push_back(1);
+ vector>dp(n+2,vector(n+2,0));
+ // return solve(nums,1,n,dp);
+ for(int i=n;i>=1;i--){
+ for(int j=1;j<=n;j++){
+ if(i>j){
+ continue;
+ }
+ int maxi=INT_MIN;
+ for(int ind=i;ind<=j;ind++){
+ int cost=nums[ind]*nums[i-1]*nums[j+1]+dp[ind+1][j]+dp[i][ind-1];
+ maxi=max(cost,maxi);
+ }
+ dp[i][j]=maxi;
+ }
+ }
+ return dp[1][n];
+ }
+};
+ ```
+
+
+
+
+#### Complexity Analysis
+- Time Complexity: $ O(N^3)$
+ - Reason: There are N*A states therefore at max ‘N*A’ new problems will be solved. A is amount.
+- Space Complexity: $O(N*N)$
+ - Reason: We are using a recursion stack space(O(N)) and a 2D array ( O(N*A)).
+
+### Using Tabulation Method
+#### Intuition
+ Here comes the final solutions. Note that we put 2 balloons with 1 as boundaries and also burst all the zero balloons in the first round since they won't give any coins.
+The algorithm runs in $O(n^3)$ which can be easily seen from the 3 loops in dp solution.
+
+
+
+
+ ```cpp
+ int maxCoinsDP(vector &iNums) {
+ int nums[iNums.size() + 2];
+ int n = 1;
+ for (int x : iNums) if (x > 0) nums[n++] = x;
+ nums[0] = nums[n++] = 1;
+
+
+ int dp[n][n] = {};
+ for (int k = 2; k < n; ++k) {
+ for (int left = 0; left < n - k; ++left)
+ int right = left + k;
+ for (int i = left + 1; i < right; ++i)
+ dp[left][right] = max(dp[left][right],
+ nums[left] * nums[i] * nums[right] + dp[left][i] + dp[i][right]);
+ }
+
+ return dp[0][n - 1];
+}
+ ```
+ - Here Time and Space Complexity are same as memoized approach.
+
+
+
+
+
+## References
+
+- **LeetCode Problem**: [Coin Change](https://leetcode.com/problems/burst-balloons/description/)
+
diff --git a/dsa/Algorithms/String-Algorithms/03-Z-Algorithm.md b/dsa/Algorithms/String-Algorithms/03-Z-Algorithm.md
new file mode 100644
index 000000000..309217040
--- /dev/null
+++ b/dsa/Algorithms/String-Algorithms/03-Z-Algorithm.md
@@ -0,0 +1,392 @@
+---
+id: Z Algorithm
+title: Z Algorithm
+sidebar_label: Z-Algorithm
+tags:
+ - Intermediate
+ - String Matching Algorithms
+ - CPP
+ - Python
+ - Java
+ - DSA
+description: "This is a solution for the string matching in linear time using Z algorithm."
+---
+
+## 1. What is the Z Algorithm?
+
+This algorithm finds all occurrences of a pattern in a text in linear time. Let length of text be n and of pattern be m, then total time taken is `O(m + n)` with linear space complexity. Now we can see that both time and space complexity is same as KMP algorithm but this algorithm is Simpler to understand.
+In this algorithm, we construct a Z array.
+
+## 2. What is Z Array?
+
+For a string `str[0..n-1]`, Z array is of same length as string. An element Z[i] of Z array stores length of the longest substring starting from `str[i]` which is also a prefix of `str[0..n-1]`. The first entry of Z array is meaning less as complete string is always prefix of itself.
+
+#### Examples
+```
+Index 0 1 2 3 4 5 6 7 8 9 10 11
+Text a a b c a a b x a a a z
+Z values X 1 0 0 3 1 0 0 2 2 1 0
+```
+## 3. How to construct Z array?
+
+A Simple Solution is to run two nested loops, the outer loop goes to every index and the inner loop finds length of the longest prefix that matches the substring starting at the current index. The time complexity of this solution is $O(n2)$.
+ We can construct Z array in linear time.
+
+```
+The idea is to maintain an interval [L, R] which is the interval with max R
+such that [L,R] is prefix substring (substring which is also prefix).
+
+Steps for maintaining this interval are as follows –
+
+1) If i > R then there is no prefix substring that starts before i and
+ ends after i, so we reset L and R and compute new [L,R] by comparing
+ str[0..] to str[i..] and get Z[i] (= R-L+1).
+
+2) If i <= R then let K = i-L, now Z[i] >= min(Z[K], R-i+1) because
+ str[i..] matches with str[K..] for atleast R-i+1 characters (they are in
+ [L,R] interval which we know is a prefix substring).
+ Now two sub cases arise –
+ a) If Z[K] < R-i+1 then there is no prefix substring starting at
+ str[i] (otherwise Z[K] would be larger) so Z[i] = Z[K] and
+ interval [L,R] remains same.
+ b) If Z[K] >= R-i+1 then it is possible to extend the [L,R] interval
+ thus we will set L as i and start matching from str[R] onwards and
+ get new R then we will update interval [L,R] and calculate Z[i] (=R-L+1).
+
+```
+
+
+
+
+## 4. Problem Description
+
+Given a text string and a pattern string, implement the Z algorithm to find all occurrences of the pattern in the text.
+
+## 5. Examples
+
+**Example 1:**
+```
+Input: text = "GEEKS FOR GEEKS", pattern = "GEEK"
+Output: Pattern found at index 0, Pattern found at index 10
+```
+
+**Example 2:**
+```
+Input: text = "ABABDABACDABABCABAB", pattern = "ABAB"
+Output: Pattern found at index 0, Pattern found at index 10, Pattern found at index 12
+```
+
+**Explanation of Example 1:**
+- The pattern "GEEK" is found in the text "GEEKS FOR GEEKS" starting from index 0 and index 10.
+
+## 6. Constraints
+
+- $The text and pattern can contain any number of characters.$
+- $All characters are ASCII characters.$
+
+## 7. Implementation
+
+
+
+ ```python
+ def getZarr(string, z):
+ n = len(string)
+
+ # [L,R] make a window which matches
+ # with prefix of s
+ l, r, k = 0, 0, 0
+ for i in range(1, n):
+
+ # if i>R nothing matches so we will calculate.
+ # Z[i] using naive way.
+ if i > r:
+ l, r = i, i
+
+ # R-L = 0 in starting, so it will start
+ # checking from 0'th index. For example,
+ # for "ababab" and i = 1, the value of R
+ # remains 0 and Z[i] becomes 0. For string
+ # "aaaaaa" and i = 1, Z[i] and R become 5
+ while r < n and string[r - l] == string[r]:
+ r += 1
+ z[i] = r - l
+ r -= 1
+ else:
+
+ # k = i-L so k corresponds to number which
+ # matches in [L,R] interval.
+ k = i - l
+
+ # if Z[k] is less than remaining interval
+ # then Z[i] will be equal to Z[k].
+ # For example, str = "ababab", i = 3, R = 5
+ # and L = 2
+ if z[k] < r - i + 1:
+ z[i] = z[k]
+
+ # For example str = "aaaaaa" and i = 2,
+ # R is 5, L is 0
+ else:
+
+ # else start from R and check manually
+ l = i
+ while r < n and string[r - l] == string[r]:
+ r += 1
+ z[i] = r - l
+ r -= 1
+
+# prints all occurrences of pattern
+# in text using Z algo
+def search(text, pattern):
+
+ # Create concatenated string "P$T"
+ concat = pattern + "$" + text
+ l = len(concat)
+
+ # Construct Z array
+ z = [0] * l
+ getZarr(concat, z)
+
+ # now looping through Z array for matching condition
+ for i in range(l):
+
+ # if Z[i] (matched region) is equal to pattern
+ # length we got the pattern
+ if z[i] == len(pattern):
+ print("Pattern found at index",
+ i - len(pattern) - 1)
+
+
+ if __name__ == "__main__":
+ text = "GEEKS FOR GEEKS"
+ pattern = "GEEK"
+ search(text, pattern)
+
+
+```
+
+
+
+ ```cpp
+#include
+using namespace std;
+
+void getZarr(string str, int Z[]);
+
+// prints all occurrences of pattern in text using Z algo
+void search(string text, string pattern)
+{
+ // Create concatenated string "P$T"
+ string concat = pattern + "$" + text;
+ int l = concat.length();
+
+ // Construct Z array
+ int Z[l];
+ getZarr(concat, Z);
+
+ // now looping through Z array for matching condition
+ for (int i = 0; i < l; ++i)
+ {
+ // if Z[i] (matched region) is equal to pattern
+ // length we got the pattern
+ if (Z[i] == pattern.length())
+ cout << "Pattern found at index "
+ << i - pattern.length() -1 << endl;
+ }
+}
+
+// Fills Z array for given string str[]
+void getZarr(string str, int Z[])
+{
+ int n = str.length();
+ int L, R, k;
+
+ // [L,R] make a window which matches with prefix of s
+ L = R = 0;
+ for (int i = 1; i < n; ++i)
+ {
+ // if i>R nothing matches so we will calculate.
+ // Z[i] using naive way.
+ if (i > R)
+ {
+ L = R = i;
+
+ // R-L = 0 in starting, so it will start
+ // checking from 0'th index. For example,
+ // for "ababab" and i = 1, the value of R
+ // remains 0 and Z[i] becomes 0. For string
+ // "aaaaaa" and i = 1, Z[i] and R become 5
+ while (R
+
+
+ ```java
+
+class GFG {
+
+ // prints all occurrences of pattern in text using
+ // Z algo
+ public static void search(String text, String pattern)
+ {
+
+ // Create concatenated string "P$T"
+ String concat = pattern + "$" + text;
+
+ int l = concat.length();
+
+ int Z[] = new int[l];
+
+ // Construct Z array
+ getZarr(concat, Z);
+
+ // now looping through Z array for matching condition
+ for(int i = 0; i < l; ++i){
+
+ // if Z[i] (matched region) is equal to pattern
+ // length we got the pattern
+
+ if(Z[i] == pattern.length()){
+ System.out.println("Pattern found at index "
+ + (i - pattern.length() - 1));
+ }
+ }
+ }
+
+ // Fills Z array for given string str[]
+ private static void getZarr(String str, int[] Z) {
+
+ int n = str.length();
+
+ // [L,R] make a window which matches with
+ // prefix of s
+ int L = 0, R = 0;
+
+ for(int i = 1; i < n; ++i) {
+
+ // if i>R nothing matches so we will calculate.
+ // Z[i] using naive way.
+ if(i > R){
+
+ L = R = i;
+
+ // R-L = 0 in starting, so it will start
+ // checking from 0'th index. For example,
+ // for "ababab" and i = 1, the value of R
+ // remains 0 and Z[i] becomes 0. For string
+ // "aaaaaa" and i = 1, Z[i] and R become 5
+
+ while(R < n && str.charAt(R - L) == str.charAt(R))
+ R++;
+
+ Z[i] = R - L;
+ R--;
+
+ }
+ else{
+
+ // k = i-L so k corresponds to number which
+ // matches in [L,R] interval.
+ int k = i - L;
+
+ // if Z[k] is less than remaining interval
+ // then Z[i] will be equal to Z[k].
+ // For example, str = "ababab", i = 3, R = 5
+ // and L = 2
+ if(Z[k] < R - i + 1)
+ Z[i] = Z[k];
+
+ // For example str = "aaaaaa" and i = 2, R is 5,
+ // L is 0
+ else{
+
+
+ // else start from R and check manually
+ L = i;
+ while(R < n && str.charAt(R - L) == str.charAt(R))
+ R++;
+
+ Z[i] = R - L;
+ R--;
+ }
+ }
+ }
+ }
+
+ // Driver program
+ public static void main(String[] args)
+ {
+ String text = "GEEKS FOR GEEKS";
+ String pattern = "GEEK";
+
+ search(text, pattern);
+ }
+}
+
+ ```
+
+
+
+## 8. Complexity Analysis
+
+- **Time Complexity**:
+ - Average and Best Case: $O(N + M)$
+
+- **Space Complexity**: $O(N)$ .
+
+## 9. Advantages and Disadvantages
+
+**Advantages:**
+- Efficient on average with good hash functions.
+- Suitable for multiple pattern searches in a single text.
+
+**Disadvantages
+
+:**
+- Hash collisions can degrade performance to $O(N * M)$.
+- Requires a good hash function to minimize collisions.
+
+## 10. References
+
+- **GFG Problem:** [GFG Problem](https://www.geeksforgeeks.org/z-algorithm-linear-time-pattern-searching-algorithm/)