【Leetcode 動態規劃】 不知如何分類 就都放這裏了

312. Burst Balloons

Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.

Find the maximum coins you can collect by bursting the balloons wisely.

Note: 
(1) You may imagine nums[-1] = nums[n] = 1. They are not real therefore you can not burst them.
(2) 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

題意:
給定n個氣球。每次你可以打破一個,打破第i個,那麼你會獲得nums[left] * nums[i] * nums[right]個積分。 如果旁邊沒有氣球了,則按1算,以此類推,求能得到的最多金幣數

思路:

我們維護一個二維動態數組dp,其中dp[i][j]表示打爆區間[i,j]中的所有氣球能得到的最多金幣。題目中說明了邊界情況,當氣球周圍沒有氣球的時候,旁邊的數字按1算,這樣我們可以在原數組兩邊各填充一個1,這樣方便於計算。【即新建的vector的大小是n+2維! 如果還是n維 就會超時】

遞推式爲:dp[i][j] = max(dp[i][j],  dp[i][k - 1] + nums[i - 1]*nums[k]*nums[j + 1] + dp[k + 1][j])                 ( i ≤ k ≤ j )

含義:打破i~j間氣球所得金幣 = 之前的or打破i~k-1間氣球所得金幣 + 打破k+1~j間氣球所得金幣

nums[i - 1]*nums[k]*nums[j + 1] 這次打破k的:因爲i~k-1破了,所以他的左邊相鄰是nums[i-1],同理,右邊相鄰是nums[j+1]

代碼:

注意點:1.新建的dp數組必須是n+2維;2.left的範圍是1~(n-打破長度+1),而不是1~n【會超時】;3.return的是dp[1][n]【本應是dp[0][n-1],但因爲前面加了邊界的1,所以~

class Solution {
public:
    int maxCoins(vector<int>& nums) {
        int n = nums.size();
        if(n == 0) return 0;
        nums.insert(nums.begin(), 1);
        nums.insert(nums.end(), 1);
        vector<vector<int> > dp (n + 2, vector<int>(n + 2, 0));
        
        for(int len = 1; len <= n; ++len) {
            for(int left = 1; left <= n - len + 1; ++left) {
                int right = left + len - 1;
                for(int k = left; k <= right; ++k) {
                    dp[left][right] = max(dp[left][right],  dp[left][k-1] + nums[left-1]*nums[k]*nums[right+1] + dp[k + 1][right]);
                }
            }
        }
        return dp[1][n];
    }
};


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.

題意:給一組nums代表所擁有的硬幣面額,amount代表和。求組成amount所需的硬幣最小數目,若不能組成amount則返回-1。

思路:

dp[i][v], which is the minimum number of coins used to sum up to v, and i is the number of different denominations used for this sum (use the first i denominations in coins).

f[i][v] = min {case1, case2}

Case1 is f[i-1][v], where coins[i] isn't used;

Case2 is f[i][v-coins[i-1]]+1, where coins[i] is used (and can be used multiple times)

So the weight-lifting work is now finished, and all we have to do is to iterate i from 1 to coins.size() (set count[0] to 0, obviously), while for each i we update coins[v] from v=cost[i-1] to v=amount (no need to start from v=0, because count[v-coins[i-1]] is meaningful only when v-coins[i-1]>0

After finishing all this, return count[amount] as the final result (if count[amouint] is still INT_MAX, which means the search fails, then return -1)

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, amount + 1);//初值amount+1,表達INT_MAX的功能,若用後者會有潛在風險,因爲後文會用到該值+1,則變成0x80000000,最小值了
        dp[0] = 0;
        
        for(int i = 0; i < coins.size(); ++i) {
            for(int v = coins[i]; v <= amount; ++v)
                dp[v] = min(dp[v], dp[v - coins[i]] + 1);
        }
        return (dp[amount] == amount + 1)? -1: dp[amount];
    }
};




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].

