训练题:最长公共子串

题目:http://t.cn/A6VAyutk

  1. string LCS( string str1, string str2 )
  2. {
  3. if( str1 == "" || str2 == "" ) { return ""; }
  4. const auto size1 = str1.size();
  5. const auto size2 = str2.size();
  6. map<char, vector<int>> dataMap;
  7. for( int idx1 = 0; idx1 < size1; ++idx1 ) { dataMap[str1[idx1]].push_back( idx1 ); }
  8. int maxLen = 0, startIdx = -1;
  9. for( int idx2 = 0, tmpCount = 0; idx2 < size2; ++idx2 )
  10. {
  11. if( dataMap.count( str2[idx2] ) )
  12. {
  13. for( auto &idx1 : dataMap[str2[idx2]] )
  14. {
  15. tmpCount = 1;
  16. for( ; idx1 + tmpCount < size1 && idx2 + tmpCount < size2 && str1[idx1 + tmpCount] == str2[idx2 + tmpCount]; ++tmpCount );
  17. if( tmpCount > maxLen )
  18. {
  19. startIdx = idx1;
  20. maxLen = tmpCount;
  21. }
  22. }
  23. }
  24. }
  25. if( !maxLen ) { return ""; }
  26. return str1.substr( startIdx, maxLen );
  27. }

训练题:最长回文子串

题目:http://t.cn/Eq0YD3Q

方法一:动态规划

  • 空间复杂度:O(n2)
  • 时间复杂度:O(n2)

dp[i][j]指子串[i~j]是否为回文串。有如下动态公式:
算法训练_动态规划 - 图1

  1. string longestPalindrome( string s )
  2. {
  3. if( s.size() < 2 ) { return s; }
  4. const short size = s.size();
  5. short maxLen = -1;
  6. short maxBeginIdx = -1;
  7. vector<vector<bool>> dataMap( size, vector<bool>( size ) );
  8. // 根据动态规划公式,我们应该先找出端的回文串,再此基础上才能确定更长的串
  9. // 因此遍历方式应该是:
  10. // 1、遍历字符串中的所有子串
  11. // 2、先遍历短的子串
  12. for( short interval = 0; interval < size; ++interval ) // 遍历首尾间隔为interval的子串
  13. {
  14. // left,right为子串的首尾索引
  15. for( short left = 0, right = -1; left < size; ++left )
  16. {
  17. right = left + interval;
  18. if( right > size - 1 ) { break; }
  19. if( left == right ) { dataMap[left][right] = true; } // 单个字符肯定是回文
  20. else if( right - left == 1 ) // 2个字符串只有相同才是回文
  21. {
  22. dataMap[left][right] = s[left] == s[right];
  23. }
  24. else // 动态规划公式
  25. {
  26. dataMap[left][right] = dataMap[left + 1][right - 1] && s[left] == s[right];
  27. }
  28. // 保存当前最长的回文串
  29. if( dataMap[left][right] && maxLen < right - left + 1 )
  30. {
  31. maxLen = right - left + 1;
  32. maxBeginIdx = left;
  33. }
  34. }
  35. }
  36. return s.substr( maxBeginIdx, maxLen );
  37. }

方法二:中心扩展算法

  1. string longestPalindrome( string s )
  2. {
  3. if( s.size() < 2 ) { return s; }
  4. const short size = s.size();
  5. short maxLen = 0;
  6. short maxLeftIdx = 0;
  7. // 遍历找出以idx或者idx,idx+1为中心的最长回文长度。
  8. for( short idx = 0, len1 = -1, len2 = -1, len3 = -1; idx < size; ++idx )
  9. {
  10. len3 = std::max( expand( s, size, idx, idx ), expand( s, size, idx, idx + 1 ) );
  11. if( len3 > maxLen )
  12. {
  13. maxLen = len3;
  14. maxLeftIdx = idx - ( ( len3 - 1 ) >> 1 );
  15. }
  16. }
  17. return s.substr( maxLeftIdx, maxLen );
  18. }
  19. // left~right之间是回文,向两边扩展,找出最长的回文。
  20. short expand( const string &s, const short &size, short left, short right )
  21. {
  22. for( ; left >= 0 && right < size && s[left] == s[right]; --left, ++right );
  23. return right - left - 1;
  24. }

