Algorithm design and analysis: dynamic programming

3-2 edit distance

Problem description

Let A and B be two strings. To convert string A to string B with the least number of character operations. The character operation here includes (1) deleting A character; (2) Insert A character; (3) Change one character to another. The minimum character operand used to transform string A into string B is called the editing distance from string A to B, and is recorded as d(A,B). For A given string A and string B, calculate the editing distance d(A,B).

Algorithm description

To solve the dynamic programming problem of two strings, we usually use two pointers I and j to point to the end of the two strings, and then move forward step by step to reduce the scale of the problem.

For each pair of characters s1[i] and s2[j], there are four operations:

if s1[i-1] == s2[j-1]
    No operation( skip)
    i, j Move forward at the same time
else
    One out of three, whichever is the smallest:
        Insert( insert)
        Delete( delete)
        Replace( replace)

Define dp array: dp[i][j] stores the minimum editing distance of s1[0..i-1] and s2[0..j-1].

The dp recurrence expression corresponding to the above four choices is:

Matching operation:

\[dp[i][j]=dp[i-1][j-1] \]

Insert, delete, replace,

\[dp[i][j] = min( dp[i][j - 1] + 1,dp[i - 1][j] + 1,dp[i - 1][j - 1] + 1) \]

critical code

        int n1 = word1.length();
        int n2 = word2.length();
        int[][] dp = new int[n1+1][n2+1];

        //base case
        for(int i=0;i<=n2;i++){
            dp[0][i]=i;
        }
        for(int i=0;i<=n1;i++){
            dp[i][0]=i;
        }
        //state transition 
        for(int i=1;i<=n1;i++){
            for(int j =1;j<=n2;j++){
                if(word1.charAt(i-1)==word2.charAt(j-1)){
                    dp[i][j]=dp[i-1][j-1];
                }else{
                    dp[i][j]=1+Math.min(
                        dp[i][j-1],
                        Math.min(dp[i-1][j],dp[i-1][j-1])
                    );
                }
            }
        }
 	   System.out.println(dp[n1][n2]);

Result analysis

Input: word1 = "fxpimu", word2 = "xwrs"

Output: 5

Input: word1 = "horse", word2 = "ros"

Output: 3

Input: word1 = "intention", word2 = "execution"

Output: 5

Each item of dp array is accessed only once. The time complexity of the algorithm is \ (O(n1*n2) \), the space complexity is \ (O(n1*n2) \), \ (n1,n2 \) are the lengths of strings W1 and W2 respectively

3-5 multiplication table problem

Problem description

The multiplication table defined on the alphabet Σ {a,b,c) is shown in the table:

  

According to this multiplication table, for any string defined on ∑ {a,b,c}, an expression is obtained after appropriately adding parenthesized expression.

For example, for the string x=bbbba, its bracketed expression is (b(bb))(ba). According to the multiplication table, the value of the expression is a.

Try to design a dynamic programming algorithm. For any string x=x1x2... xn defined on ∑ {a,b,c}, calculate how many different bracketed methods there are, so that the value of the bracketed expression derived from X is a.

Algorithm description

Let 0, 1 and 2 represent constants a,b,c and N respectively as the length of the string.

Define dp array: dp[i][j][k], indicating how many different bracketed ways the character string i-j can get K

There are dp[i][j][0] bracketed methods for setting the product of bits I to j of a string as a,

There are dp[i][j][1] bracketed methods where the product of bits I to j of a string is b,

There are dp[i][j][2] bracketed methods in which the product of bits I to j of a string is c.

Then the solution of the original problem is dp[0][N-1][0].

Let k be a character from i to j, then for k, according to the multiplication table:

dp[i][j][0] += dp[i][k][0] * dp[k + 1][j][2] + dp[i][k][1] * dp[k + 1][j][2] + dp[i][k][2] * dp[k + 1][j][0];
dp[i][j][1] += dp[i][k][0] * dp[k + 1][j][0] + dp[i][k][0] * dp[k + 1][j][1] + dp[i][k][1] * dp[k + 1][j][1];
dp[i][j][2] += dp[i][k][1] * dp[k + 1][j][0] + dp[i][k][2] * dp[k + 1][j][1] + dp[i][k][2] * dp[k + 1][j][2];

