概要
本文继续讲解geeksforgeeks上的面试题。
包括Count number of ways to cover a distance(到达指定距离的走法)、 Longest Path In Matrix(特定矩阵的最长路径)、
Subset Sum Problem(动态规划子集和问题)、
0-1 Knapsack Problem(01背包问题)
Count number of ways to cover a distance
题目
https://leetcode.com/problems/climbing-stairs/description/
题目与geeksforgeeks上类似,就按LeetCode上说(统一按所给链接的题目讲解),因为有测试样例,方便检验。
分析
本题求解指定步长下阶梯n的走法,可以分解为走n-1阶楼梯和走n-2阶楼梯的走法的子问题的和,即为阶梯n的走法。
有状态转移方程:
编程
自顶向下记忆法:
#include <iostream>
#include <cstring>
using namespace std;
class Solution {
public:
int climbStairs(int n) {
int dp[n + 1];
memset(dp, 0, sizeof(dp));
return cs(n, dp);
}
int cs(int n, int dp[]) {
if(n == 0)
return dp[n] = 1;
if(n < 0)
return 0;
if(dp[n] > 0)
return dp[n];
return dp[n] = cs(n - 1, dp) + cs(n - 2, dp);
}
};
int main(){
Solution * s = new Solution();
cout << s->climbStairs(3);
}
自底向上制表法
#include <iostream>
using namespace std;
class Solution {
public:
int climbStairs(int n) {
int dp[n + 1];
dp[0] = 1; dp[1] = 1;
for(int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
};
int main(){
Solution * s = new Solution();
cout << s->climbStairs(3);
}
时间复杂度:
O(n)
O
(
n
)
空间复杂度:
O(n)
O
(
n
)
Longest Path In Matrix
题目
https://leetcode.com/problems/longest-increasing-path-in-a-matrix/description/
题目与geeksforgeeks上类似,但限定条件更少,做法略有差别。
分析
本题与lis类似,只不过是在矩阵中,加了限制条件,当前状态仅与它的上下左右元素状态有关。
设lipm(i, j)为以i, j为末尾的lis长
状态转移方程为
基本情况为1,即为只包含本身的lis长。
求矩阵的lis长,则需要考虑所有元素作为结尾的lis长,选择最长的lis。即
编程
自顶向下记忆法
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
class Solution {
public:
int longestIncreasingPath(vector<vector<int> > & matrix) {
if(matrix.empty())
return 0;
int row = matrix.size();
int col = matrix[0].size();
int dp[row][col];
int globalMax = 1;
memset(dp, -1, sizeof(int) * row * col);
for(int i = 0; i < row; i++) {
for(int j = 0; j < col; j++) {
lipm(matrix, row, col, i, j, &dp[0][0], &globalMax);
}
}
return globalMax;
}
int lipm(vector<vector<int> > & matrix, int row, int col, int i, int j, int * dp, int * globalMax) {
if(*(dp+i*col+j) > 0)
return *(dp+i*col+j);
*(dp+i*col+j) = 1;
int tmp;
if(i < row-1 && matrix[i][j] > matrix[i+1][j]) {
tmp = 1 + lipm(matrix, row, col, i+1, j, dp, globalMax);
*(dp+i*col+j) = *(dp+i*col+j) < tmp ? tmp : *(dp+i*col+j);
}
if(i > 0 && matrix[i][j] > matrix[i-1][j]) {
tmp = 1 + lipm(matrix, row, col, i-1, j, dp, globalMax);
*(dp+i*col+j) = *(dp+i*col+j) < tmp ? tmp : *(dp+i*col+j);
}
if(j < col-1 && matrix[i][j] > matrix[i][j+1]) {
tmp = 1 + lipm(matrix, row, col, i, j+1, dp, globalMax);
*(dp+i*col+j) = *(dp+i*col+j) < tmp ? tmp : *(dp+i*col+j);
}
if(j > 0 && matrix[i][j] > matrix[i][j-1]) {
tmp = 1 + lipm(matrix, row, col, i, j-1, dp, globalMax);
*(dp+i*col+j) = *(dp+i*col+j) < tmp ? tmp : *(dp+i*col+j);
}
if(*(dp+i*col+j) > *globalMax)
*globalMax = *(dp+i*col+j);
return *(dp+i*col+j);
}
};
int main(){
int a[3][3] = {9,9,4,6,6,8,2,1,1};
vector<vector<int> > arr;
arr.resize(3);
for(int i = 0; i < 3; i++)
arr[i].resize(3);
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
arr[i][j] = a[i][j];
Solution * s = new Solution();
cout << s->longestIncreasingPath(arr);
}
自底向上制表法
// undo.
// 初步设想,用grap往四周延伸,扩散。
时间复杂度:
O(n2∗m2)
O
(
n
2
∗
m
2
)
空间复杂度:
O(n∗m)
O
(
n
∗
m
)
(如有问题,欢迎指出)
Subset Sum Problem
题目
https://www.geeksforgeeks.org/dynamic-programming-subset-sum-problem/
没找到OJ上有这一题,如果看见的话,欢迎评论补充。
分析
判断序列是否存在子集,使子集和等于指定值。那么可以考虑将最后一个值拿走,当前状态可能会跟哪些子问题相关?
可能仍然有子集和等于指定值,即拿走的元素并非子集中元素,记为ssp(sum, i-1),问题规模缩减一,判断拿走最后一个元素,子集和是否可能等于指定sum
还有一种可能,拿走的元素是子集中的元素,拿走之后,我们要判断问题是否能达到sum-arr[i]的值,如果可以,则存在子集和等于指定值。
判断当前序列是否存在子集和等于指定值,则若有一种子问题满足条件,则说明当前序列存在子集和等于指定值,因而采用或操作取子问题的解。
设ssp(sum, i)为布尔值,表示序列的[0, i]子序列中是否包含和为sum的子集。
则状态转移式为
编程
因其状态因素有两个,所以采用二维数组存储子问题的解。
自上而下记忆法
#include <iostream>
#include <vector>
using namespace std;
class Solution{
public:
int ssp(int sum, vector<int> & arr) {
int dp[sum+1][arr.size()];
memset(dp, -1, sizeof(int) * (sum+1) * arr.size());
return _ssp(sum, arr, arr.size()-1, &dp[0][0]);
}
//此处表示的是以i结尾
int _ssp(int sum, vector<int> & arr, int i, int * dp) {
if(*(dp+sum * arr.size() + i) != -1)
return *(dp+sum * arr.size() + i);
if(sum == arr[i])
return *(dp+sum * arr.size() + i) = 1; // true
if(i == 0 && sum != arr[i])
return *(dp+sum * arr.size() + i) = 0; // false
if(sum < arr[i])
return *(dp+sum * arr.size() + i) = _ssp(sum, arr, i-1, dp);
return *(dp+sum * arr.size() + i) = _ssp(sum-arr[i], arr, i-1, dp)|_ssp(sum, arr, i-1, dp);
}
};
int main() {
int set[] = {3,34,4,12,5,2};
int sum = 9;
vector<int> a(set, set + sizeof(set)/sizeof(int));
Solution * s = new Solution();
cout << s->ssp(sum, a);
}
自底向上制表法
#include <iostream>
#include <vector>
using namespace std;
//don't have test cases
class Solution{
public:
// here means ending with i.
bool ssp(int sum, vector<int> & arr) {
bool dp[sum+1][arr.size()];
for(int i = 0; i <= sum; i++) {
for(int j = 0; j < arr.size(); j++) {
if(i==arr[j])
dp[i][j] = true;
else if(j==0 && i != arr[j])
dp[i][j] = false;
else if(i < arr[j])
dp[i][j] = dp[i][j-1];
else
dp[i][j] = dp[i-arr[j]][j-1] || dp[i][j-1];
}
}
return dp[sum][arr.size()-1];
}
};
int main() {
int set[] = {3,34,4,12,5,2};
int sum = 9;
vector<int> a(set, set + sizeof(set)/sizeof(int));
Solution * s = new Solution();
cout << s->ssp(sum, a);
}
时间复杂度:
O(sum∗N)
O
(
s
u
m
∗
N
)
空间复杂度:
O(sum∗N)
O
(
s
u
m
∗
N
)
0-1 Knapsack Problem
题目
http://acm.hdu.edu.cn/showproblem.php?pid=2602
分析
kp(i, v)为对前i个骨头进行选择后背包已用空间为v时的价值。
编程
自顶向下记忆法:
#define LOCAL
#include <stdio.h>
#include <string.h>
class Solution{
public:
int knapsack(int size, int bvol[], int bval[], int volumn) {
int value[1001][1001];
memset(value, -1, sizeof(int) * 1001 * 1001);
return _kp(size, bvol, bval, volumn, &value[0][0]);
}
int _kp(int i, int bvol[], int bval[], int j, int *value) {
if(*(value+i*1001+j) >= 0)
return *(value+i*1001+j);
if(i == 0) { // i== 0 || j == 0 wrong! don't know why?
return *(value+i*1001+j) = 0;
}
if(j < bvol[i-1])
return *(value+i*1001+j) = _kp(i-1, bvol, bval, j, value);
return *(value+i*1001+j) = max(_kp(i-1, bvol, bval, j - bvol[i-1], value) + bval[i-1], _kp(i-1, bvol, bval, j, value));
}
int max(int a, int b) {
return a > b ? a : b;
}
};
int main() {
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
#endif
int T, N, V;
int bvol[1000], bval[1000];
Solution * s = new Solution();
scanf("%d", &T);
for(int i = 0; i < T; i++) {
scanf("%d %d", &N, &V);
for(int j = 0; j < N; j++) {
scanf("%d", &bval[j]);
}
for(int j = 0; j < N; j++) {
scanf("%d", &bvol[j]);
}
printf("%d\n", s->knapsack(N, bvol, bval, V));
}
}
注释处出错尚未弄清原因。
自底向上制表法
//#define LOCAL
#include <stdio.h>
#include <string.h>
class Solution{
public:
int knapsack(int size, int bvol[], int bval[], int volumn) {
int value[1001][1001];
memset(value, 0, sizeof(value));
for(int i = 1; i <= size; i++) {
for(int j = 0; j <= volumn; j++) {
if(j < bvol[i-1]) {
value[i][j] = value[i-1][j];
} else {
value[i][j] = max(value[i-1][j-bvol[i-1]]+bval[i-1], value[i-1][j]);
}
}
}
return value[size][volumn];
}
int max(int a, int b) {
return a > b ? a : b;
}
};
int main() {
#ifdef LOCAL
freopen("data.in", "r", stdin);
freopen("data.out", "w", stdout);
#endif
int T, N, V;
int bvol[1000], bval[1000];
Solution * s = new Solution();
scanf("%d", &T);
for(int i = 0; i < T; i++) {
scanf("%d %d", &N, &V);
for(int j = 0; j < N; j++) {
scanf("%d", &bval[j]);
}
for(int j = 0; j < N; j++) {
scanf("%d", &bvol[j]);
}
printf("%d\n", s->knapsack(N, bvol, bval, V));
}
}
时间复杂度:
O(size∗volumn)
O
(
s
i
z
e
∗
v
o
l
u
m
n
)
空间复杂度:
O(size∗volumn)
O
(
s
i
z
e
∗
v
o
l
u
m
n
)
因为整体状态只依赖前一轮状态,可用滚动数组代替。此时,空间复杂度为
O(volumn)
O
(
v
o
l
u
m
n
)