训练题:N个骰子点数

题目:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/

  • 1、定义dp[i]或者dp[i][j]

算法训练_动态规划 - 图2

  • 2、找出转移方程

算法训练_动态规划 - 图3

  • 第i个骰子投出点数为1的次数 = 算法训练_动态规划 - 图4
  • 第i个骰子投出点数为2的次数 = 算法训练_动态规划 - 图5
  • ……
  • 第i个骰子投出点数为6的次数 = 算法训练_动态规划 - 图6

这六种情况之和就是dp[i][j]的所有可能情况。

  • 3、边界条件

算法训练_动态规划 - 图7
即第1个骰子投出各个点数的次数。

1、递归实现

  • 时间复杂度:O(6n),实现简单,但是非常低效,因为有大量重复计算。
  • 空间复杂度:O(n2)
  1. vector<double> dicesProbability(int n) {
  2. vector<double> ret;
  3. // 投n个骰子的全排列。
  4. double totalCount = pow(6.0, n);
  5. // 投n个骰子的总点数范围
  6. for(int i = 1*n; i <= 6*n; ++i) {
  7. ret.push_back( fuck(n, i) / totalCount );
  8. }
  9. return ret;
  10. }
  11. int fuck( int i, int j ) {
  12. if(i == 1){
  13. if(j >= 1 && j <= 6) return 1;
  14. return 0;
  15. }
  16. return fuck(i-1, j-1) + fuck(i-1, j-2) + fuck(i-1, j-3) +
  17. fuck(i-1, j-4) + fuck(i-1, j-5) + fuck(i-1, j-6);
  18. }

2、优化计算

  • 改成迭代
  • 保存状态记录 ```cpp

vector dicesProbability(int n) {

  1. if(n < 1 || n > 11) return {0};
  2. vector<double> ret;
  3. double totalCount = pow(6, n);
  4. // dp[i][j]
  5. int dp[12][67] = {0};
  6. for(char i = 1; i <= 6; ++i) dp[1][i] = 1;
  7. // i,j就是指dp[i][j]的i、j
  8. for(char i = 2; i <= n; ++i) {
  9. for(char j = i; j <= 6*i; ++j) {
  10. for(char cur = 1; cur <=6; ++cur){
  11. if(j > cur) dp[i][j] += dp[i-1][j-cur];
  12. }
  13. }
  14. }
  15. ret.reserve(5*n+1);
  16. for(char i = n; i <= 6*n; ++i) ret.push_back(dp[n][i] / totalCount);
  17. return ret;

}

  1. <a name="QMqj6"></a>
  2. ## 3、优化空间
  3. ```cpp
  4. vector<double> dicesProbability( int n )
  5. {
  6. if( n < 1 || n > 11 ) return{ 0 };
  7. vector<double> ret;
  8. double totalCount = pow( 6, n );
  9. // 如果当前循环是第n轮,则dp保存的是n-1轮的结果,这是可以通过dp的数据更新自己为n轮的数据。
  10. int dp[67] = { 0 };
  11. for( char i = 1; i <= 6; ++i ) { dp[i] = 1; }
  12. for( char i = 2; i <= n; ++i )
  13. {
  14. // 从后往前更新,避免把还需要用到的数据覆盖。
  15. for( char j = 6 * i; j >= i; --j )
  16. {
  17. // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  18. // i=1 1 1 1 1 1 1
  19. // i=2 1 2 3 4 5 6 5 4 3 2 1
  20. // i=3 1 3 6 10 15 21 25 27 27 25 21 15 10 6 3 1
  21. dp[j] = 0;
  22. for(char cur = 1; cur <= 6; ++cur){
  23. if(j - cur + 1 >= i) dp[j] += dp[j - cur];
  24. }
  25. }
  26. }
  27. ret.reserve( 5 * n + 1 );
  28. for( char i = n; i <= 6 * n; ++i ) { ret.push_back( dp[i] / totalCount ); }
  29. return ret;
  30. }

