LeetCode--Dynamic Programming

关于动态规划,推荐一篇博客,写的很详细:http://www.cppblog.com/menjitianya/archive/2015/10/23/212084.html


303. Range Sum Query - Immutable

Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.

Example:

Given nums = [-2, 0, 3, -5, 2, -1]
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3

Note:

You may assume that the array does not change.
There are many calls to sumRange function.

C++代码实现:

class NumArray {
public:
    NumArray(vector<int> nums) {
        sums.push_back(0);
        for(int a : nums)
            sums.push_back(sums.back()+a);
    }

    int sumRange(int i, int j) {
        return sums[j+1]-sums[i];
    }
private:
    vector<int> sums;
};

53. Maximum Subarray(子数组最大和)

Find the contiguous subarray within an array (containing at least one number) which has the largest sum.
For example, given the array [-2,1,-3,4,-1,2,1,-5,4],
the contiguous subarray [4,-1,2,1] has the largest sum = 6.

解析:

使用动态规划的思想。用sum[i]表示以i结尾的子数组和的最大值,则有:
sum[i] = num[i], i=0或者sum[i-1]<0(此时应该放弃前面的和);
sum[i] = sum[i-1] + num[i], sum[i-1]>=0(前面的和大于0则继续相加)
sum中的最大值即为结果。

C++代码实现:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int> sums(nums.size(),0);    //sums[i]表示以i结尾的子数组最大值
        sums[0] = nums[0];
        int maxsum = sums[0];
        for(int i=1; iif(sums[i-1]<0)
                sums[i] = nums[i];
            else
                sums[i] = sums[i-1] + nums[i];
            if(sums[i]>maxsum)
                maxsum = sums[i];
        }
        return maxsum;
    }
};

629. K Inverse Pairs Array

Given two integers n and k, find how many different arrays consist of numbers from 1 to n such that there are exactly k inverse pairs.

We define an inverse pair as following: For ith and jth element in the array, if i < j and a[i] > a[j] then it’s an inverse pair; Otherwise, it’s not.

Since the answer may very large, the answer should be modulo 109 + 7.

Example 1:

Input: n = 3, k = 0
Output: 1
Explanation:
Only the array [1,2,3] which consists of numbers from 1 to 3 has exactly 0 inverse pair.

Example 2:

Input: n = 3, k = 1
Output: 2
Explanation:
The array [1,3,2] and [2,1,3] have exactly 1 inverse pair.

Note:

The integer n is in the range [1, 1000] and k is in the range [0, 1000].

解析:

动态规划算法。用dp[i][j]代表i个数中有j个逆序对的排列方式数量,下面举个例子:
以i=5为例,假设我们已经有1~4的某种排列,
5XXXX,将5插入第1个位置,则会增加4个逆序对;
X5XXX,将5插入第2个位置,则会增加3个逆序对;

XXXX5,将5插入最后一位,则增加0个逆序对;
即:
dp[i][0] = dp[i-1][0]
dp[i][1] = dp[i-1][1] + dp[i-1][0]
dp[i][2] = dp[i-1][2] + dp[i-1][1] + dp[i-1][0]

dp[i][j] = dp[i-1][j] + dp[i-1][j-1] + dp[i-1][j-2] + … + dp[i-1][j-i+1]
….
dp[i][j+1] = dp[i-1][j+1] + dp[i-1][j] + dp[i-1][j-1] + … + dp[i-1][j+1-i+1]
由上述可得:
dp[i][j] = dp[i-1][j] + dp[i][j-1] (j < i)
dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-i] (j >= i)

C++代码实现:

class Solution {
public:
    int kInversePairs(int n, int k) {
        int dp[1001][1001];
        dp[0][0] = 1;
        int mod = 1000000007;
        for(int i=1; i<=n; i++) {
            dp[i][0] = 1;
            for(int j=1; j<=k; j++){
                dp[i][j] = (dp[i-1][j]+dp[i][j-1])%mod;
                if(j>=i)
                    dp[i][j] = (dp[i][j]-dp[i-1][j-i]+mod)%mod;
            }
        }
        return dp[n][k];
    }
};

494. Target Sum

You are given a list of non-negative integers, a1, a2, …, an, and a target, S. Now you have 2 symbols + and -. For each integer, you should choose one from + and - as its new symbol.
Find out how many ways to assign symbols to make sum of integers equal to target S.

Example 1:

Input: nums is [1, 1, 1, 1, 1], S is 3.
Output: 5
Explanation:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
There are 5 ways to assign symbols to make the sum of nums be target 3.

Note:

The length of the given array is positive and will not exceed 20.
The sum of elements in the given array will not exceed 1000.
Your output answer is guaranteed to be fitted in a 32-bit integer.

解析:

(1)方法一:DFS。
(2)方法二:动态规划。假设num=[1,2,3,4,5],target=3。1-2+3-4+5=3是一种方案。
即可以假设符号为+为数字集合为P,符号为-的数字集合为N。P={1,3,5} ,N={2,4}。
sum(P) - sum(N) = target
sum(P) + sum(N) + sum(P) - sum(N) = target + sum(num)
2*sum(P) = target + sum(num)
==> sum(P) = (target+sum(num))/2
则原来的问题变成如下问题:
找子集P使得 sum(P) = (target+sum(num))/2。由于target和sum(num)固定。target+sum(num)必须为偶数才有解。
上述例子演变为:从num={1,2,3,4,5}中找子集P,使得sum(P) = (15+3)/2=9。
用dp[i] 记录子集元素和等于当前目标值i的方案数量,初始 dp[0] = 1,当前目标值等于9减去当前元素值
当前元素等于1时,
dp[9] = dp[9] + dp[9-1]
dp[8] = dp[8] + dp[8-1]

dp[1] = dp[1] + dp[1-1]
当前元素等于2时,
dp[9] = dp[9] + dp[9-2]
dp[8] = dp[8] + dp[8-2]

dp[2] = dp[2] + dp[2-2]

C++代码实现:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        int sum = 0;
        for(int n : nums)
            sum += n;
        return (S > sum || (S+sum) & 1 ) ? 0 : subArrSum(nums,(S+sum)>>1);

    }
    int subArrSum(vector<int>& nums, int s){
        vector<int>dp(s+1,0);  
        dp[0] = 1;  
        for(int n: nums) {  
            for (int i = s; i >= n; --i) {  
                dp[i] += dp[i-n];  
            }  
        }  
        return dp[s];  
    }
};

91. Decode Ways

A message containing letters from A-Z is being encoded to numbers using the following mapping:
‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26
Given an encoded message containing digits, determine the total number of ways to decode it.
For example,
Given encoded message “12”, it could be decoded as “AB” (1 2) or “L” (12).
The number of ways decoding “12” is 2.

解析:

用dp[i]表示前i个字符有几种解码方式。dp[0]=1,dp[1]=1(开头字符不为0)或0(开头字符为0,则去掉这个字符)。
dp[i] = dp[i] + dp[i-1],(i的前1个数字在1-9之间,则可以按单个数字进行解码)
dp[i] = dp[i] + dp[i-2],(i的前2个数字在10-26之间,则可以按照双数字进行解析)
以1234为例:dp[0] = 1, dp[1] = 1
dp[2] = 0 + dp[1] +dp[0] = 2,(1、2和12)
dp[3] = 0 + dp[2] + d[1] = 3,(2、3和23)
dp[4] = 0 + dp[3] = 3, (3、4,但34不行)
因此,1234有3种解码方式。

C++代码解析:

class Solution {
public:
    int numDecodings(string s) {
        int size = s.length();
        if(size<=1)
            return size;
        vector<int> dp(size+1,0);
        dp[0] = 1;
        dp[1] = s[0]=='0' ? 0 : 1;

        for(int i=2; i<=size; i++) {
            int first = stoi(s.substr(i-1,1));
            int second = stoi(s.substr(i-2,2));
            if(first>=1 && first<=9) 
                dp[i] += dp[i-1];
            if(second>=10 && second<=26)
                dp[i] += dp[i-2];
        }
        return dp[size];
    }
};

416. Partition Equal Subset Sum

Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.
Note:
Each of the array element will not exceed 100.
The array size will not exceed 200.

Example 1:

Input: [1, 5, 11, 5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].

Example 2:

Input: [1, 2, 3, 5]
Output: false
Explanation: The array cannot be partitioned into equal sum subsets.

