算法分析与设计 练习二
内容一 最长公共子序列
给定两个序列X和Y,编写程序找出X和Y所有的最长公共子序列。
1.FindLCS
源码:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int longestCommonSubsequence(vector<vector<string>>& rec, const string& s1, const string& s2)
{
int size1 = s1.size();
int size2 = s2.size();
//初始化dp
vector<vector<int>> dp(size1 + 1,vector<int>(size2 + 1, 0));
//自底向上计算
for (int i = 1;i < size1 + 1;++i)
{
for (int j = 1;j < size2 + 1;++j)
{
if (s1[i - 1] == s2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
rec[i][j] = "UL";
}
else
{
if (dp[i - 1][j] >= dp[i][j - 1])
{
dp[i][j] = dp[i - 1][j];
rec[i][j] = "U";
}
else
{
dp[i][j] = dp[i][j - 1];
rec[i][j] = "L";
}
}
}
}
return dp[size1][size2];
}
void findLCS(const vector<vector<string>>& rec, string s1, int i, int j)
{
if (i == 0 || j == 0)
{
return;
}
if (rec[i][j] == "UL")
{
findLCS(rec, s1, i - 1, j - 1);
cout << s1[i - 1];
}
else if (rec[i][j] == "U")
{
findLCS(rec, s1, i - 1, j);
}
else
{
findLCS(rec, s1, i, j - 1);
}
}
int main()
{
string s1("abcbdab");
string s2("bdcaba");
int size1 = s1.size();
int size2 = s2.size();
vector<vector<string>> rec(size1 + 1, vector<string>(size2 + 1, ""));
cout << "s1 = " << s1 << endl;
cout << "s2 = " << s2 << endl;
cout << "最长公共子序列的长度为: " << longestCommonSubsequence(rec, s1, s2) << endl;
cout << "最长公共子序列为: ";
findLCS(rec, s1, size1, size2);
cout << endl;
return 0;
}
思路:
(1).明确原始问题:设序列X长度为M,序列Y长度为N,则dp[M] [N]为X[0…M - 1]和Y[0…N - 1]的最长公共子序列的长度。
(2).递推公式:dp[i] [j] = max(dp[i - 1] [j], dp[i] [j - 1]) , X[i - 1] != Y[j - 1]
dp[i] [j] = dp[i - 1] [j - 1] + 1 , X[i - 1] = Y[j - 1]
(3).根据递推公式自底向上计算
时间复杂度:O(MN)
空间复杂度:O(MN)
内容二 Ski
Michael 想知道在一个区域中最长的滑坡。 区域由一个二维数组给出,数组的每个数字代表点的高度。当且仅当高度减小,一个人可以从某个点滑向上下左右相邻四个点之一。
1.Ski
源码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//滑行数组
vector<int> dx = {1, -1, 0, 0};
vector<int> dy = {0, 0, 1, -1};
//辅助函数,判断是否越界
bool inBound(int rows, int columns, int i,int j)
{
return (i >= 0 && i < rows && j >=0 && j <columns);
}
int dpSki(const vector<vector<int>>& height, vector<vector<int>>& dp, int i, int j)
{
int rows = height.size();
int columns = height[0].size();
//使用dp[i][j]存储记忆化搜索
if (dp[i][j] != 0)
{
return dp[i][j];
}
for (int k = 0;k < 4;++k)
{
//未越界
if (inBound(rows, columns, i + dx[k], j + dy[k]))
{
//四周比中间低
if (height[i + dx[k]][j + dy[k]] < height[i][j])
{
int temp = dpSki(height, dp, i + dx[k], j + dy[k]);
dp[i][j] = max(dp[i][j], temp + 1);
}
}
}
return dp[i][j];
}
int main()
{
int rows;
int columns;
cout << "请输入行数和列数: " << endl;
cin >> rows >> columns;
cout << "请输入高度矩阵: " << endl;
vector<vector<int>> height(rows, vector<int>(columns, 0));
for (int i = 0;i < rows;++i)
{
for (int j = 0;j <columns;++j)
{
cin >> height[i][j];
}
}
vector<vector<int>> dp(rows, vector<int>(columns, 0));
int maxDist = 0;
for (int i = 0;i < rows;++i)
{
for (int j = 0;j <columns;++j)
{
int temp = dpSki(height, dp, i, j);
maxDist = max(maxDist, temp);
}
}
cout << "最长滑行距离为:" << maxDist + 1 << endl;
return 0;
}
思路:
(1).明确原始问题:
设二维数组height行数为M,列数为N。
max{dp[i] [j]} + 1,其中0 <= i <= M - 1, 0 <= j <= N - 1,为最长滑坡的长度 。
(2).递推公式:
dp[i] [j] = max{dp[i-1] [j], dp[i] [j-1], dp[i+1] [j], dp[i] [j+1]} + 1
(3).根据递推公式自底向上计算:
时间复杂度:O(MN)
空间复杂度:O(MN)
内容三 最大路径和
给定一个m * n的矩阵,每个单元中都有一个非负整数,只能向右或向下移动。求从左上角到右下角的所有路径中的最大值。
1.MaxPathSum
源码:
#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;
int maxPathSum(vector<vector<int>>& grid)
{
if (grid.size() == 0 || grid[0].size() == 0)
{
return 0;
}
int m = grid.size();
int n = grid[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
dp[0][0] = grid[0][0];
for (int i = 1;i < m;++i)
{
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1;j < n;++j)
{
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1;i < m;++i)
{
for (int j = 1;j < n;++j)
{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[m - 1][n - 1];
}
int main()
{
vector<vector<int>> grid = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
cout << "二维数组为: " << endl;
for (int i = 0;i < grid.size();++i)
{
for (int j = 0;j < grid[0].size();++j)
{
cout << grid[i][j] << " ";
}
cout << endl;
}
cout << "路径最大值为: " << maxPathSum(grid) << endl;
return 0;
}
思路:
(1).明确原始问题:
设二维数组grid行数为M,列数为N。则dp[M - 1] [N - 1]为从左上角到右下角的所有路径中的最大值。
(2).递推公式:
二维数组第一行的每个元素只能从左上角元素开始向右移动到达,第一列的每个元素只能从左上角元素开始向下移动到达,此时的路径是唯一的,因此每个元素对应的最大路径和即为对应的路径上的数字总和。
对于不在第一行和第一列的元素,可以从其上方相邻元素向下移动一步到达,或者从其左方相邻元素向右移动一步到达,元素对应的最小路径和等于其上方相邻元素与其左方相邻元素两者对应的最大路径和中的最大值加上当前元素的值。
dp[0] [0] = grid[0] [0]
dp[i] [0] = dp[i - 1] [0] + grid[i] [0], 其中i > 0且j = 0
dp[0] [j] = dp[0] [j - 1] + grid[0] [j], 其中i = 0且j > 0
dp[i] [j] = max(dp[i - 1] [j], dp[i] [j - 1]) + grid[i] [j], 其中i > 0且j > 0
(3).根据递推公式自底向上计算:
时间复杂度:O(MN)
空间复杂度:O(MN)
内容三 最大路径和2
给定一个m * n的二维数组。现在从左上走到右下再回到左上,中间不走重复的点,求所走单元的和的最大值。
1.MaxPathSum2
源码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int maxPathSum2(vector<vector<vector<int>>>& dp, const vector<vector<int>>& grid,
int step, int row1, int row2)
{
int m = grid.size();
int n = grid[0].size();
//两者同时走到右下
if (step == m + n -1)
{
return grid[row1][step - row1 - 1];
}
//两者在非起点非终点位置相遇
if (row1 == row2 && step != 1)
{
return 0;
}
//使用dp[step][row1][row2]存储记忆化搜索
if (dp[step][row1][row2] != -1)
{
return dp[step][row1][row2];
}
int result = 0;
//两者同时向下
if (row1 < m - 1 && row2 < m - 1)
{
result = max(result, maxPathSum2(dp, grid, step + 1, row1 + 1, row2 + 1));
}
//一个向下,另一个向右
if (row1 < m - 1 && step - row2 - 1 < n - 1)
{
result = max(result, maxPathSum2(dp, grid, step + 1, row1 + 1, row2));
}
//一个向右,另一个向下
if (step - row1 - 1 < n - 1 && row2 < m - 1)
{
result = max(result, maxPathSum2(dp, grid, step + 1, row1, row2 + 1));
}
//两者同时向右
if (step - row1 - 1 < n - 1 && step - row2 - 1 < n - 1)
{
result = max(result, maxPathSum2(dp, grid, step + 1, row1, row2));
}
result = result + grid[row1][step - row1 -1] + grid[row2][step - row2 -1];
dp[step][row1][row2] = result;
return result;
}
int main()
{
vector<vector<int>> grid = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
vector<vector<vector<int>>> dp(5, vector<vector<int>>(3, vector<int>(3, -1)));
cout << "二维数组为: " << endl;
for (int i = 0;i < grid.size();++i)
{
for (int j = 0;j < grid[0].size();++j)
{
cout << grid[i][j] << " ";
}
cout << endl;
}
cout << "所走单元之和最大值为: " << maxPathSum2(dp, grid, 1, 0, 0) - grid[0][0] <<endl;
return 0;
}
思路:
(1).分析问题:
设二维数组grid行数为M,列数为N。
**首先,**将问题转换为求两个对象同时从(0, 0)出发到(M - 1, N - 1),途中两者不能相遇。
使用一个三维数组dp[step] [row1] [row2],step代表两者当前步数,row1,row2分别代表两者当前所在的行,相应的两者当前所在的列可以通过step - row1 -1, step - row2 -1计算得到。两者同时从起点出发,两者走到同一位置也肯定是同时到达:如果一起走到了终点,则返回终点的值;如果一起走到了除终点外的同一位置,则返回0 。
(2)递推公式:
dp [step] [row1] [row2] =
max(两者同时向下,一个向下,另一个向右,一个向右,另一个向下,两者同时向右) +
grid[row1] [step - row1 -1] + grid[row2] [step - row2 -1]
内容五
输入长度为n的数列nums。在一次操作中你可以选择一个偶数c,并且把所有等于c的数除以2。请问最少进行多少次操作后,nums的所有数都变成奇数。
1.oddNums
源码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int oddNums(vector<int>& nums)
{
int size = nums.size();
//将数组中的偶数取出
vector<int> evenNums;
for (int i = 0;i < size;++i)
{
if (nums[i] % 2 == 0)
{
evenNums.emplace_back(nums[i]);
}
}
sort(evenNums.begin(), evenNums.end());
int count = 0;
while (evenNums.size() != 0)
{
//每次对最大的那一批偶数除以2
int num = evenNums[evenNums.size() - 1];
++count;
for (int i = evenNums.size() - 1;i >= 0;--i)
{
if (evenNums[i] == num)
{
evenNums[i] = evenNums[i] / 2;
}
if (evenNums[i] % 2 == 1)
{
evenNums.pop_back();
}
}
sort(evenNums.begin(), evenNums.end());
}
return count;
}
int main()
{
vector<int> nums1 = {40, 6, 40, 3, 20, 1};
cout << "数组nums1: ";
for (int i = 0;i < nums1 .size();++i)
{
cout << nums1[i] << " ";
}
cout << endl;
cout << "数组nums1经过" << oddNums(nums1) << "次操作后,数组中的所有数变为奇数。"<< endl <<endl;
vector<int> nums2 = {30, 20, 20, 30, 40, 40, 5, 3};
cout << "数组nums2: ";
for (int i = 0;i < nums2.size();++i)
{
cout << nums2[i] << " ";
}
cout << endl;
cout << "数组nums2经过" << oddNums(nums2) << "次操作后,数组中的所有数变为奇数。"<< endl;
return 0;
}
思路:
采用贪心策略,每次选择数组中最大的那一批偶数作为操作数,进行除以2操作,直到数组中全部为奇数为止。
贪心策略证明:设当前有四个偶数并满足a > b > c > d。根据贪心策略,第一次取操作数为a,得到数组[a/2,b,c,d]:
- if (a/2 > b),第二次操作数为a/2,得到的数组为[a/4,b,c,d]
- else if (a/2 == b),第二次操作数为a/2,得到的数组为[a/4,b/2,c,d]
- else if (a/2 < b),第二次操作数为b,得到的数组为[a/2,b/2,c,d]