Dynamic programming algorithm and its implementation in JavaScript

Dynamic programming is a very important algorithmic idea, and it has a wide range of applications, such as in computer science, artificial intelligence, economics, operations research, biology and other fields. The key of the dynamic programming algorithm is to decompose the problem into sub-problems, and solve the optimal solution of the sub-problems in a recursive manner, so as to derive the optimal solution of the original problem.

The main applications of dynamic programming algorithms include the following aspects:

  1. Shortest Path Problem: In a graph, find the shortest path from the start point to the end point.
  2. Maximum subsegment sum problem: In an array, find the maximum value of the sum of a contiguous subarray.
  3. Knapsack problem: There is a knapsack with capacity C, given n items, each item has a volume and a value, find the maximum total value of the items that can be loaded without exceeding the capacity of the knapsack.
  4. Longest Common Subsequence Problem: Given two strings, find their longest common subsequence.
  5. Edit distance problem: Given two strings, find the edit distance between them, that is, the minimum number of operations required to convert one string into the other.

In addition to the above applications, dynamic programming algorithms can also be used to solve many other types of optimization problems, such as minimizing costs, maximizing benefits, minimizing risks, etc.

The following is a JavaScript implementation of the dynamic programming algorithm for a classic knapsack problem:

function knapsack(weights, values, capacity) {
    
    
  let n = weights.length;
  let dp = [];
  for(let i = 0; i <= n; i++) {
    
    
    dp[i] = [];
    for(let j = 0; j <= capacity; j++) {
    
    
      if(i === 0 || j === 0) {
    
    
        dp[i][j] = 0;
      } else if(weights[i-1] > j) {
    
    
        dp[i][j] = dp[i-1][j];
      } else {
    
    
        dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weights[i-1]] + values[i-1]);
      }
    }
  }
  return dp[n][capacity];
}

let weights = [1, 2, 3];
let values = [60, 100, 120];
let capacity = 5;

console.log(knapsack(weights, values, capacity)); // 输出220

In the above code, the knapsack function accepts three arrays weights, values ​​and a capacity as parameters, and returns the maximum value of items that can be loaded into the backpack. The dp array is used to record the maximum value that can be loaded in backpacks with different capacities, and dp[i][j] represents the maximum value that can be loaded in a backpack with a capacity of j among the first i items. The initial state is dp[0][j]=0 and dp[i][0]=0. When the capacity is 0, no matter how many items there are, they cannot be put into the backpack, so the first column of the dp array is 0; when there are no items to choose from, no matter how much the backpack capacity is, no items can be put into the backpack, so the first row of the dp array Both are 0.

The time complexity of the dynamic programming algorithm:

The time complexity of the dynamic programming algorithm is related to the number of states. Generally speaking, the number of states is equal to the number of sub-problems, and the number of sub-problems is usually equal to a power of the size of the problem. Therefore, the time complexity of the dynamic programming algorithm Usually O(n^k), where n is the problem size and k is the number of subproblems.

In practical applications, the time complexity of dynamic programming algorithms may be affected by state transition equations, for example, some state transition equations may contain nested loops, resulting in higher time complexity.

A JavaScript implementation of the dynamic programming algorithm for the longest common subsequence problem is given below:

function longestCommonSubsequence(s1, s2) {
    
    
  let m = s1.length;
  let n = s2.length;
  let dp = [];
  for(let i = 0; i <= m; i++) {
    
    
    dp[i] = [];
    for(let j = 0; j <= n; j++) {
    
    
      if(i === 0 || j === 0) {
    
    
        dp[i][j] = 0;
      } else if(s1[i-1] === s2[j-1]) {
    
    
        dp[i][j] = dp[i-1][j-1] + 1;
      } else {
    
    
        dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
      }
    }
  }
  return dp[m][n];
}

let s1 = 'ABCBDAB';
let s2 = 'BDCABA';

console.log(longestCommonSubsequence(s1, s2)); // 输出4

In the above code, the longestCommonSubsequence function accepts two strings s1 and s2 as parameters and returns the length of their longest common subsequence. The dp array is used to record the length of the longest common subsequence of s1 and s2 under different lengths, and dp[i][j] represents the length of the longest common subsequence of the first i characters of s1 and the first j characters of s2 . The initial state is dp[0][j]=0 and dp[i][0]=0. When s1[i-1] is equal to s2[j-1], dp[i][j]=dp[i-1][j-1]+1, because the longest common subsequence is from the previous i-1 characters and the longest common subsequence of the first j-1 characters plus the last identical character; when s1[i-1] is not equal to s2[j-1], dp[i][j] depends on In the previous state, you can choose to transfer from dp[i-1][j] or dp[i][j-1], because the length of the longest common subsequence depends on whether the previous characters are the same.

Finally, the function returns dp[m][n], the length of the longest common subsequence of s1 and s2.

In this example, the time complexity is O(mn), where m and n are the lengths of the two strings. Although the time complexity of this algorithm is lower than that of the violent enumeration method, it still needs to use a two-dimensional dp array, so the space complexity is O(mn).

Optimize space complexity using rolling arrays.

function longestCommonSubsequence(s1, s2) {
    
    
  let m = s1.length;
  let n = s2.length;
  let dp = new Array(n + 1).fill(0);
  let temp;
  for(let i = 1; i <= m; i++) {
    
    
    temp = [...dp];
    for(let j = 1; j <= n; j++) {
    
    
      if(s1[i-1] === s2[j-1]) {
    
    
        dp[j] = temp[j-1] + 1;
      } else {
    
    
        dp[j] = Math.max(dp[j-1], temp[j]);
      }
    }
  }
  return dp[n];
}

let s1 = 'ABCBDAB';
let s2 = 'BDCABA';

console.log(longestCommonSubsequence(s1, s2)); // 输出 4

In the above code, we first define a one-dimensional array dp of length n+1 and initialize it to 0. Next, we use a double loop to go through all possible cases and calculate the value of dp[j] according to the state transition equation. Before updating dp[j], we copy the value of the dp array of the current row into a temporary array temp so that the value of the dp array of the previous row can be used when calculating dp[j]. Specifically, if s1[i-1] is equal to s2[j-1], then the value of dp[j] is temp[j-1] plus 1; otherwise, the value of dp[j] is dp[j-1] ] and the larger value of temp[j]. Finally, the function returns dp[n], the length of the longest common subsequence of s1 and s2.

This implementation only needs an array of length n+1 in space, which reduces the space complexity from O(mn) to O(n) compared to the method of using a two-dimensional array, and it will reduce the complexity when dealing with large-scale data. Very advantageous.

The dynamic programming algorithm is an algorithm for solving problems with overlapping subproblems and optimal substructure properties, which can strike a balance between time and space efficiency. The basic idea of ​​the dynamic programming algorithm is to decompose the problem into several sub-problems, and solve the optimal solution of the sub-problems in a recursive manner, so as to derive the optimal solution of the original problem. The steps to realize the dynamic programming algorithm include defining the state, initializing the state, defining the state transition equation, solving the problem and outputting the result. Although the time complexity of the dynamic programming algorithm is usually O(n^k), it may be affected by the state transition equation in practical applications. In JavaScript, you can use arrays to implement dynamic programming algorithms, but you need to pay attention to space complexity.

Guess you like

Origin blog.csdn.net/qq_29669259/article/details/130130510