训练题:股票最大利润

题目:http://t.cn/A65Jyf61

  1. int maxProfit(vector<int>& prices) {
  2. if(prices.size() < 2) return 0;
  3. int profitMax = 0;
  4. const auto days = prices.size();
  5. for(int dayIdx = 1, lastProfitMax = 0; dayIdx < days; ++dayIdx)
  6. {
  7. lastProfitMax = max(prices[dayIdx] - prices[dayIdx - 1] + lastProfitMax, 0);
  8. profitMax = max(profitMax, lastProfitMax);
  9. }
  10. return profitMax;
  11. }

训练题:丑数

题目:http://t.cn/A6VM1wMb

算法训练_动态规划 - 图8算法训练_动态规划 - 图9

其中p2、p3、p5满足以下条件:

算法训练_动态规划 - 图10

  1. int nthUglyNumber( int n )
  2. {
  3. vector<int> nums( n + 1, 1 );
  4. int p2 = 1, p3 = 1, p5 = 1;
  5. for( int i = 2; i <= n; ++i )
  6. {
  7. int a1 = nums[p2] * 2;
  8. int b1 = nums[p3] * 3;
  9. int c1 = nums[p5] * 5;
  10. nums[i] = std::min( std::min( a1, b1 ), c1 );
  11. // 求出第i个丑数之后,要及时更新p2、p3、p5
  12. nums[i] == a1 && ++p2;
  13. nums[i] == b1 && ++p3;
  14. nums[i] == c1 && ++p5;
  15. }
  16. return nums[n];
  17. }

训练题:最多礼物(最优路径)

题目:http://t.cn/A6qoF5qK

求最值,首先考虑动态规划。

动态规划的核心思想:

  • 递归化(迭代化)
    • dp[i][j],到坐标(i, j)的最大礼物值,这个提炼是否正确,就看下面这一条是否可行。
  • 前一步的结果作为下一步的参考
    • dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + val[i][j],坐标(i,j)的最大礼物值取决于左边和上方更大值者。
    • 注意考虑i-1和j-1的边界情况。
  • 选择遍历方式
    • 使得可以先计算前面的dp,再计算后面的dp。
    • 这一题,情况简单,从左往右,从上往下遍历就满足这个要求。 ```cpp

int maxValue(vector>& grid) {

  1. if(grid.empty() || grid[0].empty()) return -1;
  2. const auto m = grid.size();
  3. const auto n = grid[0].size();
  4. // 这两个循环先求出第一行/列的dp值,免得在下面的双重循环中多次判断
  5. for(int col = 1; col < n; ++col) grid[0][col] += grid[0][col-1];
  6. for(int row = 1; row < m; ++row) grid[row][0] += grid[row-1][0];
  7. for(int row = 1; row < m; ++row){
  8. for(int col = 1; col < n; ++col){
  9. grid[row][col] += max(grid[row-1][col], grid[row][col-1]);
  10. }
  11. }
  12. return grid[m-1][n-1];

}

  1. <a name="yBvv3"></a>
  2. # 训练题:跳台阶
  3. 题目:[http://t.cn/A6Vz29oQ](http://t.cn/A6Vz29oQ)
  4. <a name="vS67K"></a>
  5. ## 方法一:最简单递归
  6. - 时间复杂度:O(n2)
  7. - 空间复杂度:递归栈空间。
  8. - 极其简单,效率极其低下
  9. ```cpp
  10. int jumpFloor( int number )
  11. {
  12. if( number < 3 ) { return number; }
  13. return jumpFloor( number - 1 ) + jumpFloor( number - 2 );
  14. }