意思是給你一個非負整數num,對於 0~num 這(num+1)個整數,求出每個數用二進制表示時1的個數。
最簡單的思路:對每個數,利用移位和按位與(i & 1)運算,計算1的個數。這樣時間複雜度爲O(n*sizeof(integer)),如果int用32位表示,那麼時間複雜度就是O(32n)。
考慮優化成O(n):

對於11這個數,我們暫時用一個字節來表示

11:           0000 1011
11/2 = 5: 0000 0101

容易發現,除了11最右邊那個位和5的最高位,其他位對應一樣。也就是說i用二進制表示時1出現的次數等於i/2中1出現的次數加1(如果i用二進制表示時最右邊一位爲1,否則不加1)。這樣我們在計算i時可以利用前面已計算出的i/2:ret[i] = ret[i/2] + (i % 2 == 0 ? 0 : 1)  即 ret[i] = ret[i/2] + i % 2。

vector<int> countBits(int num) {  
    vector<int> ret(num + 1, 0);  
    for(int i = 1; i <= num; ++i)  
        ret[i] = ret[i>>1] + i % 2;  
    return ret;   
}


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.

Hint:

  1. There is a simple O(n) solution to this problem.
  2. You may check the breaking results of n ranging from 7 to 10 to discover the regularities.
這道題給了我們一個正整數n,讓我們拆分成至少兩個正整數之和,使其乘積最大,題目提示中讓我們用O(n)來解題,而且告訴我們找7到10之間的規律,那麼我們一點一點的來分析:

從1開始,但是1不能拆分成兩個正整數之和,所以不能當輸出。
2只能拆成1+1,所以乘積也爲1。
數字3可以拆分成2+1或1+1+1,顯然第一種拆分方法乘積大爲2。
數字4拆成2+2,乘積最大,爲4。
數字5拆成3+2,乘積最大,爲6。
數字6拆成3+3,乘積最大,爲9。
數字7拆爲3+4,乘積最大,爲12。
數字8拆爲3+3+2,乘積最大,爲18。
數字9拆爲3+3+3,乘積最大,爲27。
數字10拆爲3+3+4,乘積最大,爲36。
....

那麼通過觀察上面的規律,我們可以看出從5開始,數字都需要先拆出所有的3,一直拆到剩下一個數爲2或者4,因爲剩4就不用再拆了,拆成兩個2和不拆沒有意義,而且4不能拆出一個3剩一個1,這樣會比拆成2+2的乘積小。那麼這樣我們就可以寫代碼了,先預處理n爲2和3的情況,然後先將結果res初始化爲1,然後當n大於4開始循環,我們結果自乘3,n自減3,根據之前的分析,當跳出循環時,n只能是2或者4,再乘以res返回即可:

int integerBreak(int n) {
    if (n == 2 || n == 3)
		return n - 1;
    int res = 1;
    while (n > 4) {
        res *= 3;
        n -= 3;
    }
    return res * n;
}



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位數中,各個位的數字都不相同的數一共有多少個。

思路:

令 f(n) 爲所求結果。
f(1) = 10. (0, 1, 2, 3, ...., 9)
f(2) = 9 * 9. Because for each number i from 1, ..., 9, we can pick j to form a 2-digit number ij and there are 9 numbers that are different from i for j to choose from.
f(3) = f(2) * 8 = 9 * 9 * 8. Because for each number with unique digits of length 2, say ij, we can pick k to form a 3 digit number ijk and there are 8 numbers that are different from i and j for k to choose from.

Similarly f(4) = f(3) * 7 = 9 * 9 * 8 * 7....
...
f(10) = 9 * 9 * 8 * 7 * 6 * ... * 1


f(11) = 0 = f(12) = f(13)....

Hence return f(1) + f(2) + .. + f(n)

class Solution {
public:
    int countNumbersWithUniqueDigits(int n) {
        if(n == 0) return 1;
        
        int result = 10;
        int uniqueNumber = 9;
        int availbleNumber = 9;//即f()
        for(int i = 1; i < n; ++i) {
            availbleNumber *= uniqueNumber;
            result += availbleNumber;
            uniqueNumber--;
            if(uniqueNumber <= 0)
                break;
        }
        return result;
    }
};



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
題意:找出這樣一個序列,其相鄰元素的差值 交替正負(0不算),該序列不要求連續。 