解析:

将数组分为两部分,两部分元素和相等。即这个数组的元素和sum必须为偶数。newSum = sum/2,这题演变成“在集合中找一个子集,使得子集的元素和为newSum”,显然这是一个0-1背包问题。采用动态规划,用dp[i][j]表示子集{a[0],a[1],…,a[i]}的元素和是否等于j。dp[n][newSum]即为答案。
dp[i][j] = dp[i][j] || dp[i-1][j-a[i]], (即:不选第i个元素,或者 选择第i个元素)

C++代码实现:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(int a : nums)
            sum += a;
        if((sum & 1)!=0)        //sum 必然是个偶数才行
            return false;
        int newSum = sum>>1;    //从nums找一个子集,使得子集之和位newSum,变成背包问题了

        vector<vector<bool>> dp(nums.size()+1,vector<bool>(newSum+1,false)); //dp[i][j]表示子集合nums{0,1,..,i}的和是否等于j
        for(int i=0; i<=nums.size(); i++)
            dp[i][0] = true;

        for(int j=1; j<=newSum; j++) {
            for(int i=1; i<=nums.size(); i++) {
                dp[i][j] = dp[i-1][j];      //不选第i个数字作为子集的成员
                if(j>=nums[i-1])
                    dp[i][j] = dp[i][j] || dp[i-1][j-nums[i-1]]; 
            }  
        }
        return dp[nums.size()][newSum];

    }
};

392. Is Subsequence

Given a string s and a string t, check if s is subsequence of t.
You may assume that there is only lower case English letters in both s and t. t is potentially a very long (length ~= 500,000) string, and s is a short string (<=100).
A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, “ace” is a subsequence of “abcde” while “aec” is not).

Example 1:

s = “abc”, t = “ahbgdc”
Return true.

Example 2:

s = “axc”, t = “ahbgdc”
Return false.

解析:

(1)方法一:双指针+滑动窗口。用sleft和tleft分别指向s和t,然后依次比较s[sleft]==t[tleft],移动指针,直至sleft==s.size()。时间复杂度O(n)。
(2)

c++代码实现:

方法一:双指针+滑动窗口

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int ssize = s.size();
        int tsize = t.size();
        if(ssize==0)
            return true;

        int sleft = 0, tleft = 0;
        while(tleftif(s[sleft]==t[tleft]) {
                sleft++;
            }

            if(sleft>=ssize)
                return true;
            tleft++;
        }
        return false;
    }
};

338. Counting Bits

Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1’s in their binary representation and return them as an array.

Example:

For num = 5 you should return [0,1,1,2,1,2].

Follow up:

It is very easy to come up with a solution with run time O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a single pass?
Space complexity should be O(n).
Can you do it like a boss? Do it without using any builtin function like __builtin_popcount in c++ or in any other language.

解析:

最直观的方法就是,对每个n,计算n的二进制表示中的个数。用dp[]记录每个n的1的个数。
dp[0] = 0、dp[1] = 1
对于k=2^i,即数为2的幂,则dp[k] = 1
对于k!=2^i,则:
dp[k] = dp[2^i] + dp[k-2^i] = 1+dp[k-2^i]
当然,也可以优化为:
dp[k] = dp[k&(k-1)] + 1 (k&(k-1)相当于消去最低位的1,保留高位的1,因此要+1)
比如:14 —>1110 & 1101 = 1100 (将14最低位1消除,保留了最高的1)

C++代码实现:
优化前:

/**
  初始:dp[0] = 0; dp[1] = 1;
  2的幂:dp[2] = 1, dp[4] = 1,...,dp[2^n] = 1;

  奇数:
  dp[3] = dp[2]+dp[1] = 2
  dp[5] = dp[4]+dp[1] = 2
  dp[9] = dp[8]+dp[1] = 2
  dp[11] = dp[8] + dp[3] = 3
  ==> dp[k] = dp[2^i] + dp[k-2^i]  (k为奇数,2^i < k)

  偶数:
  dp[6] = dp[4] + dp[2] = 2
  dp[10] = dp[8] + dp[2] = 2
  dp[12] = dp[8] + dp[4] = 2
  dp[14] = dp[8] + dp[6] = 1+2=3
  dp[k] = dp[2^i] + dp[k-2^i]  (k为偶数,2^i < k)

  奇数偶数合并:dp[k] = dp[2^i] + dp[k-2^i]  (2^i < k)
  当然,也可以优化为:
  dp[k] = dp[k&(k-1)] + 1 (k&(k-1)相当于消去最低位的1,保留高位的1,因此要+1)
  比如:14 --->1110 & 1101 = 1100 (将14最低位1消除,保留了最高的1)
*/
class Solution {
public:
    vector<int> countBits(int num) {
        vector<int> dp(num+1,0);
        dp[0] = 0;
        if(num>=1)
            dp[1] = 1;
        for(int k=2; k<=num; k++) {
            if(k&(k-1)==0)          //k为2的幂
                dp[k] = 1;
            else {                  
                dp[k] = 1 + dp[k-calculatePow(k)];
            }
        }
        return dp;
    }
    int calculatePow(int k) {   //求2^i <= k
        int i = 1;
        while(i<=k)
            i<<=1;
        if(i>k)
            i>>=1;
        return i;
    }
};

优化后:

class Solution {
public:
    vector<int> countBits(int num) {
        vector<int> dp(num+1,0);
        dp[0] = 0;

        for(int k=1; k<=num; k++) {
            dp[k] = dp[k&(k-1)] + 1;
        }
        return dp;
    }

};

64. Minimum Path Sum

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.
Note: You can only move either down or right at any point in time.

解析:

经典动态规划问题。dp[i][j]表示走到grid[i][j]的和最小,由于只能向右或者向下走,因此状态转移方程:
dp[i][j] = min{dp[i][j-1], dp[i-1][j]} + grid[i][j]

C++代码实现:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        vector<vector<int>> dp(m,vector<int>(n,INT_MAX));
        dp[0][0] = 0;

        for(int i=0; ifor(int j=0; jif(j>0)
                    dp[i][j] = dp[i][j-1];
                if(i>0 && dp[i][j] > dp[i-1][j])
                    dp[i][j] = dp[i-1][j];
                dp[i][j] += grid[i][j];
            }
        }
        return dp[m-1][n-1];
    }
};

120. Triangle

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.
For example, given the following triangle
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

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.

解析:

和上一题类似,动态规划。用dp[i][j]表示走到tri[i][j]的最小和。
dp[i][j] = min{dp[i-1][j-1], dp[i-1][j]}+tri[i][j] ;
优化:dp[i][j]的值只与他的上一行有关系,因此到上一行计算后,就没用了。因此可以把2D空间降到1D。
即,用dp[j]记录每一行的最小和。从最后一行开始,自底向上:
dp[j] = triangle[i][j] + min{dp[j], dp[j+1]}; (j<=triangle[i].size==i ,i为行)

C++代码实现:
优化前:

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        int row = triangle.size();
        if(row==0)
            return 0;
        int col = triangle[row-1].size();
        vector<vector<int>> dp(row,vector<int>(col,INT_MAX));
        dp[0][0] = 0;

        for(int i=0; ifor(int j=0; jif(i>0){
                    if(j>0 && j1].size())
                        dp[i][j] = dp[i-1][j-1] > dp[i-1][j] ? dp[i-1][j] : dp[i-1][j-1] ;
                    else if(j==0)
                        dp[i][j] = dp[i-1][j];
                    else
                        dp[i][j] = dp[i-1][j-1];
                }
                dp[i][j] += triangle[i][j];
            }
        }
        int result = INT_MAX;
        for(int i=0; iif(dp[row-1][i] < result)
                result = dp[row-1][i];
        return result;
    }
};

优化后:

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        int row = triangle.size();
        if(row==0)
            return 0;
        vector<int> dp = triangle[row-1]; //从最后一行开始,自底向上

        for(int i=row-2; i>=0; --i) {
            for(int j=0; j<=i; ++j) {
                dp[j] = triangle[i][j] + min(dp[j],dp[j+1]);
            }
        }
        return dp[0];

    }
};

62. Unique Paths

A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
How many possible unique paths are there?

LeetCode--Dynamic Programming_第1张图片

解析:

动态规划。用dp[i][j]表示到达point[i][j]的路径数量。
dp[i][j] = dp[i-1][j] + dp[i][j-1]
分析上述转移方程,我们知道dp[i][j]只依赖dp[i-1][j]和dp[i][j-1],因此没必要用m*n空间,只需要O(min(m,n))的空间即可。

C++代码实现:
优化前:

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m,vector<int>(n,1));
        for(int i=1; ifor(int j=1; j1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
};

优化后:

class Solution {
    int uniquePaths(int m, int n) {
        if (m > n) return uniquePaths(n, m);
        vector<int> cur(m, 1);
        for (int j = 1; j < n; j++)
            for (int i = 1; i < m; i++)
                cur[i] += cur[i - 1]; 
        return cur[m - 1];
    }
}; 

63. Unique Paths II

Follow up for “Unique Paths”:
Now consider if some obstacles are added to the grids. How many unique paths would there be?
An obstacle and empty space is marked as 1 and 0 respectively in the grid.
For example,
There is one obstacle in the middle of a 3x3 grid as illustrated below.
[
[0,0,0],
[0,1,0],
[0,0,0]
]
The total number of unique paths is 2.
Note: m and n will be at most 100.

解析:

和unique path类似。只是需要判断障碍物。

C++代码实现:

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int row = obstacleGrid.size(), col = obstacleGrid[0].size();
        vector<vector<int>> dp(row,vector<int>(col,0));

        //初始状态
        dp[0][0] = 1-obstacleGrid[0][0];

        for(int i=1; i0] = dp[i-1][0]; 
            if(obstacleGrid[i][0]==1)
                dp[i][0] = 0;  
        }
        for(int i=1; i0][i] = dp[0][i-1]; 
            if(obstacleGrid[0][i]==1)
                dp[0][i] = 0;  
        }

        for(int i=1; ifor(int j=1; jif(obstacleGrid[i][j]==0)
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[row-1][col-1];
    }
};

221. Maximal Square

Given a 2D binary matrix filled with 0’s and 1’s, find the largest square containing only 1’s and return its area.
For example, given the following matrix:
1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
Return 4.

解析:

动态规划。用dp[i][j]表示走到[i][j]所形成的正方形边长。
对于第一行,dp[0][i] = matrix[0][i]
对于第一列,dp[i][0] = matrix[i][0]
dp[i][j] = 0(matrix[i][j]=0)
dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
dp中最大值的平方即为正方形面积。

C++代码实现:

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        int result = 0, row = matrix.size();
        if(row==0)
            return 0;
        int col = matrix[0].size();
        vector<vector<int>> dp(row,vector<int>(col,0));     //记录正方形边长

        //初始状态
        for(int i=0; i0] = matrix[i][0]-'0';
            if(dp[i][0] > result)
                result = dp[i][0];
        }

        for(int j=0; j0][j] = matrix[0][j]-'0';
            if(dp[0][j] > result)
                result = dp[0][j];
        }

        for(int i=1; ifor(int j=1; jif(matrix[i][j]=='1')
                    dp[i][j] = min(dp[i-1][j],min(dp[i][j-1],dp[i-1][j-1]))+1;
                if(dp[i][j] > result)
                    result = dp[i][j];
            }
        }
        return result*result;
    }
};

516. Longest Palindromic Subsequence(最长回文子串)

Given a string s, find the longest palindromic subsequence’s length in s. You may assume that the maximum length of s is 1000.

Example 1:

Input:
“bbbab”
Output:
4
One possible longest palindromic subsequence is “bbbb”.

Example 2:

Input:
“cbbd”
Output:
2
One possible longest palindromic subsequence is “bb”.

解析:

经典动态规划问题。用dp[i][j]表示区间s[i,j]内最长回文串的长度。
dp[i][i] = 1,
如果s[i]==s[j], dp[i][j] = dp[i+1][j-1] + 2;
如果s[i]!=s[j], 可以假设在s[j]后面添加一个字符s[i],或者在s[i]前面添加一个字符s[j]
dp[i][j] = max{dp[i+1][j], dp[i][j-1]}

C++代码实现:

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int size = s.size();
        if(size<=1)
            return size;
        vector<vector<int>> dp(size,vector<int>(size,0));

        for(int i=size-1; i>=0; i--) {
            dp[i][i] = 1;
            for(int j=i+1; jif(s[i]==s[j])
                    dp[i][j] = dp[i+1][j-1] + 2;
                else
                    dp[i][j] = max(dp[i+1][j],dp[i][j-1]);//在s[j]后面添加一个字符s[i],或者在s[i]前面添加一个字符s[j]
            }
        }
        return dp[0][size-1];
    }
};

376. Wiggle Subsequence

A sequence of numbers is called a wiggle sequence if the differences between successive numbers strictly alternate between positive and negative. The first difference (if one exists) may be either positive or negative. A sequence with fewer than two elements is trivially a wiggle sequence.
For example, [1,7,4,9,2,5] is a wiggle sequence because the differences (6,-3,5,-7,3) are alternately positive and negative. In contrast, [1,4,7,2,5] and [1,7,4,5,5] are not wiggle sequences, the first because its first two differences are positive and the second because its last difference is zero.
Given a sequence of integers, return the length of the longest subsequence that is a wiggle sequence. A subsequence is obtained by deleting some number of elements (eventually, also zero) from the original sequence, leaving the remaining elements in their original order.

Examples:

Input: [1,7,4,9,2,5]
Output: 6
The entire sequence is a wiggle sequence.
Input: [1,17,5,10,13,15,10,5,16,8]
Output: 7
There are several subsequences that achieve this length. One is [1,17,10,13,10,16,8].
Input: [1,2,3,4,5,6,7,8,9]
Output: 2
Follow up:
Can you do it in O(n) time?

解析:

对于每个元素nums[i],只有三种状态:
nums[i] > nums[i-1], 增加
nums[i] < nums[i-1], 减少
nums[i] = nums[i-1],
因此,可以用up[i]和down[i]分别记录位置i的摆动序列的最大长度
如果nums[i] > nums[i-1],意味着i-1前面的元素 需要 大于nums[i-1],则up[i] = down[i-1] + 1,同时down[i] = down[i-1];
如果nums[i] < nums[i-1],意味着i-1前面的元素 需要 小于nums[i-1],则down[i] = up[i-1] + 1,同时up[i] = up[i-1];
如果nums[i] = nums[i-1],则down[i] = down[i-1], up[i] = up[i-1];
最后摆动序列的最大长度为max{up[n-1], down[n-1]}。
当然,可以对up[]和down[]进行优化,优化为up和down
nums[i] > nums[i-1], up = down+1;
nums[i] < nums[i-1], down = up+1;

C++代码实现:

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n = nums.size();
        if(n<=0) 
            return 0;
        vector<int> up(n,0);
        vector<int> down(n,0);
        up[0] = 1;
        down[0] = 1;

        for(int i=1; iif(nums[i] > nums[i-1]) {       //nums[i-2] >(down) (nums[i-1] <(up) nums[i])
                up[i] = down[i-1] + 1;
                down[i] = down[i-1];
            }
            else if(nums[i] < nums[i-1]){   //nums[i-2] <(up) (nums[i-1] >(down) nums[i])
                down[i] = up[i-1] + 1;
                up[i] = up[i-1];
            }
            else {                          //nums[i-1] = nums[i]
                up[i] = up[i-1];
                down[i] = down[i-1];
            }
        }
        return max(up[n-1],down[n-1]);
    }
};

优化后:

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n = nums.size();
        if(n<=0) 
            return 0;

        int up = 1;
        int down = 1;

        for(int i=1; iif(nums[i] > nums[i-1]) {       //nums[i-2] >(down) (nums[i-1] <(up) nums[i])
                up = down + 1;
            }
            else if(nums[i] < nums[i-1]){   //nums[i-2] <(up) (nums[i-1] >(down) nums[i])
                down = up + 1;
            }
        }
        return max(up,down);
    }
};

279. Perfect Squares

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, …) which sum to n.
For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.

解析:

(1)方法一:深度优先搜索(不带记忆)。超时。
(2)方法二:纯粹的数学方法。基本原理是Legendre’s three-square theorem(勒让德平方三定理):
n = x^2 + y^2 +z^2 ,当且仅当 n = 4^a *(8b+7)
(3)动态规划。用dp[i]记录完美平方数的最小数量。
dp[n] = min{dp[n-i*i]+1 | i*i<=n};