dp array traversal order:

critical code

        String str = scan.nextLine();
        int n = str.length();

        //dp[i][j][k] represents the total number of character substrings i:j whose product is K (k=a,b,c)
        int[][][] dp = new int[n][n][3];
        for (int i = 0; i < n; i++) {
            dp[i][i][0] = str.charAt(i) == 'a' ? 1 : 0;
            dp[i][i][1] = str.charAt(i) == 'b' ? 1 : 0;
            dp[i][i][2] = str.charAt(i) == 'c' ? 1 : 0;
        }
        for (int r = 2; r <= n; r++) {
            for (int i = 0; i < n - r + 1; i++) {
                //Character substring i:j
                int j = i + r - 1;
                for (int k = i; k < j; k++) {
     dp[i][j][0] += dp[i][k][0]*dp[k + 1][j][2] + dp[i][k][1]*dp[k + 1][j][2] + dp[i][k][2]*dp[k + 1][j][0];
     dp[i][j][1] += dp[i][k][0]*dp[k + 1][j][0] + dp[i][k][0]*dp[k + 1][j][1] + dp[i][k][1]*dp[k + 1][j][1];
     dp[i][j][2] += dp[i][k][1]*dp[k + 1][j][0] + dp[i][k][2]*dp[k + 1][j][1] + dp[i][k][2]*dp[k + 1][j][2];
                }
            }
        }
        System.out.println(dp[0][n - 1][0]);
    }

Result analysis

Input: bbbbbba

Output: 6

Input: bbcbabcabc

Output: 2093

Algorithm time complexity \ (O(N^3) \), space complexity \ (O(N^2) \), N is the length of the string

3-7 vehicle fueling and driving problems

Problem description

Given an n × For the square grid of N, set its upper left corner as the starting point ◎, coordinate (1,1), X-axis as positive, Y-axis downward as positive, and the side length of each square grid is 1, as shown in the figure.

A car starts from the starting point ◎ and drives to the end point ▲ in the lower right corner. Its coordinates are (N,N).

Oil depots are set at several grid intersections for refueling during driving. The following rules shall be observed during driving:

  1. The car can only drive along the grid edge. After being filled with oil, it can drive K grid edges. When starting, the car has been filled with oil, and there is no oil depot at the starting and ending points.
  2. When the car passes a grid edge, if its X coordinate or Y coordinate decreases, the fee BB shall be paid, otherwise the fee shall be exempted.
  3. When the vehicle encounters an oil depot during driving, it shall fill up the oil and pay the refueling fee A.
  4. If necessary, an oil depot can be added at the grid point, and the additional oil depot fee CC (excluding refueling fee AA) shall be paid.
  5. N. K, a, B and C are positive integers and meet the constraints: 2 ≤ n ≤ 100, 2 ≤ K ≤ 10.

An algorithm is designed to calculate the minimum cost paid by the car from the starting point to the destination.

Algorithm description

First try the standard dynamic programming algorithm:

Define dp array: dp[i][j][k]

dp[x][y][0] represents the minimum cost from (1,1) to (x,y).
dp[x][y][1] represents the number of grid edges that can be driven from the car to (x,y).
Finally, find dp[N][N][0]

Process analysis:
1. For example, when it comes to (x,y), there are four possibilities for the previous position: top left and bottom right (sometimes less than 4 considering the situation of crossing the boundary), and these four positions correspond to a cost;
2. For example, first select the previous location as a, and then consider the current location (whether there is oil in the service station or not) and add the corresponding price;
3. Get a possible (x,y) cost at this time;
4. We traverse four locations and find the minimum cost at (x,y).

base case:

dp[1][1][0] = 0
dp[1][1][1] = K

Recurrence relation:

Arrive from last location( x,y),"Previous position"There are four situations.
dp[x][y][0] = min{dp[x+si][y+si][0]},0≤i≤3
dp[x][y][1] = dp[x+si][y+si] - 1
 if(x,y)It's an oil depot:
dp[x][y][0] += A
dp[x][y][1] = K
 if(x,y)Is it an oil depot, but there is no oil at this time dp[x][y][1]=0
dp[x][y][0] += A+C
dp[x][y][1] = K
 among si The array represents information in four directions:
s={{1,0,B},{0,1,B},{-1,0,0},{0,-1,0}

Key codes of dynamic planning:

		//cost[i][j][0] minimum cost from (1,1) to (i,j)
        //cost[i][j][1] remaining steps after (i,j)
        int[][][] cost = new int[N + 1][N + 1][2];
        for (int i = 1; i <= N; i++) {
            for (int j = 1; j <= N; j++) {
                cost[i][j][0] = Integer.MAX_VALUE / 4;
                cost[i][j][1] = K;
            }
        }
        cost[1][1][0] = 0;
        int[][] s = {
                {-1, 0, 0},
                {0, -1, 0},
                {1, 0, B},
                {0, 1, B}
        };
        for (int x = 1; x <= N; x++) {
            for (int y = 1; y <= N; y++) {
                if (x == 1 && y == 1) {
                    continue;
                }
                int minCost = Integer.MAX_VALUE / 4;
                int remainStep = 0;
                int tempCost, tempStep;
                for (int i = 0; i < 3; i++) {
                    //Cross the border
                    if (x + s[i][0] < 1 || x + s[i][0] > N || y + s[i][1] < 1 || y + s[i][1] > N) {
                        continue;
                    }
                    //In the case of fallback, tempCost is infinite and is ignored in subsequent comparisons
                    tempCost = cost[x + s[i][0]][y + s[i][1]][0] + s[i][2];
                    tempStep = cost[x + s[i][0]][y + s[i][1]][1] - 1;
                    if (a[x][y] == 1) {
                        //In case of a gas station, you must refuel
                        tempCost += A;
                        tempStep = K;
                    } else if (tempStep == 0 && (x != N || y != N)) {
                        //I can't walk. I have to build a gas station
                        tempCost += C + A;
                        tempStep = K;
                    }
                    if (tempCost < minCost) {
                        minCost = tempCost;
                        remainStep = tempStep;
                    } else if (tempCost == minCost) {
                        if (remainStep < tempStep) {
                            remainStep = tempStep;
                        }
                    }
                    if (cost[x][y][0] > minCost) {
                        cost[x][y][0] = minCost;
                        cost[x][y][1] = remainStep;
                    }
                }
            }
        }
        return cost[N][N][0];

Algorithm time complexity \ (O(N^2) \), space complexity \ (O(N^2) \), N is the width of square network map

Problems with standard dynamic programming algorithms:

Although the dp algorithm considers four possibilities of the previous position, due to the limitation of traversal order, after careful analysis, it is found that only the left and upper positions can provide information for state transition, that is, the standard dynamic programming cannot include fallback, and the result is the local optimal solution, which is similar to the result of greedy algorithm.

Consider SPFA algorithm again:

SPFA (shortest path fast algorithm) algorithm is an algorithm for finding the shortest path of a single source. It is Bellman Ford's queue optimization and a very efficient shortest path algorithm. Implementation method: establish a queue. At the beginning, there is only the starting point in the queue, and establish a table to record the shortest path from the starting point to all points (the initial value of the table should be set as the maximum value, and the path from the point to itself should be set as 0). Then, perform the relaxation operation, and use some points in the queue to refresh the shortest path from the starting point to all points. If the refresh is successful and the refreshed point is not in the queue, add the point to the end of the queue. Repeat until the queue is empty.

The square grid is abstracted into a graph structure, the state is abstracted into nodes, and the problem is transformed into the shortest path from the starting point (1,1,K) to (N,N,k). SPFA algorithm can also be applied to negative weight graph. SPFA algorithm can make a node join the queue many times. As long as the minimum cost of the node is updated, it will join the queue and re measure the minimum cost of its neighbors.

cost[x][y][k]Indicates from the starting point to(i,j)The remaining steps are k Minimum cost in case of
visited[x][y][k]Indicates by x,y,z Is the determined state already in the queue

The value of a[x][y] is used to process the logic of refueling or building a refueling plant. For the status that can update the minimum cost, join the team

Then deal with the four possibilities of the previous location and join the team for the status that can update the minimum cost

critical code

 		int[][] s = {
                {-1, 0, B},
                {0, -1, B},
                {1, 0, 0},
                {0, 1, 0}
        };
		Pair pair1 = new Pair(1, 1, K);
        //cost[i][j][k] represents the minimum cost when the number of remaining steps from the starting point to (i,j) is K
        queue.offer(pair1);
        cost[1][1][K] = 0;
        visited[1][1][K] = 1;

        while (!queue.isEmpty()) {
            pair1 = queue.poll();
            int x = pair1.x;
            int y = pair1.y;
            int k = pair1.step;
            visited[x][y][k] = 0;

            //Need refueling
            if (a[x][y] == 1 && k != K) {
                if (cost[x][y][K] > cost[x][y][k] + A) {
                    cost[x][y][K] = cost[x][y][k] + A;
                    if (visited[x][y][K] == 0) {
                        visited[x][y][K] = 1;
                        queue.offer(new Pair(x, y, K));
                    }
                }
                continue;
            } else {
                //Construction of refueling plant
                if (cost[x][y][K] > cost[x][y][k] + A + C) {
                    cost[x][y][K] = cost[x][y][k] + A + C;
                    if (visited[x][y][K] == 0) {
                        visited[x][y][K] = 1;
                        queue.offer(new Pair(x, y, K));
                    }
                }
            }
            if (k > 0) {
                for (int i = 0; i < 4; i++) {
                    int nx = x + s[i][0];
                    int ny = y + s[i][1];
                    //Cross the border
                    if (nx < 1 || nx > N || ny < 1 || ny > N) {
                        continue;
                    }
                    if (cost[nx][ny][k - 1] > cost[x][y][k] + s[i][2]) {
                        cost[nx][ny][k - 1] = cost[x][y][k] + s[i][2];
                        if (visited[nx][ny][k - 1] == 0) {
                            visited[nx][ny][k - 1] = 1;
                            queue.offer(new Pair(nx, ny, k - 1));
                        }
                    }
                }
            }
        }

Result analysis

Input:

9 3 2 3 6
0 0 0 0 1 0 0 0 0
0 0 0 1 0 1 1 0 0
1 0 1 0 0 0 0 1 0
0 0 0 0 0 1 0 0 1
1 0 0 1 0 0 1 0 0
0 1 0 0 0 0 0 1 0
0 0 0 0 1 0 0 0 1
1 0 0 1 0 0 0 1 0
0 1 0 0 0 0 0 0 0

Output: 12

Input:

9 2 4 3 6
0 0 0 0 1 0 0 0 0
0 1 0 1 0 0 1 0 0
1 0 0 0 0 0 0 1 0
0 0 1 0 0 0 0 0 1
1 0 0 1 0 1 1 0 0
0 1 0 0 0 0 0 1 0
0 0 0 0 1 0 0 0 1
0 0 0 1 0 0 1 1 0
0 1 0 0 0 0 0 0 0

Output: 38

Since SPFA algorithm is still considered as a special case of Bellman Ford algorithm in essence, the worst complexity of SPFA algorithm is still considered as \ (O(VE) \), where \ (E \) is the number of edges and \ (V \) is the number of points. The number of edges abstracted from the grid is \ (4N^2 \) and the number of points abstracted is \ (N^2 \), so the algorithm has the worst time complexity \ (O(N^4) \), space complexity \ (O(N^2) \), and n is the width of the square network map

Tags: Algorithm

Posted on Sun, 05 Dec 2021 02:48:15 -0500 by jrschwartz