代碼:(貪婪思想)

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n = nums.size();
        if(n < 2) return n;
        
        int flag = 0;
        int count = 1;
        for(int i = 1; i < n; ++i) {
            if(nums[i] < nums[i -1] && (flag == 0 || flag == 1)) {
                flag = -1;
                ++count;
            }
            if(nums[i] > nums[i - 1] && (flag == 0 || flag == -1)) {
                flag = 1;
                ++count;
            }
        }
        return count;
    }
};










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         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3
本題使用一維線性規劃解決。

如果n == 0時,結果爲0;
如果n == 1時,只有一個節點,結果爲1;
如果n == 2時,根節點有兩種選擇,結果爲2;
如果n >= 3時,
n個點中每個點都可以作爲root,當 i 作爲root時,小於 i  的點都只能放在其左子樹中,大於 i 的點只能放在右子樹中,此時只需求出左、右子樹各有多少種,二者相乘即爲以 i 作爲root時BST的總數。

class Solution {
public:
    int numTrees(int n) {
        if(n <= 2)
            return n;
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        dp[1] = 1;
        dp[2] = 2;
        
        for(int i = 3; i <= n; ++i) {
            int tmp = 0;
            for(int j = 0; j < i; ++j) {
                tmp += dp[j] * dp[ i - j - 1];
            }
            dp[i] = tmp;
        }
        return dp[n];
    }
};
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?


一、題目描述:

給定一個m*n的矩陣,讓機器人從左上方走到右下方,只能往下和往右走,一共多少種走法。

二、解題方法:

//動態規劃:
//設狀態爲f[i][j],表示從起點(1;1)到達(i; j)的路線條數,則狀態轉移方程爲:
//f[i][j] = f[i-1][j] + f[i][j-1]
class Solution {
public:
	int uniquePaths(int m, int n) {
		vector<vector<int> > f(m, vector<int>(n, 1));
		for (int i = 1; i < m; i++)
			for (int j = 1; j < n; j++)
				f[i][j] = f[i - 1][j] + f[i][j - 1];
		return f[m - 1][n - 1];
	}
};
上面方法的空間複雜度較大爲O(m*n),然而通過觀察可以發現,我們每次更新f[i][j]只需要f[i-1][j](同一列)和f[i][j-1](左一列),所以只要保存當前列和左一列就行,而不是整個m*n矩陣,下面的代碼可以將空間複雜度優化到O(min(m,n))
class Solution {
	int uniquePaths(int m, int n) {
		if (m > n) return uniquePaths(n, m);
		vector<int> pre(m, 1);
		vector<int> cur(m, 1);
		for (int j = 1; j < n; j++) {
			for (int i = 1; i < m; i++)
				cur[i] = cur[i - 1] + pre[i];
			swap(pre, cur);
		}
		return pre[m - 1];
	}
};
通過進一步的觀察,我們還可以發現,上面程序中的pre[i]就是更新前的cur[i],所以可以進一步優化爲:
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];
    }
}; 
最終優化空間程序爲:(其實先遍歷m還是先遍歷n都無所謂的。關鍵是要注意 vector的長度 與 內層for循環的長度 是一樣的~!)
class Solution{
public:
	int uniquePaths(int m, int n) {
		if (m == 0 && n == 0)
			return 0;

		vector<int> dp(n, 1);
		for (int i = 1; i < m; i++)
			for (int j = 1; j < n; j++)
				dp[j] = dp[j - 1] + dp[j];

		return dp[n - 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 Paths 差不多,只是這道題給機器人加了障礙,不是每次都有兩個選擇(向右,向下)了。
因爲有了這個條件,所以 Unique Paths 中最後一個直接求組合的方法就不適用了,這裏最好的解法就是用動態規劃了。
遞推式還是跟 Unique Paths 一樣,只是每次我們要判斷一下是不是障礙,如果是障礙,則dp[i][j] = 0;
否則還是dp[i][j] = dp[i - 1][j] + dp[i][j - 1]。

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();
        if(m == 0 || n ==0)
            return 0;
        
        vector<int> dp(n);
        dp[0] = 1;
        for(int i = 0; i < m; ++i) {
            for(int j = 0; j < n; ++j) {
                if(obstacleGrid[i][j] == 1)
                    dp[j] = 0;
                else if(j > 0)
                    dp[j] += dp[j - 1];
            }
        }
        return dp[n - 1];
    }
};
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.

這是動態規劃的問題,由於每次只能向下或者向右移動,因此[i, j]位置時的最小路徑的和等於[i, j-1] 與 [i-1, j]中較小的加上[i, j]位置的數值。

因此遞推公式是grid[i][j] += min(grid[i][j-1],  grid[i-1][j])。

時間複雜度:O(mn)
空間複雜度:O(mn)

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        if(m == 0 && n == 0)
            return 0;
        