方法二:记忆存储

保存递归过的结果,减少递归次数。

  • 时间复杂度:O(n)
  • 空间复杂度:O(n) + 递归栈空间。 ```cpp

int jumpFloor( int number ) { if( number < 4 ) { return number; }

  1. // 45个数初始化为-1,超过45,int会溢出。
  2. // index:对应number,value对应跳法数
  3. vector<int> shit( 45, -1 );
  4. return _jumpFloor( number - 1, shit ) + _jumpFloor( number - 2, shit );

} int _jumpFloor( int number, vector &shit ) { if( number < 4 ) { return number; } if( shit[number] != -1 ) { return shit[number]; } return shit[number] = _jumpFloor( number - 1, shit ) + _jumpFloor( number - 2, shit ); }

  1. <a name="Cwdzt"></a>
  2. ## 方法三:自底向上
  3. 斐波那契数列从前往后叠加,直到目的位置。
  4. - 时间复杂度:O(n)
  5. - 空间复杂度:O(1)
  6. ```cpp
  7. int jumpFloor( int number )
  8. {
  9. if( number < 4 ) { return std::max( number, 0 ); }
  10. for( int a = 1, b = 2, tmp, step = 3;; step++ )
  11. {
  12. tmp = b;
  13. b = a + b;
  14. a = tmp;
  15. if( step == number ) { return b; }
  16. }
  17. }

训练题:斐波那契数列求值

题目:http://t.cn/A6VziaJb
和上情形本质完全一样,这里就采用第三种方法:

  1. int Fibonacci(int n) {
  2. if(n < 2) return n;
  3. int b = 1;
  4. for(int a = 1, tmp; n > 2; n--){
  5. tmp = b;
  6. b = tmp + a;
  7. a = tmp;
  8. }
  9. return b;
  10. }

训练题:最大累加和

语雀内容

训练题:数字组合

语雀内容

训练题:最长递增子序列(LIS)

题目:https://leetcode-cn.com/problems/longest-increasing-subsequence/

动态规划(递归版)

  1. int lengthOfLIS(vector<int>& nums) {
  2. if(nums.size() < 2) return nums.size();
  3. const auto size = nums.size();
  4. int ret = 0;
  5. // 定义dp:dp[i]为以nums[i]结尾的最长子序列长度
  6. vector<int> dp(size, -1);
  7. for(int idx = 0; idx < size; ++idx)
  8. // ret = max(dp[i]),0 <= i <= idx。
  9. ret = max(ret, maxLenOfLIS(nums, dp, idx));
  10. return ret;
  11. }
  12. // 以idx索引处字符结尾的最长子序列长度,dp是为了保存迭代状态
  13. // dp[i] = max(dp[j]+1),其中0 <= j < i,且dp[j] < dp[i](这样才能做到以nums[i]结尾)
  14. int maxLenOfLIS(vector<int>& nums, vector<int>& dp, int idx){
  15. if(idx == 0) return 1;
  16. if(dp[idx] != -1) return dp[idx];
  17. dp[idx] = 1;
  18. for(int tmp = 0; tmp < idx; ++tmp){
  19. // dp[idx] = max(dp[i]+1),其中0 <= i < idx,且nums[i] < nums[idx]。
  20. // 因为必须要以nums[idx]结尾,则dp[i]的结尾字符值必须小于nums[idx]
  21. if(nums[tmp] < nums[idx]) dp[idx] = max(dp[idx], maxLenOfLIS(nums, dp, tmp) + 1);
  22. }
  23. return dp[idx];
  24. }

