【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& nums) {
        int n = nums.size();
        if(n == 0) return 0;
        nums.insert(nums.begin(), 1);
        nums.insert(nums.end(), 1);
        vector > dp (n + 2, vector(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& coins, int amount) {
        vector 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 countBits(int num) {  
    vector 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& 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 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 > f(m, vector(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 pre(m, 1);
		vector 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 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 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>& obstacleGrid) {
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();
        if(m == 0 || n ==0)
            return 0;
        
        vector 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>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        if(m == 0 && n == 0)
            return 0;
        
        vector > dp (m, vector(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 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 > &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 > &triangle) {
        int n = triangle.size();
        vector 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 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 > dp(m + 1, vector(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 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 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 > dp(m + 1, vector(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 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& tmp, vector >& 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> partition(string s) {
        vector > result;
        if(!s.empty()) {
            vector 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 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];
    }
};




































你可能感兴趣的:(Leetcode,C/C艹,数据结构&算法,【笔面试准备】)