|
1 | 1 | \chapter{动态规划}
|
2 | 2 |
|
3 | 3 |
|
| 4 | +\section{Maximum Subarray} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| 5 | +\label{sec:maximum-subarray} |
| 6 | + |
| 7 | + |
| 8 | +\subsubsection{描述} |
| 9 | +Find the contiguous subarray within an array (containing at least one number) which has the largest sum. |
| 10 | + |
| 11 | +For example, given the array \code{[−2,1,−3,4,−1,2,1,−5,4]}, |
| 12 | +the contiguous subarray \code{[4,−1,2,1]} has the largest \code{sum = 6}. |
| 13 | + |
| 14 | + |
| 15 | +\subsubsection{分析} |
| 16 | +最大连续子序列和,非常经典的题。 |
| 17 | + |
| 18 | +当我们从头到尾遍历这个数组的时候,对于数组里的一个整数,它有几种选择呢?它只有两种选择: 1、加入之前的SubArray;2. 自己另起一个SubArray。那什么时候会出现这两种情况呢? |
| 19 | + |
| 20 | +如果之前SubArray的总体和大于0的话,我们认为其对后续结果是有贡献的。这种情况下我们选择加入之前的SubArray |
| 21 | + |
| 22 | +如果之前SubArray的总体和为0或者小于0的话,我们认为其对后续结果是没有贡献,甚至是有害的(小于0时)。这种情况下我们选择以这个数字开始,另起一个SubArray。 |
| 23 | + |
| 24 | +设状态为d[j],表示以S[j]结尾的最大连续子序列和,则状态转移方程如下: |
| 25 | +\begin{eqnarray} |
| 26 | +d[j] &=& \max\left\{d[j-1]+S[j],S[j]\right\}, \text{ 其中 }1 \leq j \leq n \nonumber \\ |
| 27 | +target &=& \max\left\{d[j]\right\}, \text{ 其中 }1 \leq j \leq n \nonumber |
| 28 | +\end{eqnarray} |
| 29 | + |
| 30 | +解释如下: |
| 31 | +\begindot |
| 32 | +\item 情况一,S[j]不独立,与前面的某些数组成一个连续子序列,则最大连续子序列和为$d[j-1]+S[j]$。 |
| 33 | +\item 情况二,S[j]独立划分成为一段,即连续子序列仅包含一个数S[j],则最大连续子序列和为$S[j]$。 |
| 34 | +\myenddot |
| 35 | + |
| 36 | +其他思路: |
| 37 | +\begindot |
| 38 | +\item 思路1:直接在i到j之间暴力枚举,复杂度是$O(n^3)$ |
| 39 | +\item 思路2:处理后枚举,连续子序列的和等于两个前缀和之差,复杂度$O(n^2)$。 |
| 40 | +\item 思路3:分治法,把序列分为两段,分别求最大连续子序列和,然后归并,复杂度$O(n\log n)$ |
| 41 | +\item 思路4:把思路2$O(n^2)$的代码稍作处理,得到$O(n)$的算法 |
| 42 | +\item 思路5:当成M=1的最大M子段和 |
| 43 | +\myenddot |
| 44 | + |
| 45 | + |
| 46 | +\subsubsection{代码} |
| 47 | +\begin{Code} |
| 48 | +// LeetCode, Maximum Subarray |
| 49 | +class Solution { |
| 50 | +public: |
| 51 | + int maxSubArray(int A[], int n) { |
| 52 | + return mcss(A, n); |
| 53 | + //return mcss_dp(A, n); |
| 54 | + } |
| 55 | +private: |
| 56 | + /** |
| 57 | + * @brief 最大连续子序列和,思路四 |
| 58 | + * @param[in] S 数列 |
| 59 | + * @param[in] n 数组的长度 |
| 60 | + * @return 最大连续子序列和 |
| 61 | + */ |
| 62 | + static int mcss(int S[], int n) { |
| 63 | + int i, result, cur_min; |
| 64 | + int *sum = (int*) malloc((n + 1) * sizeof(int)); // 前n项和 |
| 65 | + |
| 66 | + sum[0] = 0; |
| 67 | + result = INT_MIN; |
| 68 | + cur_min = sum[0]; |
| 69 | + for (i = 1; i <= n; i++) { |
| 70 | + sum[i] = sum[i - 1] + S[i - 1]; |
| 71 | + } |
| 72 | + for (i = 1; i <= n; i++) { |
| 73 | + result = max(result, sum[i] - cur_min); |
| 74 | + cur_min = min(cur_min, sum[i]); |
| 75 | + } |
| 76 | + free(sum); |
| 77 | + return result; |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * @brief 最大连续子序列和,动规 |
| 82 | + * @param[in] S 数列 |
| 83 | + * @param[in] n 数组的长度 |
| 84 | + * @return 最大连续子序列和 |
| 85 | + */ |
| 86 | + static int mcss_dp(int S[], int n) { |
| 87 | + int i, result; |
| 88 | + int *d = (int*) malloc(n * sizeof(int)); |
| 89 | + d[0] = S[0]; |
| 90 | + result = d[0]; |
| 91 | + for (i = 1; i < n; i++) { |
| 92 | + d[i] = max(S[i], d[i - 1] + S[i]); |
| 93 | + if (result < d[i]) |
| 94 | + result = d[i]; |
| 95 | + } |
| 96 | + free(d); |
| 97 | + return result; |
| 98 | + } |
| 99 | +}; |
| 100 | +\end{Code} |
| 101 | + |
| 102 | + |
| 103 | +\subsubsection{相关题目} |
| 104 | +\begindot |
| 105 | +\item Binary Tree Maximum Path Sum,见 \S \ref{sec:binary-tree-maximum-path-sum} |
| 106 | +\myenddot |
| 107 | + |
| 108 | + |
4 | 109 | \section{Palindrome Partitioning II} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
5 | 110 | \label{sec:palindrome-partitioning-ii}
|
6 | 111 |
|
@@ -127,6 +232,115 @@ \subsubsection{代码}
|
127 | 232 |
|
128 | 233 | \subsubsection{相关题目}
|
129 | 234 |
|
| 235 | +\begindot |
| 236 | +\item 无 |
| 237 | +\myenddot |
| 238 | + |
| 239 | + |
| 240 | +\section{Best Time to Buy and Sell Stock III} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| 241 | +\label{sec:best-time-to-buy-and-sell-stock-iii} |
| 242 | + |
| 243 | + |
| 244 | +\subsubsection{描述} |
| 245 | +Say you have an array for which the i-th element is the price of a given stock on day i. |
| 246 | + |
| 247 | +Design an algorithm to find the maximum profit. You may complete at most two transactions. |
| 248 | + |
| 249 | +Note: You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again). |
| 250 | + |
| 251 | + |
| 252 | +\subsubsection{分析} |
| 253 | +设状态$f(i)$,表示区间$[0,i](0 \leq i \leq n-1)$的最大利润,状态$g(i)$,表示区间$[i, n-1](0 \leq i \leq n-1)$的最大利润,则最终答案为$\max\left\{f(i)+g(i)\right\},0 \leq i \leq n-1$。 |
| 254 | + |
| 255 | +允许在一天内买进又卖出,相当于不交易,因为题目的规定是最多两次,而不是一定要两次。 |
| 256 | + |
| 257 | +将原数组变成差分数组,本题也可以看做是最大$m$子段和,$m=2$,参考代码:\myurl{https://gist.github.com/soulmachine/5906637} |
| 258 | + |
| 259 | +\subsubsection{代码} |
| 260 | +\begin{Code} |
| 261 | +// LeetCode, Best Time to Buy and Sell Stock III |
| 262 | +class Solution { |
| 263 | +public: |
| 264 | + int maxProfit(vector<int>& prices) { |
| 265 | + if (prices.size() < 2) return 0; |
| 266 | + |
| 267 | + const int n = prices.size(); |
| 268 | + vector<int> f(n, 0); |
| 269 | + vector<int> g(n, 0); |
| 270 | + |
| 271 | + for (int i = 1, valley = prices[0]; i < n; ++i) { |
| 272 | + valley = std::min(valley, prices[i]); |
| 273 | + f[i] = std::max(f[i - 1], prices[i] - valley); |
| 274 | + } |
| 275 | + |
| 276 | + for (int i = n - 2, peak = prices[n - 1]; i >= 0; --i) { |
| 277 | + peak = std::max(peak, prices[i]); |
| 278 | + g[i] = std::max(g[i], peak - prices[i]); |
| 279 | + } |
| 280 | + |
| 281 | + int maxProfit = 0; |
| 282 | + for (int i = 0; i < n; ++i) |
| 283 | + maxProfit = max(maxProfit, f[i] + g[i]); |
| 284 | + |
| 285 | + return maxProfit; |
| 286 | + } |
| 287 | +}; |
| 288 | +\end{Code} |
| 289 | + |
| 290 | + |
| 291 | +\subsubsection{相关题目} |
| 292 | +\begindot |
| 293 | +\item Best Time to Buy and Sell Stock,见 \S \ref{sec:best-time-to-buy-and-sell-stock} |
| 294 | +\item Best Time to Buy and Sell Stock II,见 \S \ref{sec:best-time-to-buy-and-sell-stock-ii} |
| 295 | +\myenddot |
| 296 | + |
| 297 | + |
| 298 | +\section{Triangle} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| 299 | +\label{sec:triangle} |
| 300 | + |
| 301 | + |
| 302 | +\subsubsection{描述} |
| 303 | +Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below. |
| 304 | + |
| 305 | +For example, given the following triangle |
| 306 | +\begin{Code} |
| 307 | +[ |
| 308 | + [2], |
| 309 | + [3,4], |
| 310 | + [6,5,7], |
| 311 | + [4,1,8,3] |
| 312 | +] |
| 313 | +\end{Code} |
| 314 | +The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11). |
| 315 | + |
| 316 | +Note: Bonus point if you are able to do this using only $O(n)$ extra space, where n is the total number of rows in the triangle. |
| 317 | + |
| 318 | + |
| 319 | +\subsubsection{分析} |
| 320 | +设状态为$f(i, j)$,表示从从位置$(i,j)$出发,路径的最小和,则状态转移方程为 |
| 321 | +$$ |
| 322 | +f(i,j)=\min\left\{f(i,j+1),f(i+1,j+1)\right\}+(i,j) |
| 323 | +$$ |
| 324 | + |
| 325 | + |
| 326 | +\subsubsection{代码} |
| 327 | +\begin{Code} |
| 328 | +// LeetCode, Triangle |
| 329 | +class Solution { |
| 330 | +public: |
| 331 | + int minimumTotal (vector<vector<int>>& triangle) { |
| 332 | + for (int i = triangle.size() - 2; i >= 0; --i) |
| 333 | + for (int j = 0; j < i + 1; ++j) |
| 334 | + triangle[i][j] += std::min(triangle[i + 1][j], |
| 335 | + triangle[i + 1][j + 1]); |
| 336 | + |
| 337 | + return triangle [0][0]; |
| 338 | + } |
| 339 | +}; |
| 340 | +\end{Code} |
| 341 | + |
| 342 | + |
| 343 | +\subsubsection{相关题目} |
130 | 344 | \begindot
|
131 | 345 | \item 无
|
132 | 346 | \myenddot
|
0 commit comments