动态规划(迭代版)

  1. // 迭代版相对于递归版,少了递归栈空间。
  2. // 低索引的计算数据用于高索引的计算。
  3. int lengthOfLIS(vector<int>& nums) {
  4. if(nums.size() < 2) return nums.size();
  5. const auto size = nums.size();
  6. int ret = 1;
  7. vector<int> dp(size, 1); // key:idx, value:maxLen
  8. for(int idx = 1, tmp; idx < size; ++idx)
  9. {
  10. for(tmp = 0; tmp < idx; ++tmp){
  11. if(nums[tmp] < nums[idx]){
  12. dp[idx] = max(dp[idx], dp[tmp] + 1);
  13. }
  14. }
  15. ret = max(ret, dp[idx]);
  16. }
  17. return ret;
  18. }

动态规划(贪心)

考虑一

动态规划的本质就是穷举,将所有子问题解决进而得到问题的最优解。
贪心算法的思想是每一步取子问题的最优解,迭代进而得到问题的最优解。

上面动态规划中的转移方程并不是最优解:
转移方程:算法训练_动态规划 - 图11
我们在找这个max值的时候是线性遍历的方式寻找(复杂度n),即从头到尾遍历。key值i为字符索引与序列长度大小没有任何关系。假设我们以序列长度作为key值,那最大key就是最大长度。

考虑二

我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。

算法训练_动态规划 - 图12

  1. int lengthOfLIS(vector<int>& nums) {
  2. if(nums.size() < 2) return nums.size();
  3. const auto size = nums.size();
  4. vector<int> dp(size+1); // 长度为idx的子序列的最小结尾字符。
  5. dp[1] = nums[0]; // 起始状态,长度为1的子序列的结尾字符是首字符。
  6. int tmpLenIdx = 1; // tmpLenIdx表示minCharOfLen的有效长度,即
  7. for(int idx = 1; idx < size; ++idx)
  8. {
  9. // 1 4 5 2 3 4 5 6
  10. // ^
  11. // curr dp: 1 4
  12. // next dp: 1 4 5
  13. if(nums[idx] > dp[tmpLenIdx]) {
  14. minCharOfLen[++tmpLenIdx] = nums[idx];
  15. }
  16. else{
  17. // 1 4 5 2 3 4 5 6
  18. // ^
  19. // curr dp: 1 4 5
  20. // next dp: 1 2 5
  21. // 二分叉查找法,查找到第一个大于2的val值,并更新它。
  22. // 这里的实际意义指长度为2的递增子序列的最小结尾字符更新为2
  23. int left = 1;
  24. for(int right = tmpLenIdx, mid; left < right;){
  25. mid = left + ((right - left) >> 1);
  26. if(dp[mid] < nums[idx]) left = mid + 1;
  27. else if(dp[mid] >= nums[idx]) right = mid;
  28. }
  29. dp[left] = nums[idx];
  30. }
  31. }
  32. return tmpLenIdx;
  33. }

训练题:最长递增子序列个数

题目:https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/

  1. int findNumberOfLIS( vector<int> &nums )
  2. {
  3. if( nums.size() < 2 ) { return nums.size(); }
  4. const auto size = nums.size();
  5. vector<int> dp( size, 1 ); // 以nums[i]结尾的LIS长度
  6. vector<int> count( size, 1 ); // 以nums[i]结尾的LIS个数
  7. dp[0] = 1; // 初始状态
  8. count[0] = 1; // 初始状态
  9. int maxLen = 0;
  10. int maxLenCount = 0;
  11. for( int idx = 1; idx < size; ++idx )
  12. {
  13. for( int tmp = 0; tmp < idx; ++tmp )
  14. {
  15. if( nums[idx] > nums[tmp] ) { // 统计递增子序列。
  16. if( dp[tmp] + 1 > dp[idx] ) { // 出现更长的最长递增子序列
  17. dp[idx] = dp[tmp] + 1;
  18. count[idx] = count[tmp];
  19. }
  20. else if( dp[tmp] + 1 == dp[idx] ) { // 找到同样长的最长递增子序列。
  21. count[idx] += count[tmp];
  22. }
  23. }
  24. }
  25. maxLen = max(maxLen, dp[idx]);
  26. }
  27. for( int idx = 0; idx < size; ++idx )
  28. if( maxLen == dp[idx] ) maxLenCount += count[idx];
  29. return maxLenCount;
  30. }