可参考:https://leetcode.com/problems/perfect-squares/#/solutions

C++代码实现:
勒让德平方三定理

class Solution {
public:
    int numSquares(int n) {
        if(n<4)
            return n;
        if(isSquare(n))     //n = m^2
            return 1;

        // The result is 4 if and only if n can be written in the 
        // form of 4^k*(8*m + 7). Please refer to 
        // Legendre's three-square theorem.
        while ((n & 3) == 0) // n%4 == 0  
        {
            n >>= 2;  
        }
        if ((n & 7) == 7) // n%8 == 7
        {
            return 4;     
        }

        // Check whether 2 is the result.
        int sqrt_n = sqrt(n); 
        for(int i = 1; i <= sqrt_n; i++)
        {  
            if (isSquare(n - i*i)) 
            {
                return 2;  
            }
        }  
        return 3;  
    }
private:
    bool isSquare(int n) {
        int m = sqrt(n);
        return n==(m*m);
    }
};

动态规划

class Solution {
public:
    int numSquares(int n) {

        static vector<int> dp({0,1,2,3});     //用static进行共享,这样不用每次都从0开始计算
        //如果dp.size>=n,则表示已经计算过n的完美平方数
        while(dp.size() <= n) {
            int m = dp.size();
            int cntSquares = INT_MAX;
            for(int i = 1; i*i <= m; i++) {
                cntSquares = min(cntSquares, dp[m - i*i] + 1); //dp[m] = min{dp[m-i*i]+1 | i*i<=m}
            }
            dp.push_back(cntSquares);
        }
        return dp[n];
    }
};

深度优先搜索

class Solution {
public:
    int numSquares(int n) {
        return search(n);
    }
    int search(int n) {
        if(n<4)
            return n; 
        int count = 0;
        int result = INT_MAX;
        int m = sqrt(n);
        for(int i=m; i>=1; i--) {
            if(i==1) {
                if(result > n)
                    result = n;
            }
            else if(i*i <= n) {
                count = 1 + search(n-i*i);
                if(count < result)
                    result = count;
                count = 0;
            }
        }
        return result;
    }
};

523. Continuous Subarray Sum

Given a list of non-negative numbers and a target integer k, write a function to check if the array has a continuous subarray of size at least 2 that sums up to the multiple of k, that is, sums up to n*k where n is also an integer.

Example 1:

Input: [23, 2, 4, 6, 7], k=6
Output: True
Explanation: Because [2, 4] is a continuous subarray of size 2 and sums up to 6.

Example 2:

Input: [23, 2, 6, 4, 7], k=6
Output: True
Explanation: Because [23, 2, 6, 4, 7] is an continuous subarray of size 5 and sums up to 42.

Note:

The length of the array won’t exceed 10,000.
You may assume the sum of all the numbers is in the range of a signed 32-bit integer.

解析:

(1)方法一:用dp[i]记录以区间[0,i]的元素和。
如果dp[i]%k==0 或者 (dp[j]-dp[i])%k==0 && j-i>=2,则返回true。
时间复杂度为O(n^2),空间复杂度O(n)
(2)方法二:用unordered_set记录区间[0,i]的元素和对k取余的结果。
如果取余结果 已经出现在 unordered_set中,则表示存在一个连续的序列元素和是k*n。时间复杂度为O(n),空间复杂度O(n)

C++代码实现:
方法二:

class Solution {
public:
    bool checkSubarraySum(vector<int>& nums, int k) {
        unordered_set<int> dp;
        int prev = 0, mod = 0, sum = 0;
        for(int n : nums) {
            sum += n;
            mod = k==0 ? sum : sum%k;
            if(dp.count(mod))
                return true;
            dp.insert(prev);  //因为要求连续序列至少有2个元素,因此在下次的迭代中才将mod加入哈希表中
            prev = mod;
        }
        return false;
    }
};

方法一:

class Solution {
public:
    bool checkSubarraySum(vector<int>& nums, int k) {
        k = abs(k);
        int n = nums.size();
        vector<int> dp(n,0);
        dp[0] = nums[0];
        for(int i=1; i1] + nums[i];
            if((k==0 && dp[i]==0) || (k!=0 && dp[i]%k==0))
                return true;
        }

        for(int i=0; ifor(int j=i+2; jif((k==0 && (dp[j]-dp[i])==0) || (k!=0 && (dp[j]-dp[i])%k==0))
                    return true;
            }
        }
        return false;
    }
};

322. Coin Change(换零钱)

You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.

Example 1:

coins = [1, 2, 5], amount = 11
return 3 (11 = 5 + 5 + 1)

Example 2:

coins = [2], amount = 3
return -1.

Note:

You may assume that you have an infinite number of each kind of coin.

解析:

经典动态规划。用dp[i]表示凑够i元需要的最少硬币数。
dp[i] = min{dp[i-v[j]]+1 | i>=v[j]},v[j]为硬币面额

C++代码实现:

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        if(amount==0) return 0;
        int n = coins.size();
        if(n==0)    return -1;
        vector<int> dp(amount+1,amount+1);  //dp[i]表示凑够i元所需最少硬币
        dp[0] = 0;
        for(int i=1; i<=amount; i++) {
            for(int j=0; jif(i>=coins[j] && dp[i] > (dp[i-coins[j]]+1) )
                    dp[i] = dp[i-coins[j]] + 1;
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
};

357. Count Numbers with Unique Digits

Given a non-negative integer n, count all numbers with unique digits, x, where 0 ≤ x < 10n.

Example:

Given n = 2, return 91. (The answer should be the total numbers in the range of 0 ≤ x < 100, excluding [11,22,33,44,55,66,77,88,99])

解析:

n=0, result = 1;
n=1, result = 10;
n=2, result = 91
n=3, result = result(n=2) + XYZ(即:9*9*8)
n=4, result = result(n=3) + XYZW(即:9*9*8*7)
规律:result(n) = result(n-1) + X1X2…Xn(即:9*9*8*…*(10-n+1))

C++代码实现:

class Solution {
public:
    int countNumbersWithUniqueDigits(int n) {
        vector<int> dp(n+1,0);
        dp[0] = 1;
        dp[1] = 10;
        dp[2] = 91;
        int count = 9, k = 1;
        for(int i=3; i<=n; i++) {
            dp[i] += dp[i-1];
            while(k10-k);
                k++;
            }
            dp[i] += count;
            count = 9;
            k = 1;
        }
        return dp[n];
    }
};

413. Arithmetic Slices

A sequence of number is called arithmetic if it consists of at least three elements and if the difference between any two consecutive elements is the same.
For example, these are arithmetic sequence:
1, 3, 5, 7, 9
7, 7, 7, 7
3, -1, -5, -9
The following sequence is not arithmetic.
1, 1, 2, 5, 7
A zero-indexed array A consisting of N numbers is given. A slice of that array is any pair of integers (P, Q) such that 0 <= P < Q < N.
A slice (P, Q) of array A is called arithmetic if the sequence:
A[P], A[p + 1], …, A[Q - 1], A[Q] is arithmetic. In particular, this means that P + 1 < Q.
The function should return the number of arithmetic slices in the array A.

Example:

A = [1, 2, 3, 4]
return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] itself.

解析:

用dp[i]表示以A[i]结尾的子集是arithmetic slices。结果为Sum{dp[i]}。
如果A[i]-A[i-1]==A[i-1]-A[i-2],则dp[i] = dp[i-1] + 1;

C++代码实现:

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& A) {
        int n = A.size();
        if(n<3) return 0;
        vector<int> dp(n,0);    //以A[i]结尾的算数子串的数量
        if((A[2]-A[1])==(A[1]-A[0]))
            dp[2] = 1;
        int result = dp[2];
        for(int i=3; iif((A[i]-A[i-1])==(A[i-1]-A[i-2]))
                dp[i] = dp[i-1] + 1;
            result += dp[i];
        }
        return result;
    }
};

96. Unique Binary Search Trees

Given n, how many structurally unique BST’s (binary search trees) that store values 1…n?
For example,
Given n = 3, there are a total of 5 unique BST’s.
LeetCode--Dynamic Programming_第2张图片

解析:

使用[1,2,3,…,n]构造二叉搜索树。
以i为树的根节点,则[1,2,..,i-1]在左子树,[i+1,i+2,..,n]在右子树。然后分别递归对左右子树构建二叉搜索树。
以F(i, n)表示以i作为根节点的不同BST的数量,G(n)表示长度为n的序列能构造不同BST的数量。通过上述过程知道:
G(n) = F(1,n) + F(2,n)+ … + F(n,n)
F(i, n) = G(i-1)*G(n-i)
合并两式得:
G(n) = G(0)*G(n-1) + G(1)*G(n-2) + … + G(n-1)*G(0)

C++代码实现:

class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n+1,0);
        dp[0] = 1;
        dp[1] = 1;

        for(int i=2; i<=n; i++) {
            for(int j=1; j<=i; j++) {
                dp[i] += dp[j-1]*dp[i-j];
            }
        }
        return dp[n];
    }
};

95. Unique Binary Search Trees II

Given an integer n, generate all structurally unique BST’s (binary search trees) that store values 1…n.

C++代码实现:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector generateTrees(int n) {
        vector trees;
        if(n<=0)
            return trees;
        return createTrees(1,n);
    }
private:
    vector createTrees(int start,int end) {
        vector trees;
        if(start==end) {
            trees.push_back(new TreeNode(start));
            return trees;
        }
        else if(start > end) {
            trees.push_back(NULL);
            return trees;
        }


        for(int i=start; i<=end; i++) {
            vector leftTrees = createTrees(start,i-1);
            vector rightTrees = createTrees(i+1,end);

            for(TreeNode* left : leftTrees) {
                for(TreeNode* right : rightTrees) {
                    TreeNode *root = new TreeNode(i);
                    root->left = left;
                    root->right = right;
                    trees.push_back(root);
                }
            }
        }
        return trees;
    }
};

467. Unique Substrings in Wraparound String

Consider the string s to be the infinite wraparound string of “abcdefghijklmnopqrstuvwxyz”, so s will look like this: “…zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd….”.
Now we have another string p. Your job is to find out how many unique non-empty substrings of p are present in s. In particular, your input is the string p and you need to output the number of different non-empty substrings of p in the string s.
Note: p consists of only lowercase English letters and the size of p might be over 10000.

Example 1:

Input: “cac”
Output: 2
Explanation: There are two substrings “a”, “c” of string “cac” in the string s.

Example 2:

Input: “zab”
Output: 6
Explanation: There are six substrings “z”, “a”, “b”, “za”, “ab”, “zab” of string “zab” in the string s.

解析:

动态规划。用dp[26]记录以某字符结尾的子串中连续串的数量。p中连续子串数量=Sum{dp[0…25]}。
同时,用continousLen记录以某字符结尾的最长连续子串的长度。
dp[i] = max{dp[i], continousLen} (防止存在覆盖现象,比如abcdabc。)
以p=“abceabc”为例:
dp[a] = 1{a}, dp[b] = 2 {b, ab}, dp[c]=3{c, bc, abc}, dp[e]=1{e}
sum(p) = 1+2+3+1=7,即p中有7个子串在S中。

C++代码实现:

class Solution {
public:
    int findSubstringInWraproundString(string p) {
        int n = p.size();
        if(n<=1)    return n;
        vector<int> dp(26,0);
        int result = 0;
        int continousLen = 0;         //记录连续字符的长度
        for(int i=0; iif(i>0 && ((p[i]-p[i-1])==1||(p[i-1]-p[i])==25)) {   //字符连续
                continousLen++;
            }
            else
                continousLen = 1;

            if(continousLen > dp[p[i]-'a'])
                dp[p[i]-'a'] = continousLen;
        }
        for(int a : dp)
            result += a;
        return result;
    }
};

139. Word Break

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words. You may assume the dictionary does not contain duplicate words.

example

s = “leetcode”,
dict = [“leet”, “code”].
Return true because “leetcode” can be segmented as “leet code”.

解析:

动态规划。使用vector < bool > dp,dp[i]记录s[0~i]形成的单词是否在dict中。
dp[i]=True, 如果s[j~i]形成的单词在dict中并且d[j]=true。(0<=j<=i-1)
否则dp[i]=false.

C++代码实现:

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        int n = s.size();
        unordered_set<string> dict(wordDict.begin(),wordDict.end());
        vector<bool> dp(n+1,false);
        dp[0] = true;

        for(int i=1; i<=n; i++) {
            for(int j=i-1; j>=0; j--) {
                if(dp[j] && dict.count(s.substr(j,i-j))==1) {
                    dp[i] = true;
                }
            }
        }
        return dp[n];
    }
};

152. Maximum Product Subarray(子数组最大乘积)

Find the contiguous subarray within an array (containing at least one number) which has the largest product.
For example, given the array [2,3,-2,4],
the contiguous subarray [2,3] has the largest product = 6.

解析:

求子数组的最大乘积,使用动态规划。
用nmax和nmin记录以nums[i]结尾的子数组的最大乘积。
如果nums[i] < 0,则需要交换nmax和nmin(乘以负数,nmax变小、nmin变大)
nmax = max{nums[i], nmax*nums[i]}
nmin = min{nums[i], nmin*nums[i]}
result = max{result, nmax}

C++代码实现:

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size();
        if(n==0)  return 0;
        if(n==1)  return nums[0];

        int result = nums[0];
        int nmax = result, nmin = result;   //存储以nums[i]结尾的子数组乘积的最大值和最小值
        for(int i=1; iif(nums[i] < 0)
                swap(nmax,nmin);    //乘以负数,使得nmax变小,nmin变大,因此需要交换
            nmax = max(nums[i],nmax*nums[i]);
            nmin = min(nums[i],nmin*nums[i]);
            if(nmax > result)
                result = nmax;
        }
        return result;
    }
};

377. Combination Sum IV

Given an integer array with all positive numbers and no duplicates, find the number of possible combinations that add up to a positive integer target.

Example:

nums = [1, 2, 3]
target = 4
The possible combination ways are:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
Note that different sequences are counted as different combinations.
Therefore the output is 7.

Follow up:

What if negative numbers are allowed in the given array?
How does it change the problem?
What limitation we need to add to the question to allow negative numbers?

解析:

(1)方法一:不带记忆的深度搜索、递归。容易超时。而且,如果nums中出现负数,递归终止条件不好确定。
(2)上述方法由于在递归时,多次重复计算某些中间结果,因此可以将中间结果保存下来,避免多次重复计算。
(3)方法二:动态规划。dp[i]表示凑成i组合方案数量。
dp[i] = sum{dp[i-nums[j]]} (i>=nums[j], 0<=i<=target)。

C++代码实现:
方法一:递归(不带记忆)

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        if(target==0)   return 1;
        int result = 0;
        for(int n : nums) {
            if(target>=n)
                result += combinationSum4(nums,target-n);
        }
        return result;
    }   
};

递归(带记忆)

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        dp.assign(target+1,-1);
        dp[0] = 1;
        return combine(nums,target);
    }
private:
    vector<int> dp;
    int combine(vector<int>& nums, int target) {
        if(dp[target]!=-1)  return dp[target];
        int result = 0;
        for(int n : nums) {
            if(target>=n)
                result += combine(nums,target-n);
        }
        dp[target] = result;
        return result;
    }
};

动态规划:

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target+1,0);
        dp[0] = 1;

        for(int i=1; i<=target; i++) {
            for(int n : nums){
                if(i>=n)
                    dp[i] += dp[i-n];
            }
        }
        return dp[target];
    }
};

264. Ugly Number II

Write a program to find the n-th ugly number.

Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 is the sequence of the first 10 ugly numbers.

Note that 1 is typically treated as an ugly number, and n does not exceed 1690.

解析:

根据丑数的定义,我们可以知道丑数可以由另外一个丑数乘以2,3或者5得到。因此我们创建一个数组,里面的数字是排好序的丑数,每一个丑数都是前面的丑数乘以2,3或者5得到的。这种思路的关键在于怎样确保数组里面的数字是排序的。
假设丑数数组中已经有若干个排好序的丑数,比如1,2,3,4,5。我们把当前丑数数组中的最大数记为M,这里M=5。我们接下来分析如何生成下一个丑数。根据前面的介绍,我们知道这个丑数肯定是前面丑数数组中的数字乘以2,3,5得到的。所以我们首先考虑把已有的每个丑数乘以2,在乘以2的时候,能够得到若干个小于或者等于M的结果。由于是按照顺序生成的,小于或者等于M的数肯定已经在丑数数组当中了,我们不需要再次考虑;当然还会得到若干大于M的结果,但是我们只需要第一个大于M的结果,因为我们希望丑数是按顺序排列的,所以其他更大的结果可以以后考虑。我们把得到的第一个乘以2以后得到的大于M的结果记为M2。同样,我们把已有的每一个丑数乘以3和5,能得到第一个大于M的结果M3和M5。那么M后面的那一个丑数应该是M2,M3和M5当中的最小值:Min(M2,M3,M5)。比如将丑数数组中的数字按从小到大乘以2,直到得到第一个大于M的数为止,那么应该是2*2=4