        vector<vector<int> > dp (m, vector<int>(n, 0));
        dp[0][0] = grid[0][0];
        
        for(int i = 1; i < m; ++i)
            dp[i][0] += grid[i][0] + dp[i - 1][0];
        for(int i = 1; i < n; ++i)
            dp[0][i] += grid[0][i] + dp[0][i - 1];
            
        for(int i = 1; i < m; ++i) {
            for(int j = 1; j < n; ++j) {
                dp[i][j] += grid[i][j] + min(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        return dp[m - 1][n - 1];
    }
};


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.

這道題說是給我們一個正整數,求它最少能由幾個完全平方數組成。

用動態規劃Dynamic Programming來做,我們建立一個長度爲n+1的一維dp數組,將第一個值初始化爲0,其餘值都初始化爲INT_MAX.i從0循環到n,j從1循環到i+j*j <= n的位置,然後每次更新dp[i+j*j]的值,動態更新dp數組,其中dp[i]表示正整數i能少能由多個完全平方數組成,那麼我們求n,就是返回dp[n]即可,也就是dp數組的最後一個數字,參見代碼如下:

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

        vector<int> dp(n + 1, 0x7fffffff);
        for(int i = 0; i * i <= n; ++i)
            dp[i * i] = 1;
        
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; i + j * j <= n; ++j) {
                dp[i + j * j] = min(dp[i] + 1, dp[i + j * j]);
            }
        }
        return dp[n];
    }
};

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.

分析:

求一個三角形二維數組從頂到低端的最小路徑和。每次只能挪一個位置。
我們從低端向頂端計算。設狀態爲 S[i][j]表示從從位置 ( i, j ) 出發,到最低端路徑的最小和
狀態轉移方程:S[i][j] = min(S[i+1][j] + S[i+1][j+1]) +S[i][j]
S[0][0]就是要求解的答案。
時間複雜度 O(n^2) ,空間複雜度 O(1)

class Solution {
public:
    int minimumTotal(vector<vector<int> > &triangle) {
        int size = triangle.size();
        // down-to-top
        // 第i層
        for(int i = size - 2;i >= 0;--i){
            // 第i層的第j個元素
            for(int j = 0;j <= i;++j){
                triangle[i][j] += min(triangle[i+1][j], triangle[i+1][j+1]);
            }
        }
        return triangle[0][0];
    }
};