训练题:“俄罗斯套娃”信封

题目:https://leetcode-cn.com/problems/russian-doll-envelopes/

方法一:动态规划

时间复杂度:O(n)

  1. int maxEnvelopes( vector<vector<int>> &envelopes )
  2. {
  3. if( envelopes.size() < 2 ) { return envelopes.size(); }
  4. // 宽度升序排列,高度降序排列。升序排列好理解,降序排列的原因是考虑宽度相同的情况。
  5. sort( envelopes.begin(), envelopes.end(), []( const vector<int> &a, const vector<int> &b )
  6. {
  7. return a[0] < b[0] || ( a[0] == b[0] && a[1] > b[1] );
  8. } );
  9. const auto size = envelopes.size();
  10. vector<int> dp( size, 1 ); // dp[i],以第i个信封结尾的套娃数量最大长度。
  11. int ret = 1;
  12. for( int idx = 1; idx < size; ++idx )
  13. {
  14. for( int tmp = 0; tmp < idx; ++tmp)
  15. {
  16. // 这里只比较了高度,为什么就可以呢?
  17. // 首先可以肯定,envelopes[idx] >= envelopes[tmp][0]的,分两种情况分析:
  18. // 假设envelopes[tmp][0] == envelopes[idx],则envelopes[idx][1] <= envelopes[tmp][1],
  19. // envelopes[idx][1] > envelopes[tmp][1]不可能成立。也即宽度相同时候,这个if不可能成立,显然宽度相同不能进行套娃,因此这天然帮我们过滤了宽度相同的情况。
  20. // 假设envelopes[tmp][0] < envelopes[idx],显然只需要判断高度即可,即如下判断。
  21. // 综上,宽度升序,高度降序时,以下条件判断即可过滤出idx可套娃tmp的情况。
  22. if( envelopes[tmp][1] < envelopes[idx][1] )
  23. {
  24. dp[idx] = max( dp[idx], dp[tmp] + 1 ); // 找到套娃最大值。
  25. ret = max( ret, dp[idx] );
  26. }
  27. }
  28. }
  29. return ret;
  30. }

方法二:贪心算法

和最长递增子序列类似的贪心。

  1. int maxEnvelopes(vector<vector<int>>& envelopes) {
  2. if(envelopes.size() < 2) return envelopes.size();
  3. sort(envelopes.begin(), envelopes.end(), [](const vector<int>& a, const vector<int>& b){
  4. return a[0] < b[0] || (a[0] == b[0] && a[1] > b[1]);
  5. });
  6. const auto size = envelopes.size();
  7. vector<int> dp = { envelopes[0][1] }; // dp[i],套娃信封数量为i+1的最小结尾信封索引
  8. dp.reserve(envelopes.size());
  9. for(int idx = 1; idx < size; ++idx){
  10. int height = envelopes[idx][1];
  11. if(height > dp.back()){ // 后面的height大于前面的height,则后面的width肯定是大于前面的width
  12. dp.push_back(height);
  13. }
  14. else{
  15. auto it = lower_bound(dp.begin(), dp.end(), height);
  16. *it = height;
  17. }
  18. }
  19. return dp.size();
  20. }

训练题:最大乘积子数组

题目:https://leetcode-cn.com/problems/maximum-product-subarray/

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

由于乘法的负负得正的特性,越小的负数乘以一个负数结果会越大,越大的正数乘以一个正数结果也会越大。我们可以记录两个极值,最小乘积和最大乘积,这两个乘积因为负数的出现会调换位置,当然还有0。