class Solution {
public:
    int nthUglyNumber(int n) {
        if(n<=dp.size())
            return dp[n-1];
        int i2 = 0, i3 = 0, i5 = 0; 

        while(dp.size()int m = min(dp[i2]*2,min(dp[i3]*3,dp[i5]*5));
            dp.push_back(m);
            while(dp[i2]*2 <= m)
                i2++;
            while(dp[i3]*3 <= m)
                i3++;
            while(dp[i5]*5 <= m)
                i5++;
        }
        return dp[n-1];
    }
private:
    vector<int> dp={1};         //前1个丑数 
};

309. Best Time to Buy and Sell Stock with Cooldown

Say you have an array for which the ith element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times) with the following restrictions:
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day)

Example:

prices = [1, 2, 3, 0, 2]
maxProfit = 3
transactions = [buy, sell, cooldown, buy, sell]

解析:

首先,我们知道股票有三种状态:Buy、Sell、Rest(不做交易),下面是这三种状态的状态转换图:
LeetCode--Dynamic Programming_第3张图片
从上面的状态机我们可以推断出状态转移方程:
S0[i] = max{ S0[i-1], S2[i-1] }
S1[i] = max{ S1[i-1], S0[i-1]-price[i]}
S2[i] = S1[i-1] + price[i]
并且,最大收益只能来源于S0[n]和S2[n]。
初始时,S0[0]=0, S1[0]=-price[0], S2[0] = INT_MIN

C++代码实现:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n<1) return 0;
        vector<int> s0(n,0);
        vector<int> s1(n,0);
        vector<int> s2(n,0);
        s1[0] = -prices[0];
        s2[0] = INT_MIN;

        for(int i=1; i1], s2[i-1]);
            s1[i] = max(s1[i-1], s0[i-1]-prices[i]);
            s2[i] = s1[i-1] + prices[i];
        }
        return max(s0[n-1], s2[n-1]);
    }
};

343. Integer Break

Given a positive integer n, break it into the sum of at least two positive integers and maximize the product of those integers. Return the maximum product you can get.
For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).
Note: You may assume that n is not less than 2 and not larger than 58.

解析:

假设有一个足够大的n,将n分解成如下形式:
n = p1 + p2 +… + pk (pk>0)
使得:p1 * p2 * … * pk最大。
那么如何使得p1 * p2 * … * pk最大?这里有两个神器的数字:2和3,下面用数学证明一下:
假设我们将n分解成(n/x)个x,乘积即为x^(n/x),如何使得x^(n/x)最大?
对x^(n/x)求导:
x^(n/x) = e^ln(x^(n/x)) ==> [x^(n/x)]’ = e^ln(x^(n/x)) * [n/xlnx]’
==>x^(n/x) * [n/xlnx]’ = x^(n/x) * [(n/x)’lnx + n/x (lnx)’]
==>x^(n/x) * [n/x^2 * (1-lnx)] ==> n * x^(n/x-2) * (1-lnx)
因此,[x^(n/x)]’ = n * x^(n/x-2) * (1-lnx)
由上式知道:x==e时,导数为0;0< x < e时,导数>0;x > e时,导数<0;
所以,当x==e时,x^(n/x)最大。因此为了使得x^(n/x)最大,x必须接近e。
我们知道,2 < e <3,因此2和3是最接近e的数,也能保证x^(n/x)接近最大值。
但是,到底选择2还是3呢?下面举一个例子说明:
6 = 2+2+2 = 3+3,2*2*2 < 3*3
因此,我们倾向于选择3。(当然这有个前提,n足够大,比如 n = 4, 2 * 2 > 3 * 1,这就有问题了)。

C++代码实现:

class Solution {
public:
    int integerBreak(int n) {
        if(n==2)    
            return 1;
        else if(n==3) 
            return 2;
        else if(n%3==0)
            return (int)pow(3,n/3);
        else if(n%3==1)
            return 2*2*((int)pow(3,(n-4)/3));
        else
            return 2*((int)pow(3,n/3));
    }
};

304. Range Sum Query 2D - Immutable

Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).
LeetCode--Dynamic Programming_第4张图片
The above rectangle (with the red border) is defined by (row1, col1) = (2, 1) and (row2, col2) = (4, 3), which contains sum = 8.

Example:

Given matrix = [
[3, 0, 1, 4, 2],
[5, 6, 3, 2, 1],
[1, 2, 0, 1, 5],
[4, 1, 0, 1, 7],
[1, 0, 3, 0, 5]
]
sumRegion(2, 1, 4, 3) -> 8
sumRegion(1, 1, 2, 2) -> 11
sumRegion(1, 2, 2, 4) -> 12

Note:

You may assume that the matrix does not change.
There are many calls to sumRegion function.
You may assume that row1 ≤ row2 and col1 ≤ col2.

解析:

用dp[row+1][col+1]存储[0][0]~[i][j]内的元素和。
dp[i][j] = dp[i-1][j] + dp[i][j-1] + matrix[i-1][j-1] - dp[i-1][j-1];

C++代码实现:

class NumMatrix {
public:
    NumMatrix(vector<vector<int>> matrix) {
        int row = matrix.size();
        if(row<=0)   return;
        int col = matrix[0].size();
        dp.resize(row+1,vector<int>(col+1,0));

        for(int i=1; i<=row; i++) {
            for(int j=1; j<=col; j++) {
                dp[i][j] = dp[i-1][j] + dp[i][j-1] + matrix[i-1][j-1] - dp[i-1][j-1];
            }
        }
    }

    int sumRegion(int row1, int col1, int row2, int col2) {
        return dp[row2+1][col2+1] - dp[row2+1][col1] - dp[row1][col2+1] + dp[row1][col1];
    }
private:
    vector<vector<int>> dp;
};

/**
 * Your NumMatrix object will be instantiated and called as such:
 * NumMatrix obj = new NumMatrix(matrix);
 * int param_1 = obj.sumRegion(row1,col1,row2,col2);
 */

368. Largest Divisible Subset

Given a set of distinct positive integers, find the largest subset such that every pair (Si, Sj) of elements in this subset satisfies: Si % Sj = 0 or Sj % Si = 0.
If there are multiple solutions, return any subset is fine.

Example 1:

nums: [1,2,3]
Result: [1,2] (of course, [1,3] will also be ok)

Example 2:

nums: [1,2,4,8]
Result: [1,2,4,8]

解析:

用dp[i]表示以nums[i]结尾的LDS集合的元素数量
dp[i] = max{1,1+dp[j] <- if nums[j]%nums[i]==0 }
由于本题要求返回LDS集合,而不是LDS集合元素数量,因此需要有数组parent[i]专门记录nums[i]在LDS集合中的上一个元素的下标,用于最后回溯LDS集合。

C++代码实现:

class Solution {
public:
    vector<int> largestDivisibleSubset(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(),nums.end());
        vector<int> dp(n,0);      //记录以nums[i]结尾的LDS集合的元素数量
        vector<int> parent(n,-1); //记录nums[i]在LDS集合中的上一个元素的下标

        int total = 0;          //记录最长LDS集合的元素个数
        int last = 0;           //记录最长LDS集合中最后一个元素的下标

        for(int i=n-1; i>=0; i--) {
            for(int j=i; jif(nums[j]%nums[i]==0 && dp[i] < (dp[j]+1)) {
                    dp[i] = dp[j] + 1;
                    parent[i] = j;
                }
                if(dp[i] > total){
                    total = dp[i];
                    last = i;
                }
            }
        }
        vector<int> result;
        for(int i=0; i//回溯
            last = parent[last];
        }
        return result;
    }
};

576. Out of Boundary Paths

