Dynamic programming is all about ordering your computations in a way that avoids recalculating duplicate work. More specifically, Dynamic Programming is a technique used to avoid computing multiple times the same subproblem in a recursive algorithm. DP algorithms could be implemented with recursion, but they don't have to be. Follow along and learn 12 Most Common Dynamic Programming Interview Questions and Answers to nail your next coding interview.
Dynamic programming is all about ordering your computations in a way that avoids recalculating duplicate work. More specifically, Dynamic Programming is a technique used to avoid computing multiple times the same subproblem in a recursive algorithm. DP algorithms could be implemented with recursion, but they don't have to be.
With dynamic programming, you store your results in some sort of table generally. When you need the answer to a problem, you reference the table and see if you already know what it is. If not, you use the data in your table to give yourself a stepping stone towards the answer.
There are two approaches to apply Dynamic Programming:
TOP of the tree
fib(4)
fib(3)...................... + fib(2)
fib(2)......... + fib(1) fib(1)........... + fib(0)
fib(1) + fib(0) fib(1) fib(1) fib(0)
fib(1) fib(0)
BOTTOM of the treePros:
Cons:
fib(106)), you will run out of stack space, because each delayed computation must be put on the stack, and you will have 106 of them.The key idea of DP is to save answers of overlapping smaller sub-problems to avoid recomputation. For that:
The longest increasing subsequence problem is to find a subsequence of a given sequence in which the subsequence's elements are in sorted order, lowest to highest, and in which the subsequence is as long as possible.
Consider:
In the first 16 terms of the binary Van der Corput sequence
0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15a longest increasing subsequence is
0, 2, 6, 9, 11, 15.This subsequence has length six; the input sequence has no seven-member increasing subsequences. The longest increasing subsequence in this example is not unique: for instance,
0, 4, 6, 9, 11, 15 or
0, 2, 6, 9, 13, 15 or
0, 4, 6, 9, 13, 15are other increasing subsequences of equal length in the same input sequence.
DP[i] to be the length of the LIS (Longest increasing subsequence) which is ending at element with index i. DP[0] = 1; // length of LIS for the first element is always 1
int maxLength = 1;To compute DP[i] for each i > 0 we look at all indices j < i and check both:
DP[j] + 1 > DP[i] and array[j] < array[i] (we want it to be increasing). If this is true we can update the current optimum for DP[i].
for (int i = 1; i < N; i++)
{
DP[i] = 1;
prev[i] = -1;
for (int j = i - 1; j >= 0; j--)
if (DP[j] + 1 > DP[i] && array[j] < array[i])
{
DP[i] = DP[j] + 1;
prev[i] = j;
}
if (DP[i] > maxLength)
{
bestEnd = i;
maxLength = DP[i];
}
}[0...N - 1]prev to be able later to find the actual sequence not only its length. Just go back recursively from bestEnd in a loop using prev[bestEnd]. The -1 value is a sign to stop.O(n^2). Two loops of n are there.O(n). DP array of size n is used./**
* Dynamic programming approach to find longest increasing subsequence.
* Complexity: O(n * n)
*
* @param {number[]} sequence
* @return {number}
*/
export default function dpLongestIncreasingSubsequence(sequence) {
// Create array with longest increasing substrings length and
// fill it with 1-s that would mean that each element of the sequence
// is itself a minimum increasing subsequence.
const lengthsArray = Array(sequence.length).fill(1);
let previousElementIndex = 0;
let currentElementIndex = 1;
while (currentElementIndex < sequence.length) {
if (sequence[previousElementIndex] < sequence[currentElementIndex]) {
// If current element is bigger then the previous one then
// current element is a part of increasing subsequence which
// length is by one bigger then the length of increasing subsequence
// for previous element.
const newLength = lengthsArray[previousElementIndex] + 1;
if (newLength > lengthsArray[currentElementIndex]) {
// Increase only if previous element would give us bigger subsequence length
// then we already have for current element.
lengthsArray[currentElementIndex] = newLength;
}
}
// Move previous element index right.
previousElementIndex += 1;
// If previous element index equals to current element index then
// shift current element right and reset previous element index to zero.
if (previousElementIndex === currentElementIndex) {
currentElementIndex += 1;
previousElementIndex = 0;
}
}
// Find the biggest element in lengthsArray.
// This number is the biggest length of increasing subsequence.
let longestIncreasingLength = 0;
for (let i = 0; i < lengthsArray.length; i += 1) {
if (lengthsArray[i] > longestIncreasingLength) {
longestIncreasingLength = lengthsArray[i];
}
}
return longestIncreasingLength;
}The following would be considered DP, but without recursion (using bottom-up or tabulation DP approach).
int fibresult[N];
void setup_fib()
{
fibresult[0] = 1;
fibresult[1] = 1;
for (int i = 2; i < N; i++)
fibresult[i] = fibresult[i-1] + fibresult[i-2];
}
int fib(int x) { return fibresult[x]; }This way may be described as "eager", "precaching" or "iterative". Its faster overall but we have to manually figure out the order the subproblems need to be calculated in. This is easy for fibonacci, but for more complex DP problems it gets harder, and so we fall back to the lazy recursive method if it is fast enough.
Pros:
Cons:
Dynamic programming is an extension of Divide and Conquer paradigm.
Two things to consider when deciding which algorithm to use
fibonacci(n) = fibonacci(n-1) + fibonacci(n-2), we only need to store the past two calculationsThat being said, bottom-up is not always the best choice, I will try to illustrate with examples:
O(1) vs O(weight)nmlog(nm) pre-processing time before DPRust has been Stack Overflow’s most loved language for four years in a row and emerged as a compelling language choice for both backend and system developers, offering a unique combination of memory safety, performance, concurrency without Data races...
Clean Architecture provides a clear and modular structure for building software systems, separating business rules from implementation details. It promotes maintainability by allowing for easier updates and changes to specific components without affe...
Azure Service Bus is a crucial component for Azure cloud developers as it provides reliable and scalable messaging capabilities. It enables decoupled communication between different components of a distributed system, promoting flexibility and resili...