decreasing stack + dp O(nd) time O(n) space
f[i]表示经过day天后截至jobDifficulty[i]的最小困难结果,即f[day][i]
因为f[day][i] = min{f[day - 1][j] + max{jobDifficulty[j + 1 : i]}},假设jobDifficulty[j]是jobDifficulty[i]左边第一个更大的数,则max{jobDifficulty[j + 1 : i]} = jobDifficulty[i]
f[day][i] = min{f[day][j], min{f[day - 1][j + 1 : i - 1]} + jobDifficulty[i]}
因为jobDifficulty[j] > jobDifficulty[i],所以f[day][j]的最后一个区间一定也可以cover整个jobDifficulty[j + 1 : i]区间,且根据归纳法f[day][j]一定不会比f[day - 1][j] + jobDifficulty[i]更差
找jobDifficulty[j]左边第一个更大的jobDifficulty[i]可以用单调栈,求min{f[day - 1][j + 1 : i - 1]}可以利用单调栈顺便保存每个小区间的前一天的最小困难结果,具体而言,如果单调栈所存下标为 k1,k2,…,kn,则在 k1的位置上额外存储区间[0, k1)的最小值,在k2的位置上额外存储区间[k1, k2)的最小值,以此类推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Solution { public: int minDifficulty(vector<int>& jobDifficulty, int d) { int n = size(jobDifficulty); if (n < d) return -1; int M = 3e5 + 1; vector<int> f(n); f[0] = jobDifficulty[0]; for (int i = 1; i < n; ++i) { f[i] = max(f[i - 1], jobDifficulty[i]); } vector<int> t(n); for (int day = 1; day < d; ++day) { swap(t, f); stack<pair<int, int>> s; for (int i = day; i < n; ++i) { int p = t[i - 1]; while (!empty(s) && jobDifficulty[s.top().second] <= jobDifficulty[i]) { p = min(p, s.top().first); s.pop(); } if (empty(s)) { f[i] = p + jobDifficulty[i]; } else { f[i] = min(p + jobDifficulty[i], f[s.top().second]); } s.emplace(p, i); } } return f[n - 1]; } };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { public: int minDifficulty(vector<int>& jobDifficulty, int d) { int n = size(jobDifficulty); if (n < d) return -1; int M = 3e5 + 1; vector<int> f(n); f[0] = jobDifficulty[0]; for (int i = 1; i < n; ++i) { f[i] = max(f[i - 1], jobDifficulty[i]); } vector<int> t(f); for (int day = 1; day < d; ++day) { swap(t, f); vector<array<int, 3>> s{{M, M, M}}; for (int i = day; i < n; ++i) { int p = t[i - 1]; while (s.back()[1] <= jobDifficulty[i]) { p = min(p, s.back()[0]); s.pop_back(); } s.push_back({p, jobDifficulty[i], min(p + jobDifficulty[i], s.back()[2])}); f[i] = s.back()[2]; } } return f[n - 1]; } };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Solution { public: int minDifficulty(vector<int>& jobDifficulty, int d) { int n = size(jobDifficulty); if (n < d) return -1; int M = 3e5 + 1; vector<int> f(n, M), t(n); for (int day = 0; day < d; ++day) { swap(t, f); stack<int> s; for (int i = day; i < n; ++i) { f[i] = i > 0 ? t[i - 1] + jobDifficulty[i] : jobDifficulty[i]; while (!empty(s) && jobDifficulty[s.top()] <= jobDifficulty[i]) { f[i] = min(f[i], f[s.top()] - jobDifficulty[s.top()] + jobDifficulty[i]); s.pop(); } if (!empty(s)) { f[i] = min(f[i], f[s.top()]); } s.push(i); } } return f[n - 1]; } };
|
bottom-up dp O(nnd) time O(nd) space
f[day][i]表示到第day天安排前i个job的最小难度
转移方程为
f[day][i] = min{f[day - 1][j] + max{jobDifficulty[j : i - 1]}} where j ~ [day - 1, i - 1]
初始化
f[0][0] = 0表示到第0天安排前0个job的最小难度为0
其他都为inf,在之后的计算过程中凡是有f[day][i] where i < day的理论上都应该为inf
最终结果
f[d][n]
计算顺序
从前往后扫每天day,对于每天day,从前往后遍历每个i,再对每个i从后往前尝试每个j
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Solution { public: int minDifficulty(vector<int>& jobDifficulty, int d) { int n = size(jobDifficulty); if (n < d) return -1; int M = 3e5 + 1; vector<vector<int>> f(d + 1, vector<int>(n + 1, M)); f[0][0] = 0; for (int day = 1; day <= d; ++day) { for (int i = day; i <= n; ++i) { int mx = INT_MIN; for (int j = i - 1; j >= day - 1; --j) { mx = max(mx, jobDifficulty[j]); f[day][i] = min(f[day][i], f[day - 1][j] + mx); } } } return f[d][n]; } };
|
rolling array优化
O(nnd) time O(n) space
需要注意的是计算每个f[day][i]之前必须要重置为inf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Solution { public: int minDifficulty(vector<int>& jobDifficulty, int d) { int n = size(jobDifficulty); if (n < d) return -1; int M = 3e5 + 1; vector<vector<int>> f(2, vector<int>(n + 1, M)); f[0][0] = 0; for (int day = 1; day <= d; ++day) { int dd = day & 1; for (int i = day; i <= n; ++i) { int mx = INT_MIN; f[dd][i] = M; for (int j = i - 1; j >= day - 1; --j) { mx = max(mx, jobDifficulty[j]); f[dd][i] = min(f[dd][i], f[dd ^ 1][j] + mx); } } } return f[d & 1][n]; } };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public: int minDifficulty(vector<int>& jobDifficulty, int d) { int n = size(jobDifficulty); if (n < d) return -1; int M = 3e5 + 1; vector<vector<int>> f(2, vector<int>(n + 1, M)); f[0][0] = 0; for (int day = 1; day <= d; ++day) { for (int i = day; i <= n; ++i) { int mx = INT_MIN; f[day & 1][i] = M; for (int j = i - 1; j >= day - 1; --j) { mx = max(mx, jobDifficulty[j]); f[day & 1][i] = min(f[day & 1][i], f[(day - 1) & 1][j] + mx); } } } return f[d & 1][n]; } };
|
一维数组优化
O(nnd) time O(n) space
注意到每一行只跟前一行有关,对于每天day,可以改成从后往前遍历每个i,再对每个i从后往前尝试每个j
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Solution { public: int minDifficulty(vector<int>& jobDifficulty, int d) { int n = size(jobDifficulty); if (n < d) return -1; int M = 3e5 + 1; vector<int> f(n + 1, M); f[0] = 0; for (int day = 1; day <= d; ++day) { for (int i = n; i >= day; --i) { f[i] = M; int mx = INT_MIN; for (int j = i - 1; j >= day - 1; --j) { mx = max(mx, jobDifficulty[j]); f[i] = min(f[i], f[j] + mx); } } } return f[n]; } };
|