算法训练_动态规划 - 图13

  1. int maxProduct(vector<int>& nums) {
  2. if(nums.empty()) return 0;
  3. if(nums.size() == 1) return nums[0];
  4. const auto size = nums.size();
  5. int ret = 0;
  6. vector<int> dp_max(size, 0);
  7. vector<int> dp_min(size, 0);
  8. ret = dp_max[0] = dp_min[0] = nums[0];
  9. for(int idx = 1; idx < size; ++idx){
  10. dp_max[idx] = max(dp_max[idx-1]*nums[idx], max(nums[idx], dp_min[idx-1]*nums[idx]));
  11. dp_min[idx] = min(dp_max[idx-1]*nums[idx], min(nums[idx], dp_min[idx-1]*nums[idx]));
  12. ret = max(ret, dp_max[idx]);
  13. }
  14. return ret;
  15. }

把O(n)空间优化为O(1),dp[i]只需要dp[i-1]的数据。

  1. int maxProduct(vector<int>& nums) {
  2. if(nums.empty()) return 0;
  3. if(nums.size() == 1) return nums[0];
  4. const auto size = nums.size();
  5. int ret = nums[0];
  6. int dp_max = nums[0];
  7. int dp_min = nums[0];
  8. for(int idx = 1, dp_tmp; idx < size; ++idx){
  9. dp_tmp = dp_max;
  10. dp_max = max(dp_max*nums[idx], max(nums[idx], dp_min*nums[idx]));
  11. dp_min = min(dp_tmp*nums[idx], min(nums[idx], dp_min*nums[idx]));
  12. ret = max(ret, dp_max);
  13. }
  14. return ret;
  15. }

训练题:环形子数组的最大和

题目:https://leetcode-cn.com/problems/maximum-sum-circular-subarray/

共有两种情况:
算法训练_动态规划 - 图14
蓝色区域为最大和的连续子数组。
情况一:该连续子数组没有同时包含首尾元素,经没有across的情况,就是求最大子序和的问题。
情况二:该连续子数组同时包含了首尾元素,此时该区域在数组上是在首尾两端两部分组成,我们只需求得中间连续部分的最小子序和,即可获得最大和。
算法训练_动态规划 - 图15

  1. int maxSubarraySumCircular(vector<int>& nums) {
  2. if(nums.empty()) return 0;
  3. if(nums.size() == 1) return nums[0];
  4. const auto size = nums.size();
  5. int sum = nums[0]; // 数组和
  6. int sum_max = nums[0]; // 情况一的最大子序和
  7. int sum_tmp = nums[0]; // 中间变量
  8. for(int idx = 1; idx < size; ++idx){
  9. sum += nums[idx];
  10. sum_tmp = nums[idx] + max(sum_tmp, 0);
  11. sum_max = max(sum_max, sum_tmp);
  12. }
  13. int sum_min = nums[1]; // 情况二的最小子序和
  14. sum_tmp = nums[1];
  15. // 情况二的最大子序和肯定包含了nums[0]和nums[size-1]
  16. // 因此最小子序和肯定在nums[1]~nums[size-2]区间。
  17. for(int idx = 2; idx < size - 1; ++idx){
  18. sum_tmp = nums[idx] + min(sum_tmp, 0);
  19. sum_min = min(sum_min, sum_tmp);
  20. }
  21. return max(sum_max, sum - sum_min);
  22. }

训练题:最大子矩阵(待完成)

题目:https://leetcode-cn.com/problems/max-submatrix-lcci/

训练题:Nim游戏

题目:https://leetcode-cn.com/problems/nim-game/

  1. bool canWinNim(int n) {
  2. // 余子数量为n+1时,我们能不能赢。
  3. vector<bool> dp(n+1, true);
  4. for(int idx = 4; idx <= n; ++idx) dp[idx] = !(dp[idx-1] && dp[idx-2] && dp[idx-3]);
  5. return dp[n];
  6. }