Skip to content

Commit 4f7a75d

Browse files
authored
Merge pull request #85 from anildervis/master
dp pages updated
2 parents 03b47ee + 78d09f8 commit 4f7a75d

16 files changed

+1002
-1
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Bitmask DP
2+
3+
## What is Bitmask?
4+
5+
Let’s say that we have a set of objects. How can we represent a subset of this set? One way is using a map and mapping each object with a Boolean value indicating whether the object is picked. Another way is, if the objects can be indexed by integers, we can use a Boolean array. However, this can be slow due to the operations of the map and array structures. If the size of the set is not too large (less than 64), a bitmask is much more useful and convenient.
6+
7+
An integer is a sequence of bits. Thus, we can use integers to represent a small set of Boolean values. We can perform all the set operations using bit operations. These bit operations are faster than map and array operations, and the time difference may be significant in some problems.
8+
9+
In a bitmask, the \( i \)-th bit from the right represents the \( i \)-th object. For example, let \( A = \{1, 2, 3, 4, 5\} \), we can represent \( B = \{1, 2, 4\} \) with the 11 (01011) bitmask.
10+
11+
---
12+
13+
## Bitmask Operations
14+
15+
- **Add the \( i \)-th object to the subset:**
16+
Set the \( i \)-th bit to 1:
17+
\( \text{mask } = \text{mask } | \text{ } (1 << i) \)
18+
19+
- **Remove the \( i \)-th object from the subset:**
20+
Set the \( i \)-th bit to 0:
21+
\( \text{mask } = \text{mask } \& \sim (1 << i) \)
22+
23+
- **Check whether the \( i \)-th object is in the subset:**
24+
Check if the \( i \)-th bit is set:
25+
\( \text{mask } \& \text{ } (1 << i) \).
26+
If the expression is equal to 1, the \( i \)-th object is in the subset. If the expression is equal to 0, the \( i \)-th object is not in the subset.
27+
28+
- **Toggle the existence of the \( i \)-th object:**
29+
XOR the \( i \)-th bit with 1, turning 1 into 0 and 0 into 1:
30+
\( \text{mask} = \text{mask}\) ^ \( (1 << i) \)
31+
32+
- **Count the number of objects in the subset:**
33+
Use a built-in function to count the number of 1’s in an integer variable:
34+
`__builtin_popcount(mask)` for integers or `__builtin_popcountll(mask)` for long longs.
35+
36+
---
37+
38+
## Iterating Over Subsets
39+
40+
- **Iterate through all subsets of a set with size \( n \):**
41+
\( \text{for (int x = 0; x < (1 << n); ++x)} \)
42+
43+
- **Iterate through all subsets of a subset with the mask \( y \):**
44+
\( \text{for (int x = y; x > 0; x = (y \& (x − 1)))} \)
45+
46+
---
47+
48+
## Task Assignment Problem
49+
50+
There are \( N \) people and \( N \) tasks, and each task is going to be allocated to a single person. We are also given a matrix `cost` of size \( N \times N \), where `cost[i][j]` denotes how much a person is going to charge for a task. We need to assign each task to a person such that the total cost is minimized. Note that each task is allocated to only one person, and each person is allocated only one task.
51+
52+
### Naive Approach:
53+
54+
Try \( N! \) possible assignments.
55+
**Time complexity:** \( O(N!) \).
56+
57+
### DP Approach:
58+
59+
For every possible subset, find the new subsets that can be generated from it and update the DP array. Here, we use bitmasking to represent subsets and iterate over them.
60+
**Time complexity:** \( O(2^N \times N) \).
61+
62+
**Note:** The [Hungarian Algorithm](https://en.wikipedia.org/wiki/Hungarian_algorithm) solves this problem in \( O(N^3) \) time complexity.
63+
64+
Solution code for DP approach:
65+
66+
```cpp
67+
for (int mask = 0; mask < (1 << n); ++mask)
68+
{
69+
for (int j = 0; j < n; ++j)
70+
{
71+
if((mask & (1 << j)) == 0) // jth task not assigned
72+
{
73+
dp[mask | (1 << j)] = min(dp[mask | (1 << j)], dp[mask] + cost[__builtin_popcount(mask)][j])
74+
}
75+
}
76+
}
77+
// after this operation our answer stored in dp[(1 << N) - 1]
78+
```
79+
80+
---
81+
82+
## References
83+
84+
- [Bitmask Tutorial on HackerEarth](https://www.hackerearth.com/practice/algorithms/dynamic-programming/bit-masking/tutorial/)
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# Common Dynamic Programming Problems
2+
3+
## Coin Problem
4+
5+
As discussed earlier, the Greedy approach doesn’t work all the time for the coin problem. For example, if the coins are \{4, 3, 1\} and the target sum is \(6\), the greedy algorithm produces the solution \(4+1+1\), while the optimal solution is \(3+3\). This is where Dynamic Programming (DP) helps.
6+
7+
### Solution
8+
9+
#### Approach:
10+
11+
1. If \( V == 0 \), then 0 coins are required.
12+
2. If \( V > 0 \), compute \( \text{minCoins}(coins[0..m-1], V) = \min \{ 1 + \text{minCoins}(V - \text{coin}[i]) \} \) for all \( i \) where \( \text{coin}[i] \leq V \).
13+
14+
```python
15+
def minCoins(coins, target):
16+
# base case
17+
if (V == 0):
18+
return 0
19+
20+
n = len(coins)
21+
# Initialize result
22+
res = sys.maxsize
23+
24+
# Try every coin that has smaller value than V
25+
for i in range(0, n):
26+
if (coins[i] <= target):
27+
sub_res = minCoins(coins, target-coins[i])
28+
29+
# Check for INT_MAX to avoid overflow and see if
30+
# result can minimized
31+
if (sub_res != sys.maxsize and sub_res + 1 < res):
32+
res = sub_res + 1
33+
34+
return res
35+
```
36+
37+
## Knapsack Problem
38+
39+
We are given the weights and values of \( n \) items, and we are to put these items in a knapsack of capacity \( W \) to get the maximum total value. In other words, we are given two integer arrays `val[0..n-1]` and `wt[0..n-1]`, which represent the values and weights associated with \( n \) items. We are also given an integer \( W \), which represents the knapsack's capacity. Our goal is to find out the maximum value subset of `val[]` such that the sum of the weights of this subset is smaller than or equal to \( W \). We cannot break an item; we must either pick the complete item or leave it.
40+
41+
#### Approach:
42+
43+
There are two cases for every item:
44+
1. The item is included in the optimal subset.
45+
2. The item is not included in the optimal subset.
46+
47+
The maximum value that can be obtained from \( n \) items is the maximum of the following two values:
48+
1. Maximum value obtained by \( n-1 \) items and \( W \) weight (excluding the \( n \)-th item).
49+
2. Value of the \( n \)-th item plus the maximum value obtained by \( n-1 \) items and \( W - \text{weight of the } n \)-th item (including the \( n \)-th item).
50+
51+
If the weight of the \( n \)-th item is greater than \( W \), then the \( n \)-th item cannot be included, and case 1 is the only possibility.
52+
53+
For example:
54+
55+
- Knapsack max weight: \( W = 8 \) units
56+
- Weight of items: \( \text{wt} = \{3, 1, 4, 5\} \)
57+
- Values of items: \( \text{val} = \{10, 40, 30, 50\} \)
58+
- Total items: \( n = 4 \)
59+
60+
The sum \( 8 \) is possible with two combinations: \{3, 5\} with a total value of 60, and \{1, 3, 4\} with a total value of 80. However, a better solution is \{1, 5\}, which has a total weight of 6 and a total value of 90.
61+
62+
### Recursive Solution
63+
64+
```python
65+
def knapSack(W , wt , val , n):
66+
67+
# Base Case
68+
if (n == 0 or W == 0):
69+
return 0
70+
71+
# If weight of the nth item is more than Knapsack of capacity
72+
# W, then this item cannot be included in the optimal solution
73+
if (wt[n-1] > W):
74+
return knapSack(W, wt, val, n - 1)
75+
76+
# return the maximum of two cases:
77+
# (1) nth item included
78+
# (2) not included
79+
else:
80+
return max(val[n-1] + knapSack(W - wt[n - 1], wt, val, n - 1), knapSack(W, wt, val, n - 1))
81+
```
82+
83+
### Dynamic Programming Solution
84+
85+
It should be noted that the above function computes the same subproblems again and again. Time complexity of this naive recursive solution is exponential \(2^n\).
86+
Since suproblems are evaluated again, this problem has Overlapping Subproblems property. Like other typical Dynamic Programming(DP) problems, recomputations of same subproblems can be avoided by constructing a temporary array \(K[][]\) in bottom up manner. Following is Dynamic Programming based implementation.
87+
88+
```python
89+
def knapSack(W, wt, val, n):
90+
K = [[0 for x in range(W + 1)] for x in range(n + 1)]
91+
92+
# Build table K[][] in bottom up manner
93+
for (i in range(n + 1)):
94+
for (w in range(W + 1)):
95+
if (i == 0 or w == 0):
96+
K[i][w] = 0
97+
elif (wt[i - 1] <= w):
98+
K[i][w] = max(val[i - 1] + K[i - 1][w - wt[i - 1]], K[i - 1][w])
99+
else:
100+
K[i][w] = K[i - 1][w]
101+
102+
return K[n][W]
103+
```
104+
105+
## Longest Common Substring (LCS) Problem
106+
107+
We are given two strings \( X \) and \( Y \), and our task is to find the length of the longest common substring.
108+
109+
### Sample Case:
110+
111+
- Input: \( X = "inzvahackerspace" \), \( Y = "spoilerspoiler" \)
112+
- Output: 4
113+
114+
The longest common substring is "ersp" and is of length 4.
115+
116+
#### Approach:
117+
118+
Let \( m \) and \( n \) be the lengths of the first and second strings, respectively. A simple solution is to consider all substrings of the first string one by one and check if they are substrings of the second string. Keep track of the maximum-length substring. There will be \( O(m^2) \) substrings, and checking if one is a substring of the other will take \( O(n) \) time. Thus, the overall time complexity is \( O(n \cdot m^2) \).
119+
120+
Dynamic programming can reduce this to \( O(m \cdot n) \). The idea is to find the length of the longest common suffix for all substrings of both strings and store these lengths in a table. The longest common suffix has the following property:
121+
122+
\[
123+
LCSuff(X, Y, m, n) = LCSuff(X, Y, m-1, n-1) + 1 \text{ if } X[m-1] = Y[n-1]
124+
\]
125+
Otherwise, \( LCSuff(X, Y, m, n) = 0 \).
126+
127+
The maximum length of the Longest Common Suffix is the Longest Common Substring.
128+
129+
### DP - Iterative
130+
131+
```python
132+
def LCSubStr(X, Y):
133+
m = len(X)
134+
n = len(Y)
135+
136+
# Create a table to store lengths of
137+
# longest common suffixes of substrings.
138+
# Note that LCSuff[i][j] contains the
139+
# length of longest common suffix of
140+
# X[0...i−1] and Y[0...j−1]. The first
141+
# row and first column entries have no
142+
# logical meaning, they are used only
143+
# for simplicity of the program.
144+
145+
# LCSuff is the table with zero
146+
# value initially in each cell
147+
LCSuff = [[0 for k in range(n+1)] for l in range(m + 1)]
148+
149+
# To store the length of
150+
# longest common substring
151+
result = 0
152+
153+
# Following steps to build
154+
# LCSuff[m+1][n+1] in bottom up fashion
155+
for (i in range(m + 1)):
156+
for (j in range(n + 1)):
157+
if (i == 0 or j == 0):
158+
LCSuff[i][j] = 0
159+
elif (X[i - 1] == Y[j - 1]):
160+
LCSuff[i][j] = LCSuff[i - 1][j - 1] + 1
161+
result = max(result, LCSuff[i][j])
162+
else:
163+
LCSuff[i][j] = 0
164+
return result
165+
```
166+
167+
### DP - Recursive
168+
169+
```python
170+
def lcs(int i, int j, int count):
171+
if (i == 0 or j == 0):
172+
return count
173+
174+
if (X[i - 1] == Y[j - 1]):
175+
count = lcs(i - 1, j - 1, count + 1)
176+
177+
count = max(count, max(lcs(i, j - 1, 0), lcs(i - 1, j, 0)))
178+
return count
179+
```
180+
181+
## Longest Increasing Subsequence (LIS) Problem
182+
183+
The Longest Increasing Subsequence (LIS) problem is to find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
184+
185+
For example, given the array \([0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]\), the longest increasing subsequence has a length of 6, and it is \{0, 2, 6, 9, 11, 15\}.
186+
187+
### Solution
188+
189+
A naive, brute-force approach is to generate every possible subsequence, check for monotonicity, and keep track of the longest one. However, this is prohibitively expensive, as generating each subsequence takes \( O(2^N) \) time.
190+
191+
Instead, we can use recursion to solve this problem and then optimize it with dynamic programming. We assume that we have a function that gives us the length of the longest increasing subsequence up to a certain index.
192+
193+
The base cases are:
194+
- The empty list, which returns 0.
195+
- A list with one element, which returns 1.
196+
197+
For every index \( i \), calculate the longest increasing subsequence up to that point. The result can only be extended with the last element if the last element is greater than \( \text{arr}[i] \), as otherwise, the sequence wouldn’t be increasing.
198+
199+
```python
200+
def longest_increasing_subsequence(arr):
201+
if (not arr):
202+
return 0
203+
if (len(arr) == 1):
204+
return 1
205+
206+
max_ending_here = 0
207+
for (i in range(len(arr))):
208+
ending_at_i = longest_increasing_subsequence(arr[:i])
209+
if (arr[-1] > arr[i - 1] and ending_at_i + 1 > max_ending_here):
210+
max_ending_here = ending_at_i + 1
211+
return max_ending_here
212+
```
213+
214+
This is really slow due to repeated subcomputations (exponential in time). So, let’s use dynamic
215+
programming to store values to recompute them for later.
216+
217+
We’ll keep an array A of length N, and A[i] will contain the length of the longest increasing subsequence ending at i. We can then use the same recurrence but look it up in the array instead:
218+
219+
```python
220+
def longest_increasing_subsequence(arr):
221+
if (not arr):
222+
return 0
223+
cache = [1] * len(arr)
224+
for (i in range(1, len(arr))):
225+
for (j in range(i)):
226+
if (arr[i] > arr[j]):
227+
cache[i] = max(cache[i], cache[j] + 1)
228+
return max(cache)
229+
```
230+
231+
This now runs in \( O(N^2) \) time and \( O(N) \) space.

0 commit comments

Comments
 (0)