從上面思路的狀態轉移方程中看出:S[i][j] = min(S[i+1][j] + S[i+1][j+1]) +S[i][j]
S[i][j]只與下一行的第j個元素和第j+1個元素相關,i的關係是固定的,因此我們可以省去這一維。
開闢O(N)的數組,然後規劃的時候使用S[j] = min(S[j+1], S[j) +Triangle[i][j]就可以了。

class Solution {
    public:
    int minimumTotal(vector<vector<int> > &triangle) {
        int n = triangle.size();
        vector<int> dp(triangle.back());//dp初值設爲triangle的最後一行
        // down-to-top
        // 第i層
        for(int i = n - 2;i >= 0;--i){
            // 第i層的第j個元素
            for(int j = 0;j <= i;++j){
                dp[j] = min(dp[j], dp[j+1]) + triangle[i][j];
            }
        }
        return dp[0];
    }
};


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.

題意:1-26對應於A-Z,這樣一個數字字符串可以解碼成只包含A-Z的字符串。實現numDecodings(string s)接受數字字符串,返回可以解碼的方式數。例如,12可以解碼成AB,也可以解碼成L。這樣12就有兩種解碼方式。但要注意的是像10,就只能解成J了,因爲單個的0無法解碼,因此只有一種解碼方式。

思路:每次對於當前的字符判斷是否屬於1-9(0肯定不行,因爲0不在1-26中),如果屬於,那麼當前的字符可以被decode,並且和f[n-1]組合,f[n] += f[n-1]
然後對於當前字符和前一個字符組成的字符串判斷是否屬於10-26,如果屬於,那麼這兩個字符可以被decode,並且和f[n-2]組合,f[n] += f[n-2]

而result[1]初始化時不要出錯了,它 = (check(s[0]) & check(s[1])) + check(s[0], s[1]); 

class Solution {
public:
    int checkOne(char a){
        return (a == '0') ? 0: 1;
    }
    int checkTwo(char a, char b){
        return (a == '1' || a == '2' && b>= '0' && b <= '6')? 1: 0;
    }
    int numDecodings(string s) {
        int length = s.size();
        if(length == 0) return 0;
        if(length == 1) return checkOne(s[0]);
        
        vector<int> result(length + 1, 0);
        result[0] = checkOne(s[0]);
        result[1] = checkTwo(s[0], s[1]) + (checkOne(s[0]) & checkOne(s[1]));

        for(int i = 2; i < length; ++i) {
            if(checkOne(s[i]))
                result[i] += result[i - 1];
            if(checkTwo(s[i - 1], s[i]))
                result[i] += result[i - 2];
        }
        return result[length - 1];
    }
};

至此可見,有點像斐波那契數列,只需記錄下“上一個”和“上上個”的結果即可,無需O(n)空間。不再贅附代碼。




72. Edit Distance 編程之美里的“字符串相似度”

Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.)

You have the following 3 operations permitted on a word:

a) Insert a character
b) Delete a character
c) Replace a character

思路:

dp[i][j]指把word1[0..i - 1]轉換爲word2[0..j - 1] 的最小操作數。

邊界條件:

dp[i][0] = i; 從長度爲 i 的字符串轉爲空串 要刪除 i 次
dp[0][j] = j. 從空串轉爲長度爲 j 的字符串 要添加 j 次

一般情況:

如果word[i - 1] == word2[j - 1],則dp[i][j] = dp[i - 1][j - 1],因爲不需要進行操作,即操作數爲0.

如果word[i - 1] != word2[j - 1],則需考慮三種情況,取最小值:

Replace word1[i - 1] by word2[j - 1]: (dp[i][j] = dp[i - 1][j - 1] + 1 (for replacement));
Delete word1[i - 1]:                             (dp[i][j] = dp[i - 1][j] + 1 (for deletion));
Insert word2[j - 1] to word1[0..i - 1]:   (dp[i][j] = dp[i][j - 1] + 1 (for insertion)).

class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size();
        int n = word2.size();
        vector<vector<int> > dp(m + 1, vector<int>(n + 1, 0));
        
        //邊界條件
        for(int i = 1; i <= m; ++i) //從長度爲i的字符串轉爲空串 要刪除i次~
            dp[i][0] = i;
        for(int j = 1; j <= n; ++j)//從空串轉爲長度爲j的字符串 要添加j次~
            dp[0][j] = j;
        
        for(int i = 1; i <= m; ++i) {
            for(int j = 1; j <= n; ++j) {
                if(word1[i - 1] == word2[j - 1])
                    dp[i][j] = dp[i - 1][j - 1];
                else
                    dp[i][j] = min(dp[i - 1][j - 1] + 1, min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
            }
        }
        return dp[m][n];
    }
};
可以發現,當我們更新dp[i][j]時,我們只需要dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]。所以,我們不必記錄整個m*n矩陣。事實上,我們只維護一列就夠了。空間複雜度可以被優化爲O(m) 【維護一列】或 O(n)【維護一行】。
優化爲代碼爲:

class Solution { 
public:
    int minDistance(string word1, string word2) {
        int m = word1.length(), n = word2.length();
        vector<int> cur(m + 1, 0);
        for (int i = 1; i <= m; i++)
            cur[i] = i;
        for (int j = 1; j <= n; j++) {
            int pre = cur[0];
            cur[0] = j;
            for (int i = 1; i <= m; i++) {
                int temp = cur[i];
                if (word1[i - 1] == word2[j - 1])
                    cur[i] = pre;
                else cur[i] = min(pre + 1, min(cur[i] + 1, cur[i - 1] + 1));
                pre = temp;
            }
        }
        return cur[m]; 
    }
}; 

f[i][j] only depends on f[i-1][j-1], f[i-1][j] and f[i][j-1], we can reduce the space to O(n) by using only the (i-1)th array and previous updated element(f [i] [j - 1]).【上法是用一列,所以for循環先n後m。此處用一行,for循環就先m後n,即內層個數 = vector長度】

int minDistance(string word1, string word2) {
    int m = word1.size();
    int n = word2.size();
    
    vector<int> dp(n+1, 0);
    for (int j = 1; j <= n; ++j)
        dp[j] = j;
    
    for (int i = 1; i <= m; ++i) {
        int prev = i;
        for (int j = 1; j <= n; ++j) {
            int cur;
			
            if (word1[i-1] == word2[j-1])
                cur = dp[j-1];
            else 
                cur = min(min(dp[j-1], prev), dp[j]) + 1;
    
            dp[j-1] = prev;
            prev = cur;
        }
        dp[n] = prev;
    }
    return dp[n];
}  







115. Distinct Subsequences

Given a string S and a string T, count the number of distinct subsequences of T in S.

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).

Here is an example:
S = "rabbbit"T = "rabbit"

Return 3.

思路:

我們維護dp[i][j],對應的值是S的前i個字符和T的前j個字符有多少個可行的序列(注意這道題是序列,不是子串,也就是隻要字符按照順序出現即可,不需連續出現)。下面來看看遞推式,假設我們現在擁有之前的歷史信息,我們怎麼在常量操作時間內得到dp[i][j]。

假設S的第i個字符和T的第j個字符不相同,那麼就意味着dp[i][j]的值跟dp[i-1][j]是一樣的,前面該是多少還是多少,而第i個字符的加入也不會多出來任何可行結果。如果S的第i個字符和T的第j個字符相同,那麼所有dp[i-1][j-1]中滿足的結果都會成爲新的滿足的序列,當然dp[i-1][j]的也仍是可行結果,所以dp[i][j] = [i-1][j-1] + dp[i-1][j]。

例子:

不同時:S = ABCDE, T = F。此時S[6] != T[1], dp[5][1] = dp[4][1]【即S變成ABCD,少了一個】

相同時:S = ABCDBB, T = AB。 此時S[6] == T[2]。

dp[6][2]【意爲S的前6個裏面找T的前2個的次數】 =  dp[5][1] 【T中的B是S中的最後一個B,即就是這個二者相等的B。那麼問題就變成了S = ABCDB, T = A,即dp[5][1]】 +  dp[5][2]【T中的B不是S中的最後一個B,問題就變成了S = ABCDB, T = AB,即dp[5][2]】

所以綜合上面兩種情況,遞推式應該是dp[i][j]=(S[i]==T[j]?dp[i-1][j-1]:0)+dp[i][j]。算法進行兩層循環,時間複雜度是O(m*n)

class Solution {
public:
    int numDistinct(string s, string t) {
        int m = s.size(), n = t.size();
        if(m == 0 || m < n)
            return 0;
            
        vector<vector<int> > dp(m + 1, vector<int>(n + 1, 0));
        for(int i = 0; i < m; ++i)//從任意長度的字符串轉化爲空串,只有全部刪除這一種方法
            dp[i][0] = 1;

        for(int i = 1; i <= m; ++i) {
            for(int j = 1; j <= n; ++j) {
                if(s[i - 1] == t[j - 1])
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                else
                    dp[i][j] = dp[i - 1][j];
            }
        }
        return dp[m][n];
    }
};
發現每次更新只跟[i-1]行有關,所以可以優化空間複雜度。