There is an m by n grid with a ball. Given the start coordinate (i,j) of the ball, you can move the ball to adjacent cell or cross the grid boundary in four directions (up, down, left, right). However, you can at most move N times. Find out the number of paths to move the ball out of grid boundary. The answer may be very large, return it after mod 109 + 7.
LeetCode--Dynamic Programming_第5张图片

Note:

Once you move the ball out of boundary, you cannot move it back.
The length and height of the grid is in range [1,50].
N is in range [0,50].

解析:

(1)方法一:深度搜索。如果回溯是不带记忆的,会多次重复计算某些过程,容易超时。因此,最好有一个子问题记录表,将计算过的结果进行保存。
(2)方法二:动态规划。
用dp[N+1][m][n]记录路径数量。
dp[k][i][j] += i == 0 ? 1 : dp[k - 1][i - 1][j]; //上
dp[k][i][j] += i == m - 1 ? 1 : dp[k - 1][i + 1][j]; //下
dp[k][i][j] += j == 0 ? 1 : dp[k - 1][i][j - 1]; //左
dp[k][i][j] += j == n - 1 ? 1 : dp[k - 1][i][j + 1]; //右
dp[k][i][j] %= bound;
该方法复杂度为O(N*m*n)。
但通过上面的转移方程,我们知道dp[k]只需要使用到dp[k-1]的信息,因此可以将空间复杂度减小为O(2*m*n)。

C++代码实现:
带记忆的深度搜索:

const int orientation[][2] = {{-1,0}, {0,1}, {1,0}, {0,-1}};  //up right down left
const int bound = 1000000007;
class Solution {
public:
    int findPaths(int m, int n, int N, int i, int j) {
        row = m;
        col = n;
        for(int i=0; i<51; i++)
            for(int j=0; j<51; j++)
                for(int k=0; k<51; k++)
                    dp[i][j][k] = -1;
        return backtrack(N,i,j);
    }
private:
    int row,col;
    int dp[51][51][51];
    int backtrack(int N, int i, int j) {
        if(i<0 || j<0 || i>=row || j>=col)  
            return 1;
        if(N<=0)    return 0;
        if(dp[N][i][j]!=-1)
            return dp[N][i][j];
        int result = 0;
        for(int k=0; k<4; k++) {
            if(N>=1){
                result += backtrack(N-1,i+orientation[k][0],j+orientation[k][1])%bound;
                result %= bound;
            }   
        }
        dp[N][i][j] = result;
        return result;
    }
};

动态规划:O(N*m*n)


class Solution {
public:
    int findPaths(int m, int n, int N, int x, int y) {
       const int bound = 1000000007;
        vector<vector<vector>> dp(N+1,vector<vector>(m,vector(n,0)));  //dp[N][m][n]

        for(int k=1; k<=N; k++){
            for(int i=0; ifor(int j=0; j0     ? 1 : dp[k - 1][i - 1][j];
                    dp[k][i][j] += i == m - 1 ? 1 : dp[k - 1][i + 1][j];
                    dp[k][i][j] += j == 0     ? 1 : dp[k - 1][i][j - 1];
                    dp[k][i][j] += j == n - 1 ? 1 : dp[k - 1][i][j + 1];
                    dp[k][i][j] %= bound;
                }
            }
        }
        return dp[N][x][y];
    }
};

动态规划:O(2*m*n)


class Solution {
public:
    int findPaths(int m, int n, int N, int x, int y) {
       const int bound = 1000000007;
        vector<vector<vector>> dp(2,vector<vector>(m,vector(n,0)));  //dp[N][m][n]

        while(N-- > 0){
            for(int i=0; ifor(int j=0,nc = (N + 1) % 2, np = N % 2; j0 ? 1 : dp[np][i - 1][j]) + (i == m - 1 ? 1 : dp[np][i + 1][j])
                    + (j == 0 ? 1 : dp[np][i][j - 1]) + (j == n - 1 ? 1 : dp[np][i][j + 1])) % bound;
                }
            }
        }
        return dp[1][x][y];
    }
};

363. Max Sum of Rectangle No Larger Than K(和不超过K的最大子矩阵)[难度:hard]

Given a non-empty 2D matrix matrix and an integer k, find the max sum of a rectangle in the matrix such that its sum is no larger than k.

Example:
Given matrix = [
[1, 0, 1],
[0, -2, 3]
]
k = 2
The answer is 2. Because the sum of rectangle [[0, 1], [-2, 3]] is 2 and 2 is the max number no larger than k (k = 2).

解析:

解决这个问题之前,先来解决“最大子矩阵”问题。
最大子矩阵:即从矩阵中找到一个元素和最大的子矩阵。比如下图:
LeetCode--Dynamic Programming_第6张图片
上图中,右下角子矩阵和最大为12。
我们将这个问题描述为如下形式:
——> MaxSum{第i行到第j行,第r列到第k列},假设矩阵A如下:
LeetCode--Dynamic Programming_第7张图片
最笨的方法是:枚举所有可能的子矩阵。其实没有必要
从这个问题,其实我们能想到另外一个问题“最大子数组”,
最大子数组:在数组中A[a1,a2,a3,a4,…,aN]中,查找一个和最大的子数组,比如:
这里写图片描述
一种解决“最大子矩阵”问题的方法就是将这个问题转化为“最大子数组”问题
具体来说就是:对2D矩阵A,将第i行到第j行对应列的元素加起来,形成一个数组,然后从该数组中选择最大子数组,比如下图:
LeetCode--Dynamic Programming_第8张图片
依次求i([0,n])行到j([i,n])行的数组的最大子数组,其中的最大值即为最大子矩阵。


上面已经讲解了求解最大子矩阵的方法,在本题中,多了一个限制条件,即子矩阵内元素和不能超过K。因此在上面的基础上,还需要修改最大子数组的算法,使得最大子数组的和不超过K。具体实现见下面代码。LeetCode Accept。

C++代码实现:
最大子矩阵:

class Solution {
public:
    //求最大子矩阵
    int maxSumSubmatrix(vector<vector<int>>& matrix) {
        int row = matrix.size(), col = matrix[0].size();
        vector<int> sums(col,0);    //记录从i行到j行元素的和
        int result = INT_MIN;
        int temp = 0;
        for(int i=0; ifor(int j=i; jfor(int k=0; kif(temp > result)
                    result = temp;
            }
            sums.assign(col,0);          //清空sums
        }
        return result;
    }
    //求最大子数组
    int maxSubArray(vector<int>& nums,int n) {  
        vector<int> sums(n,0);    //sums[i]表示以i结尾的子数组最大值
        sums[0] = nums[0];
        int maxsum = sums[0];
        for(int i=1; iif(sums[i-1]<0)
                sums[i] = nums[i];
            else
                sums[i] = sums[i-1] + nums[i];
            if(sums[i]>maxsum)
                maxsum = sums[i];
        }
        return maxsum;
    }
};

和不超过K的最大子矩阵:

class Solution {
public:
    int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
        int row = matrix.size(), col = matrix[0].size();
        vector<int> sums(col,0);    //记录从i行到j行元素的和
        int result = INT_MIN;
        int temp = 0;
        for(int i=0; ifor(int j=i; jfor(int k=0; kif(temp==k) return k;
                if(temp < k && temp > result)
                    result = temp;
            }
            sums.assign(col,0);          //清空sums
        }
        return result;
    }
    int maxSubArrayNLTK(vector<int>& nums,int k){
        set<int> sumset;
        sumset.insert(0);
        int result = INT_MIN, sum = 0;
        for(int i=0; iauto it = sumset.lower_bound(sum-k);
            if(it!=sumset.end() && result < (sum-*it)) //sum-*it表示去掉某段子序列的和,使得result最接近K
                result = sum-*it;
            sumset.insert(sum);
        }
        return result;
    }
};

403. Frog Jump

A frog is crossing a river. The river is divided into x units and at each unit there may or may not exist a stone. The frog can jump on a stone, but it must not jump into the water.
Given a list of stones’ positions (in units) in sorted ascending order, determine if the frog is able to cross the river by landing on the last stone. Initially, the frog is on the first stone and assume the first jump must be 1 unit.
If the frog’s last jump was k units, then its next jump must be either k - 1, k, or k + 1 units. Note that the frog can only jump in the forward direction.

Note:

The number of stones is ≥ 2 and is < 1,100.
Each stone’s position will be a non-negative integer < 231.
The first stone’s position is always 0.

Example 1:

[0,1,3,5,6,8,12,17]
There are a total of 8 stones.
The first stone at the 0th unit, second stone at the 1st unit,
third stone at the 3rd unit, and so on…
The last stone at the 17th unit.
Return true. The frog can jump to the last stone by jumping
1 unit to the 2nd stone, then 2 units to the 3rd stone, then
2 units to the 4th stone, then 3 units to the 6th stone,
4 units to the 7th stone, and 5 units to the 8th stone.

Example 2:

[0,1,2,3,4,8,9,11]
Return false. There is no way to jump to the last stone as
the gap between the 5th and 6th stone is too large.

解析:

如果第一步大于1,或者最后一个数字大于本次青蛙所能跳的最大位置,结果为false。(这样能直接排除很多不合理的case。)
(1)方法一:深度搜索。每走一步,都有3中选择:跳k-1、k、k+1步。
(2)方法二:动态规划。基本的思路就是基于每个stone都建立一个走到它的步骤,然后看它能走到哪些其他的stone,如果可以走到就在那个stone记下这个步骤大小,依次类推,看能不能走到最后一个。

C++代码实现:
深度搜索:Accept,用时:16ms/39 test case,beat 97.02% submission

class Solution {
public:
    bool canCross(vector<int>& stones) {
        int n = stones.size();
        if(n<1) return false;
        if(stones[1]-stones[0] > 1)     //the first jump must be 1
            return false;
        int biggest = stones[0] + n*(n-1)/2;    //数列中允许的最大数字
        if(stones[n-1]>biggest)
            return false;
        dp.assign(n,vector<bool>(n,true));
        return backtrack(stones,n,1,1);
    }
private:
    vector<vector<bool>> dp;        //缓存中间结果
    bool backtrack(vector<int>& stones, int n, int k, int current) { //k表示上次跳的距离,current表示当前所在位置
        if(current>=n-1)    return true;    //跳到最后一块,成功
        if(stones[current+1]-stones[current]> k+1)
            return false;
        if(dp[current][k]==false)
            return false;
        int step = k-1;
        int next = current+1;
        while(step <= k+1) {
            while(next < n && stones[next]-stones[current]if(stones[next]-stones[current]==step && backtrack(stones,n,step,next))
                return true;
            step++;
        }
        dp[current][k] = false;
        return false;
    }
};

动态规划:

class Solution {
public:
    bool canCross(vector<int>& stones) {
        int n = stones.size();
        if(n<1) return false;
        if(stones[1]-stones[0] > 1)     //the first jump must be 1
            return false;
        int biggest = stones[0] + n*(n-1)/2;    //数列中允许的最大数字
        if(stones[n-1]>biggest)
            return false;
        unordered_map<int, unordered_set<int>> res;
        res[0].insert(0);
        res[1].insert(1);
        int i,  k, maxstep=1;
        for (i = 1; i < stones.size(); i++) {
            for (k = i + 1; k < stones.size(); k++) {
                int dist = stones[k] - stones[i];
                if (res[i].count(dist-1) || res[i].count(dist) || res[i].count(dist+1)) {
                    res[k].insert(dist);
                    maxstep = max(dist, maxstep);
                }
                else {
                    if ((stones[k]-stones[i]) > maxstep) break;
                }
            }

        }
        if (res[stones.size()-1].size() > 0) return true;
        else return false;
    }

};

174. Dungeon Game

The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon. The dungeon consists of M x N rooms laid out in a 2D grid. Our valiant knight (K) was initially positioned in the top-left room and must fight his way through the dungeon to rescue the princess.
The knight has an initial health point represented by a positive integer. If at any point his health point drops to 0 or below, he dies immediately.
Some of the rooms are guarded by demons, so the knight loses health (negative integers) upon entering these rooms; other rooms are either empty (0’s) or contain magic orbs that increase the knight’s health (positive integers).
In rder to reach the princess as quickly as possible, the knight decides to move only rightward or downward in each step.
Write a function to determine the knight’s minimum initial health so that he is able to rescue the princess.
For example, given the dungeon below, the initial health of the knight must be at least 7 if he follows the optimal path RIGHT-> RIGHT -> DOWN -> DOWN.
LeetCode--Dynamic Programming_第9张图片

Notes:

The knight’s health has no upper bound.
Any room can contain threats or power-ups, even the first room the knight enters and the bottom-right room where the princess is imprisoned.

解析:

由于最终目标是骑士到达公主位置,因此在任何位置必须满足HP不小于1.
从右下角位置开始倒推,每个位置需要同时满足两个条件:(1)该位置HP至少为1(保证不死),(2)该位置的HP足够到达公主(使用动态规划)
用dp[i][j]表示走到dungeon[i][j]所需最少血量。

C++代码实现:

class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int m = dungeon.size();
        if(m==0)    return 0;
        int n = dungeon[0].size();
        vector<vector<int>> dp(m,vector<int>(n,0));      //表示走到dungeon[i][j]最少所需血量

        for(int i=m-1; i>=0; i--) {
            for(int j=n-1; j>=0; j--) {
                if(i == m-1 && j == n-1)
                    dp[i][j] = max(1, 1-dungeon[i][j]);     //如果dungeon[i][j]<0,则至少需要1-dungeon[i][j];否则,需要1
                else if(i == m-1)
                    dp[i][j] = max(1, dp[i][j+1]-dungeon[i][j]);    //向右
                else if(j == n-1)
                    dp[i][j] = max(1, dp[i+1][j]-dungeon[i][j]);    //向下
                else 
                    dp[i][j] = max(1, min(dp[i+1][j]-dungeon[i][j], dp[i][j+1]-dungeon[i][j]));
            }
        }
        return dp[0][0];
    }
};

638. Shopping Offers

In LeetCode Store, there are some kinds of items to sell. Each item has a price.
However, there are some special offers, and a special offer consists of one or more different kinds of items with a sale price.
You are given the each item’s price, a set of special offers, and the number we need to buy for each item. The job is to output the lowest price you have to pay for exactly certain items as given, where you could make optimal use of the special offers.
Each special offer is represented in the form of an array, the last number represents the price you need to pay for this special offer, other numbers represents how many specific items you could get if you buy this offer.
You could use any of special offers as many times as you want.

Example 1:

Input: [2,5], [[3,0,5],[1,2,10]], [3,2]
Output: 14
Explanation:
There are two kinds of items, A and B. Their prices are 2and 5 respectively.
In special offer 1, you can pay 5for3Aand0BInspecialoffer2,youcanpay 10 for 1A and 2B.
You need to buy 3A and 2B, so you may pay 10 for 1A and 2B (special offer #2), and4 for 2A.

Example 2:

Input: [2,3,4], [[1,1,0,4],[2,2,1,9]], [1,2,1]
Output: 11
Explanation:
The price of A is 2,and 3 for B, 4forC.Youmaypay 4 for 1A and 1B, and 9for2A,2Band1C.Youneedtobuy1A,2Band1C,soyoumaypay 4 for 1A and 1B (special offer #1), and 3for1B, 4 for 1C.
You cannot add more items, though only $9 for 2A ,2B and 1C.

Note:

There are at most 6 kinds of items, 100 special offers.
For each item, you need to buy at most 6 of them.
You are not allowed to buy more items than you want, even if that would lower the overall price.

解析:

递归搜索。每一次买只有两种选择:(1)按原价买;(2)买special。
买special的时候,应该选择付钱最少的special进行购买。
从上述两种选择中,选择花钱较少的,即可。

C++代码实现

class Solution {
public:
    int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {
        return shopping(price,special,needs);
    }
    //递归搜索
    //每一次只有两种选择:1、按原价买 2、买special(选付钱最少的)
    int shopping(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {
        int result = 0, j = 0;
        result = calOrigin(price,needs);                  //按原价买
        for(vector<int> s : special) {                    //买special
            vector<int> rest(needs.begin(),needs.end());  
            for( j=0; jint diff = rest[j] - s[j];       
                if(diff < 0)
                    break;
                rest[j] = diff;
            }
            if(j==needs.size())
                result = min(result,s[j] + shopping(price,special,rest));
        }
        return result;
    }
    //计算商品原价
    int calOrigin(vector<int>& price,vector<int>& needs) {
        int size = price.size(), total = 0;
        for(int i=0; ireturn total;
    }
};

你可能感兴趣的:(leetcode,动态规划,LeetCode,动态规划)