概要
本文继续讲解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的走法。
有状态转移方程:
climbStairs(d)=⎧⎩⎨⎪⎪climbStairs(d−1)+climbStairs(d−2)10if d>0if d=0if d<0(1) (1) c l i m b S t a i r s ( d ) = { c l i m b S t a i r s ( d − 1 ) + c l i m b S t a i r s ( d − 2 ) if d > 0 1 if d = 0 0 if d < 0
编程
自顶向下记忆法:
#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长
状态转移方程为
lipm(i,j)=1+max(⎧⎩⎨⎪⎪lipm(i+1,j),lipm(i−1,j),lipm(i,j+1),lipm(i,j−1)if i+1<n∧seq[i][j]>seq[i+1][j]if i>0∧seq[i][j]>seq[i−1][j]if j+1<m∧seq[i][j]>seq[i][j+1]if j>0∧seq[i][j]>seq[i][j−1])(2) (2) l i p m ( i , j ) = 1 + m a x ( { l i p m ( i + 1 , j ) , if i + 1 < n ∧ s e q [ i ] [ j ] > s e q [ i + 1 ] [ j ] l i p m ( i − 1 , j ) , if i > 0 ∧ s e q [ i ] [ j ] > s e q [ i − 1 ] [ j ] l i p m ( i , j + 1 ) , if j + 1 < m ∧ s e q [ i ] [ j ] > s e q [ i ] [ j + 1 ] l i p m ( i , j − 1 ) if j > 0 ∧ s e q [ i ] [ j ] > s e q [ i ] [ j − 1 ] )
基本情况为1,即为只包含本身的lis长。
求矩阵的lis长,则需要考虑所有元素作为结尾的lis长,选择最长的lis。即
lipmG()=max0≤i<n,0≤j<m(lipm(i,j)) l i p m G ( ) = max 0 ≤ i < n , 0 ≤ j < m ( l i p m ( i , j ) )
编程
自顶向下记忆法
#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的子集。
则状态转移式为
ssp(sum,i)=⎧⎩⎨⎪⎪truefalsessp(sum,i−1)ssp(sum,i−1)∨ssp(sum−arr[i],i−1)if sum=arr[i]if i=0,sum≠arr[i]if sum<arr[i]others(3) (3) s s p ( s u m , i ) = { t r u e if s u m = a r r [ i ] f a l s e if i = 0 , s u m ≠ a r r [ i ] s s p ( s u m , i − 1 ) if s u m < a r r [ i ] s s p ( s u m , i − 1 ) ∨ s s p ( s u m − a r r [ i ] , i − 1 ) others
编程
因其状态因素有两个,所以采用二维数组存储子问题的解。
自上而下记忆法
#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时的价值。
kp(i,v)=⎧⎩⎨⎪⎪0kp(i−1,v)max(kp(i−1,v−vol[i−1])+value[i−1],kp(i−1,v))if i=0if v<vol[i−1]others(4) (4) k p ( i , v ) = { 0 if i = 0 k p ( i − 1 , v ) if v < v o l [ i − 1 ] m a x ( k p ( i − 1 , v − v o l [ i − 1 ] ) + v a l u e [ i − 1 ] , k p ( i − 1 , v ) ) others
编程
自顶向下记忆法:
#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 )