class Solution {
public:
    int numDistinct(string s, string t) {
        int m = s.size(), n = t.size();
        if(m == 0 || m < n)
            return 0;
            
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        
        for(int i = 1; i <= m; ++i) {
            int pre = 1;
            for(int j = 1; j <= n; ++j) {
                int tmp = dp[j];
                dp[j] = dp[j] + (s[i-1] == t[j-1] ? pre: 0);//+比 三目運算符優先級別要高!!!! 所以要把整個的三目運算符用括號括起來
                pre = tmp;
            }
        }
        return dp[n];
    }
};


131. Palindrome Partitioning

Given a string s, partition s such that every substring of the partition is a palindrome.

Return all possible palindrome partitioning of s.

For example, given s = "aab",
Return

[
  ["aa","b"],
  ["a","a","b"]
]
題意:給出一個string s,把它分割成一些子串,要求每個子串都是迴文的。求所有可能。

思路:DFS,去找所有可能,有點類似劍指offer 28 字符串的全排列,只是多了一個判斷條件:是否爲迴文。

步驟:從左到右遍歷字符串,當傳入起點爲index時,判斷s(index,i)作爲一個子串是否迴文。若是迴文,將其保存至tmp中,接着調用dfs函數繼續處理後面的部分s(i+1, ...)。當遍歷至結尾,將tmp保存到result中,作爲一種情況。

class Solution {
public:
    bool IsPalindrome(const string s, int start, int end) {
        while(start < end) {
            if(s[start] != s[end])
                return false;
            start++, end--;
        }
        return true;
    }
    
    void dfs(int index, string s, vector<string>& tmp, vector<vector<string> >& result) {
        if(index == s.size()) {
            result.push_back(tmp);
            return;
        }
        for(int i = index; i < s.size(); ++i) {
            if(IsPalindrome(s, index, i)) {
                tmp.push_back(s.substr(index, i - index + 1));//substr的兩個參數:起點,長度
                dfs(i + 1, s, tmp, result);
                tmp.pop_back();
            }
        }
    }
    
    vector<vector<string>> partition(string s) {
        vector<vector<string> > result;
        if(!s.empty()) {
            vector<string> tmp;
            dfs(0, s, tmp, result);
        }
        return result;
    }
};


132. Palindrome Partitioning II

Given a string s, partition s such that every substring of the partition is a palindrome.

Return the minimum cuts needed for a palindrome partitioning of s.

For example, given s = "aab",
Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.

題意:給出一個string s,求將其分割成迴文串子串,最少分割幾次。

class Solution {
public:
    int minCut(string s) {
        int n = s.size();
        if(n <= 1) return 0;
        
        vector<int> minCut(n + 1);//minCut[i]代表前i個字符(0~i-1)需要的最少分割次數。
        for(int i = 0; i <= n; ++i)
            minCut[i] = i - 1;//賦初值:前i個字符最壞情況下是分割i-1次。
            
        for(int i = 1; i < n; ++i) {//循環,表示以i爲中心,j爲對稱長度的迴文串
            //奇數長度迴文串abcdcbe 此時i是d,畫圖 即懂
            for(int j = 0; i - j >= 0 && i + j < n && s[i - j] == s[i + j]; ++j)
                minCut[i + j + 1] = min(minCut[i + j + 1], minCut[i - j] + 1);
            //偶數長度迴文串abcddcbe  此時的i是第二個d,畫圖,即懂
            for(int j = 0; i - j - 1 >= 0 && i + j < n && s[i - j - 1] == s[i + j]; ++j)
                minCut[i + j + 1] = min(minCut[i + j + 1], minCut[i - j - 1] + 1);
            
            //當內部兩個j的循環結束,說明以i爲對稱中心的處理完了,接着處理下一個
        }
        return minCut[n];
    }
};




































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章