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; i<nums.size(); i++) {
            if(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(tleft<tsize) {
            if(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; i<m; i++) {
            for(int j=0; j<n; j++){
                if(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; i<row; i++) {
            for(int j=0; j<triangle[i].size(); j++) {
                if(i>0){
                    if(j>0 && j<triangle[i-1].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; i<col; i++)
            if(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?

這裏寫圖片描述

解析:

動態規劃。用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; i<m; i++) {
            for(int j=1; j<n; j++) {
                dp[i][j] = dp[i-1][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; i<row; i++){
            dp[i][0] = dp[i-1][0]; 
            if(obstacleGrid[i][0]==1)
                dp[i][0] = 0;  
        }
        for(int i=1; i<col; i++){
            dp[0][i] = dp[0][i-1]; 
            if(obstacleGrid[0][i]==1)
                dp[0][i] = 0;  
        }

        for(int i=1; i<row; i++) {
            for(int j=1; j<col; j++) {
                if(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; i<row; i++) {
            dp[i][0] = matrix[i][0]-'0';
            if(dp[i][0] > result)
                result = dp[i][0];
        }

        for(int j=0; j<col; j++) {
            dp[0][j] = matrix[0][j]-'0';
            if(dp[0][j] > result)
                result = dp[0][j];
        }

        for(int i=1; i<row; i++) {
            for(int j=1; j<col; j++) {
                if(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; j<size; j++) {
                if(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; i<n; i++) {
            if(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; i<n; i++) {
            if(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; i<n; i++) {
            dp[i] = dp[i-1] + nums[i];
            if((k==0 && dp[i]==0) || (k!=0 && dp[i]%k==0))
                return true;
        }

        for(int i=0; i<n; i++) {
            for(int j=i+2; j<n; j++) {
                if((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; j<n; j++){
                if(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(k<i){
                count *= (10-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; i<n; i++) {
            if((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.
這裏寫圖片描述

解析:

使用[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<TreeNode*> generateTrees(int n) {
        vector<TreeNode*> trees;
        if(n<=0)
            return trees;
        return createTrees(1,n);
    }
private:
    vector<TreeNode*> createTrees(int start,int end) {
        vector<TreeNode*> 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<TreeNode*> leftTrees = createTrees(start,i-1);
            vector<TreeNode*> 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; i<n; i++) {
            if(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; i<n; i++) {
            if(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()<n){
            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(不做交易),下面是這三種狀態的狀態轉換圖:
這裏寫圖片描述
從上面的狀態機我們可以推斷出狀態轉移方程:
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; i<n; i++) {
            s0[i] = max(s0[i-1], 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).
這裏寫圖片描述
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; j<n; j++) {
                if(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<total; i++) {
            result.push_back(nums[last]);  //回溯
            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.
這裏寫圖片描述

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<uint>>> dp(N+1,vector<vector<uint>>(m,vector<uint>(n,0)));  //dp[N][m][n]

        for(int k=1; k<=N; k++){
            for(int i=0; i<m; i++) {
                for(int j=0; j<n; j++) {
                    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;
                }
            }
        }
        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<uint>>> dp(2,vector<vector<uint>>(m,vector<uint>(n,0)));  //dp[N][m][n]

        while(N-- > 0){
            for(int i=0; i<m; i++) {
                for(int j=0,nc = (N + 1) % 2, np = N % 2; j<n; j++) {

                    dp[nc][i][j] = ((i == 0 ? 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).

解析:

解決這個問題之前,先來解決“最大子矩陣”問題。
最大子矩陣:即從矩陣中找到一個元素和最大的子矩陣。比如下圖:
這裏寫圖片描述
上圖中,右下角子矩陣和最大爲12。
我們將這個問題描述爲如下形式:
——> MaxSum{第i行到第j行,第r列到第k列},假設矩陣A如下:
這裏寫圖片描述
最笨的方法是:枚舉所有可能的子矩陣。其實沒有必要
從這個問題,其實我們能想到另外一個問題“最大子數組”,
最大子數組:在數組中A[a1,a2,a3,a4,…,aN]中,查找一個和最大的子數組,比如:
這裏寫圖片描述
一種解決“最大子矩陣”問題的方法就是將這個問題轉化爲“最大子數組”問題
具體來說就是:對2D矩陣A,將第i行到第j行對應列的元素加起來,形成一個數組,然後從該數組中選擇最大子數組,比如下圖:
這裏寫圖片描述
依次求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; i<row; i++) {
            for(int j=i; j<row; j++) {
                for(int k=0; k<col; k++) {
                    sums[k] += matrix[j][k];
                }
                temp = maxSubArray(sums,col);
                if(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; i<n; i++) {
            if(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; i<row; i++) {
            for(int j=i; j<row; j++) {
                for(int k=0; k<col; k++) {
                    sums[k] += matrix[j][k];
                }
                temp = maxSubArrayNLTK(sums,k);
                if(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; i<nums.size(); i++) {
            sum += nums[i];
            auto 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]<step)
                next++;
            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.
這裏寫圖片描述

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), and 4 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; j<needs.size(); j++) {
                int 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; i<size; i++) {
            total += price[i]*needs[i];
        }
        return total;
    }
};
發佈了31 篇原創文章 · 獲贊 8 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章