Tag Archives: DP

LeetCode Best Time to Buy and Sell Stock with Cooldown

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

又是股票买卖问题。在LeetCode Best Time to Buy and Sell Stock II的基础上,限制一次交易之后必须冷却一天,隔天才能再次买入。此时就不能再用贪心策略了,还需要用DP。参考这篇博客解法I。 定义两个数组sells和buys,sells[i]表示第i天的动作是卖出,截止当前的最大收益;buys[i]表示第i天的动作是买入,截止当前的最大收益。显然buys[0]=prices[0], sells[0]=0。 令profit=prices[i]-prices[i-1],递推公式如下:
  1. sells[i]=max(buys[i-1]+prices[i], sells[i-1]+profit)
  2. buys[i]=max(sells[i-2]-prices[i], buys[i-1]-profit)
含义为:
  1. 第i天卖出的收益=max(第i-1天买入+第i天卖出的收益,第i-1天卖出~但反悔了~改为第i天卖出)
  2. 第i天买入的收益=max(第i-2天卖出~第i-1天冷却~第i天买入的负收益,第i-1天买入~但反悔了~改为第i天买入)
而事实上:
  1. 第i-1天卖出后反悔,改为第i天卖出 等价于 第i-1天持有股票,第i天再卖出
  2. 第i-1天买入后反悔,改为第i天买入 等价于 第i-1天没有股票,第i天再买入
所求的最大收益为max(sells)。显然,卖出股票时才可能获得收益。完整代码如下: [cpp] class Solution { public: int maxProfit(vector<int>& prices) { int days = prices.size(); if (days < 2)return 0; vector<int> sells(days), buys(days); buys[0] = -prices[0]; int ans = 0; for (int i = 1; i < days; ++i) { int profit = prices[i] – prices[i – 1]; sells[i] = max(buys[i – 1] + prices[i], sells[i – 1] + profit); buys[i] = max(i > 1 ? sells[i – 2] – prices[i] : INT_MIN, buys[i – 1] – profit); ans = max(ans, sells[i]); } return ans; } }; [/cpp] 本代码提交AC,用时6MS。]]>

LeetCode Best Time to Buy and Sell Stock IV

188. Best Time to Buy and Sell Stock IV

Say you have an array for which the i-th element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete at most k transactions.

Note:
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

Example 1:

Input: [2,4,1], k = 2
Output: 2
Explanation: Buy on day 1 (price = 2) and sell on day 2 (price = 4), profit = 4-2 = 2.

Example 2:

Input: [3,2,6,5,0,3], k = 2
Output: 7
Explanation: Buy on day 2 (price = 2) and sell on day 3 (price = 6), profit = 6-2 = 4.
             Then buy on day 5 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3.

这是股票买卖最难的一题!问最多进行k次交易能获得的最大收益是多少。参考网上题解1题解2。 令mustSell[i][j]表示前i天最多交易j次能获得的最大收益,且第i天必须卖出;globalBest[i][j]表示前i天最多交易j次能获得的最大收益,不要求第i天必须卖出。则有如下递推关系:

  1. mustSell[i][j]=max(globalBest[i-1][j-1]+profit, mustSell[i-1][j]+profit)
  2. globalBest[i][j]=max(globalBest[i-1][j], mustSell[i][j])

解释一下,profit=prices[i]-prices[i-1]为第i-1天买入,第i天卖出的收益。

第一个公式中,前i天最多交易j次且第i天卖出的最大收益为“前i-1天最多交易j-1次的全局最大收益+第i-1天买入第i天卖出的收益”“前i-1天最多交易j次且第i-1天卖出的最大收益+第i-1天买入第i天卖出的收益”的较大者。第一部分好理解,第二部分为什么还是j次交易呢,第一眼看上去好像有j+1次交易了,但是因为mustSell[i-1][j]表示第i-1天必须卖出,而profit表示第i-1天要买入,第i天卖出,两者加起来的效果相当于第i-1天的卖出买入抵消了,即交易次数还是j次,而且恰好变成了第i天卖出,和mustSell[i][j]的含义一致。

第二个公式就好理解了,前i天最多交易j次的全局最大收益为“最后一次交易不在第i天发生,则globalBest[i-1][j]”与“最后一次交易在第i天发生,则mustSell[i][j]”的较大者。
如果允许交易的次数k很大,那么以上DP效率较低,可以换成LeetCode Best Time to Buy and Sell Stock II的贪心策略。因为一次交易至少涉及到两天,所以如果k>=prices.size()/2时,可以采用贪心策略。

完整代码如下:

class Solution {
public:
    int maxProfit(int k, vector<int>& prices)
    {
        int days = prices.size();
        if (days < 2)
            return 0;
        if (k >= days / 2) {
            int ans = 0;
            for (int i = 1; i < days; ++i) {
                if (prices[i] > prices[i – 1])
                    ans += (prices[i] – prices[i – 1]);
            }
            return ans;
        }
        vector<vector<int> > mustSell(days, vector<int>(k + 1, 0)), globalBest(days, vector<int>(k + 1, 0));
        for (int i = 1; i < days; ++i) {
            int profit = prices[i] – prices[i – 1];
            for (int j = 1; j <= k; ++j) {
                mustSell[i][j] = max(globalBest[i – 1][j – 1] + profit, mustSell[i – 1][j] + profit);
                globalBest[i][j] = max(globalBest[i – 1][j], mustSell[i][j]);
            }
        }
        return globalBest[days – 1][k];
    }
};

本代码提交AC,用时9MS。

LeetCode Best Time to Buy and Sell Stock III

123. Best Time to Buy and Sell Stock III

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 at most two transactions.

Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).

Example 1:

Input: [3,3,5,0,0,3,1,4]
Output: 6
Explanation: Buy on day 4 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3.
             Then buy on day 7 (price = 1) and sell on day 8 (price = 4), profit = 4-1 = 3.

Example 2:

Input: [1,2,3,4,5]
Output: 4
Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4.
             Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are
             engaging multiple transactions at the same time. You must sell before buying again.

Example 3:

Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.

股票买卖题三。这次限制最多交易两次,问最大能获利多少。这确实是个hard题,研一卜神算法作业中遇到过。 先来回顾一下股票买卖第一题LeetCode Best Time to Buy and Sell Stock,当时的做法是从左往右找到除了当前值的最小值min_so_far,然后用当前值减去min_so_far,表示如果在当前卖出(即用min_so_far买入)能获得的最大收益profit1。这一题允许最多两次交易,那么如果求到在当前买入能获得的最大收益profit2,则profit1+profit2表示在当前卖出后买入能获得的最大收益。计算所有的profit1+profit2的最大值,就是两次交易能获得的最大收益。 计算profit2的方法和计算profit1类似,此时我们从右往左找到除了当前值的最大值max_so_far,则用max_so_far减去当前值,就表示在当前买入(max_so_far卖出)能获得的最大收益profit2。

整理一下思路:我们尝试把每个点作为两次交易的分界点,那么这个点在上一个交易中卖出了,同时在下一个交易中买入了,我们要分别求到在这个点卖出和买入能获得的最大收益,然后把两个值加起来就是在这个点两次交易的最大收益。求所有点的最大收益的最大值就是最终结果。 举个例子,如下表,表中的min_so_far和profit1是从左往右计算的,max_so_far和profit2是从右往左计算的,profit1/2计算的都是当前profit和历史profit的最大值,和代码中的含义一致。

prices326503
min_so_farINT_MAX32220
max_so_far66533INT_MIN
profit1004444
profit2443330
total447774

完整代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices)
    {
        int n = prices.size();
        if (n < 2)
            return 0;
        vector<int> minprice(n), maxprice(n);
        for (int i = 0; i < n; ++i) {
            minprice[i] = i == 0 ? INT_MAX : min(minprice[i – 1], prices[i – 1]);
            maxprice[n – i – 1] = i == 0 ? INT_MIN : max(maxprice[n – i], prices[n – i]);
        }
        vector<int> profit1(n), profit2(n);
        for (int i = 0; i < n; ++i) {
            profit1[i] = i == 0 ? 0 : max(profit1[i – 1], prices[i] – minprice[i]);
            profit2[n – i – 1] = i == 0 ? 0 : max(profit2[n – i], maxprice[n – i – 1] – prices[n – i – 1]);
        }
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            ans = max(ans, profit1[i] + profit2[i]);
        }
        return ans;
    }
};

本代码提交AC,用时9MS。

二刷。类似的代码,或许更好理解:

class Solution {
public:
	int maxProfit(vector<int>& prices) {
		int n = prices.size();
		if (n == 0 || n == 1)return 0;
		vector<int> cur_min(n, INT_MAX), cur_max(n, INT_MIN);
		cur_min[0] = prices[0];
		cur_max[n - 1] = prices[n - 1];
		for (int i = 1; i < n; ++i) {
			cur_min[i] = min(cur_min[i - 1], prices[i]);
			cur_max[n - i - 1] = max(cur_max[n - i], prices[n - i - 1]);
		}
		vector<int> dp_sell(n, 0), dp_buy(n, 0);
		for (int i = 1; i < n; ++i) {
			dp_sell[i] = max(dp_sell[i - 1], prices[i] - cur_min[i]);
			dp_buy[n - i - 1] = max(dp_buy[n - i], cur_max[n - i - 1] - prices[n - i - 1]);
		}
		int ans = 0;
		for (int i = 0; i < n; ++i) {
			ans = max(ans, dp_sell[i] + dp_buy[i]);
		}
		return ans;
	}
};

本代码提交AC,用时16MS。

LeetCode Best Time to Buy and Sell Stock

121. Best Time to Buy and Sell Stock

Say you have an array for which the ith element is the price of a given stock on day i.

If you were only permitted to complete at most one transaction (i.e., buy one and sell one share of the stock), design an algorithm to find the maximum profit.

Note that you cannot sell a stock before you buy one.

Example 1:

Input: [7,1,5,3,6,4]
Output: 5
Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.
             Not 7-1 = 6, as selling price needs to be larger than buying price.

Example 2:

Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.

股票买卖问题。给定股票每天的价格,问哪天买入和哪天卖出能获得最大收益。动态规划问题,本科就遇到过了。之前一贯的做法是这样的:要使prices[j]-prices[i]的差值最大,因为prices[j]-prices[i]=(prices[j]-prices[j-1])+(prices[j-1]-prices[j-2])+…+(prices[i+1]-prices[i]),等式右边的每一项就是相邻两天的股价差。所以我们可以对每相邻两天的股价做差,得到一个新的n-1长的数组diff,然后对diff求最大连续子串和。最大连续子串和又可以用DP解决,令dp[i]为包含diff[i]的最大连续子串和,如果dp[i-1]>0,则dp[i]=dp[i-1]+diff[i];否则dp[i]=diff[i]。最后dp数组的最大值即为最大收益。
这种思路的代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices)
    {
        if (prices.size() < 2)
            return 0;
        vector<int> diff(prices.size() – 1);
        for (int i = 1; i < prices.size(); ++i) {
            diff[i – 1] = prices[i] – prices[i – 1];
        }
        vector<int> dp(diff.size());
        int ans = 0;
        for (int i = 0; i < diff.size(); ++i) {
            if (i == 0 || dp[i – 1] < 0)
                dp[i] = diff[i];
            else
                dp[i] = dp[i – 1] + diff[i];
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};

本代码提交AC,用时9MS。
还可以对上述思路简化,我们记录当前遇到的最小值min_so_far,那么如果第i天卖出的话,最大的收益就是prices[i]-min_so_far了,所以我们用dp[i]记录第i天卖出的最大收益。最后对dp数组求最大值,即为最大收益。
这种思路的代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices)
    {
        if (prices.size() < 2)
            return 0;
        vector<int> dp(prices.size());
        int min_so_far = prices[0], ans = 0;
        for (int i = 1; i < prices.size(); ++i) {
            dp[i] = prices[i] – min_so_far;
            min_so_far = min(min_so_far, prices[i]);
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};

本代码提交AC,用时6MS。
还有一种解法,用minprice和maxprice分别记录从左往右当前的最小值和从右往左当前的最大值,最大收益就是用maxprice-minprice的最大值。完整代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices)
    {
        int n = prices.size();
        if (n < 2)
            return 0;
        vector<int> minprice(n), maxprice(n);
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            minprice[i] = (i == 0 ? prices[0] : min(minprice[i – 1], prices[i]));
            maxprice[n – i – 1] = (i == 0 ? prices[n – 1] : max(maxprice[n – i], prices[n – i – 1]));
        }
        for (int i = 0; i < n; ++i) {
            ans = max(ans, maxprice[i] – minprice[i]);
        }
        return ans;
    }
};

本代码提交AC,用时9MS。

LeetCode Arithmetic Slices

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

本题要求一个数组中等差数列的个数,并不要求是最长的等差数列。比如给的例子中数组A=[1,2,3,4],可以抽出3个等差数列,分别是[1,2,3]、[2,3,4]、[1,2,3,4]。 首先还是找特征,我们可以在数组中先找出最长的等差数列,然后在等差数列的基础上抽取(Slice)出比较小的等差数列,所以先研究一下长度为n的等差数列可以抽出多少个小的等差数列。
  • n=3,抽出1个
  • n=4,抽出3=1+2个,即1个长度为4的和2个长度为3的
  • n=5,抽出6=1+2+3个,即1个长度为5的、2个长度为4的和3个长度为3的
  • n=6,抽出10=1+2+3+4个,即…
由此可以得出规律,长度为n的等差数列,可以抽出1+2+…+(n-2)=(n-1)(n-2)/2个小的等差数列。 于是问题就转换为找出数组中所有最长连续等差数列,然后代入上述公式计算。 完整代码如下: [cpp] class Solution { public: inline int slice(int cnt) { if (cnt < 3)return 0; if (cnt == 3)return 1; return (cnt – 1)*(cnt – 2) / 2; // 1+2+…+(cnt-2) } int numberOfArithmeticSlices(vector<int>& A) { if (A.size() < 3)return 0; int ans = 0, cnt = 2, diff = A[1] – A[0]; for (int i = 2; i < A.size(); ++i) { if (A[i] – A[i – 1] == diff) { ++cnt; } else { ans += slice(cnt); cnt = 2; diff = A[i] – A[i – 1]; } } return ans + slice(cnt); } }; [/cpp] 本代码提交AC,用时3MS。 网上还有一种DP解法,我们看看上面的规律,当n从4增加到5时,抽取出来的小等差数列增加了3个,是在n=4时,最后+2的基础上+1=3;当n从5增加到6时,抽取出来的小等差数列增加了4个,是在n=5时,最后+3的基础上+1=4。 所以我们设置一个dp数组,dp[i]表示:如果第i个元素和前面构成等差数列了,则能贡献多少个小等差数列,根据前面的分析,dp[i]=dp[i-1]+1,这里的dp相当于比如n=6时的10=1+2+3+4(等号右边的数组);如果第i个元素不和前面构成等差数列,则dp[i]=0不贡献小等差数列。 这种解法的代码如下: [cpp] class Solution { public: int numberOfArithmeticSlices(vector<int>& A) { if (A.size() < 3)return 0; int ans = 0; vector<int> dp(A.size(), 0); for (int i = 2; i < A.size(); ++i) { if (A[i] – A[i – 1] == A[i – 1] – A[i – 2]) { dp[i] = dp[i – 1] + 1; } ans += dp[i]; } return ans; } }; [/cpp] 本代码提交AC,用时3MS。 DP一般都可以优化空间,上面的解法也可以不用一个dp数组,而只用一个变量。如下: [cpp] class Solution { public: int numberOfArithmeticSlices(vector<int>& A) { if (A.size() < 3)return 0; int ans = 0, cur = 0; for (int i = 2; i < A.size(); ++i) { if (A[i] – A[i – 1] == A[i – 1] – A[i – 2]) { ++cur; } else { cur = 0; } ans += cur; } return ans; } }; [/cpp] 本代码提交AC,用时也是3MS。]]>

LeetCode House Robber

198. House Robber

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

Example 1:

Input: [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
             Total amount you can rob = 1 + 3 = 4.

Example 2:

Input: [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).
             Total amount you can rob = 2 + 9 + 1 = 12.

很常见的动态规划问题。 一个小偷要偷街上的一系列屋子,但是不能偷连续的两个屋子,问最大能偷到多少钱。 设置一个二维数组dp[i][j],i表示第i个屋子,j有两种取值,dp[i][0]表示不偷第i个屋子,到目前最多能偷多少钱;dp[i][1]表示偷第i个屋子,到目前最多能偷多少钱。
显然dp[i][0]=max(dp[i-1][0],dp[i-1][1]),不偷第i个屋子,前一个屋子可偷或不偷;dp[i][1]=dp[i-1][0]+nums[i],偷第i个屋子,则前一个屋子不能偷。
实际实现的时候,dp长度比nums大1,因为dp[0]用来保留初始状态,所以for循环的时候,访问nums时i要减一。
完整代码如下:

class Solution {
public:
    int rob(vector<int>& nums)
    {
        int ans = 0;
        vector<vector<int> > dp(nums.size() + 1, vector<int>(2, 0));
        for (int i = 1; i <= nums.size(); i++) {
            dp[i][0] = max(dp[i – 1][0], dp[i – 1][1]);
            dp[i][1] = dp[i – 1][0] + nums[i – 1];
            ans = max(ans, max(dp[i][0], dp[i][1]));
        }
        return ans;
    }
};

本代码提交AC,用时0MS。

二刷。简化,只需要一维DP,代码如下:

class Solution {
public:
	int rob(vector<int>& nums) {
		int n = nums.size();
		if (n == 0)return 0;
		if (n == 1)return nums[0];
		vector<int> dp(n, 0);
		dp[0] = nums[0];
		dp[1] = max(nums[1], nums[0]);
		for (int i = 2; i < n; ++i) {
			dp[i] = max(dp[i], dp[i - 1]); // 不偷
			dp[i] = max(dp[i], dp[i - 2] + nums[i]); // 偷
		}
		return dp[n - 1];
	}
};

本代码提交AC,用时4MS。

LeetCode Edit Distance

72. Edit Distance

Given two words word1 and word2, find the minimum number of operations required to convert word1 to word2.

You have the following 3 operations permitted on a word:

  1. Insert a character
  2. Delete a character
  3. Replace a character

Example 1:

Input: word1 = "horse", word2 = "ros"
Output: 3
Explanation: 
horse -> rorse (replace 'h' with 'r')
rorse -> rose (remove 'r')
rose -> ros (remove 'e')

Example 2:

Input: word1 = "intention", word2 = "execution"
Output: 5
Explanation: 
intention -> inention (remove 't')
inention -> enention (replace 'i' with 'e')
enention -> exention (replace 'n' with 'x')
exention -> exection (replace 'n' with 'c')
exection -> execution (insert 'u')

本题要求两个字符串的编辑距离,很经典的一道动态规划题。 两个字符串的编辑距离是指通过插入、删除和替换这三种操作,将word1变换为word2(或者将word2变换为word1)。 编辑距离本科算法课就学过了,把word1和word2摆成一个表格的形式,然后从左往右,从上往下填表格,然后编辑距离就是表格右下角的值。 假设word1水平方向摆放,word2竖直方向摆放,令dp[row][col]表示把word2[:row]变换为word1[:col]需要的编辑距离,则这种变换可以通过三种方法获得:

  1. 删掉字符word2[row],然后把word2[:row-1]变换为word1[:col],删除代价为1,所以dp[row][col]=dp[row-1][col]+1
  2. 在word2[:row]变换为word1[:col-1]的基础上,在word2[:row]末尾插入字符word1[col],这样就把word2[:row]变为了word1[:col],插入代价为1,所以dp[row][col]=dp[row][col-1]+1
  3. 如果word1[col]==word2[row],则word2[:row]变为word1[:col]就相当于word2[:row-1]变为word1[:col-1],此时dp[row][col]=dp[row-1][col-1];如果word1[col]!=word2[row],则可以在word2[:row-1]变为word1[:col-1]的基础上,将word2[row]替换为word1[col],替换代价为1,所以dp[row][col]=dp[row-1][col-1]+1

综合以上三种情况(实际是四种情况),得到的递归公式为:
dp[row][col]=min(min(dp[row-1][col],dp[row][col-1])+1,dp[row-1][col-1]+(word1[col]==word2[row]?0:1))
完整的代码如下:

class Solution {
public:
    int minDistance(string word1, string word2)
    {
        if (word1.size() == 0 || word2.size() == 0)
            return max(word1.size(), word2.size());
        vector<vector<int> > dp(word2.size() + 1, vector<int>(word1.size() + 1, 0));
        for (int row = 1; row <= word2.size(); row++)
            dp[row][0] = row;
        for (int col = 1; col <= word1.size(); col++)
            dp[0][col] = col;
        for (int row = 1; row <= word2.size(); row++) {
            for (int col = 1; col <= word1.size(); col++) {
                dp[row][col] = min(dp[row – 1][col], dp[row][col – 1]) + 1;
                dp[row][col] = min(dp[row][col], dp[row – 1][col – 1] + (word1[col – 1] == word2[row – 1] ? 0 : 1));
            }
        }
        return dp[word2.size()][word1.size()];
    }
};

本代码提交AC,用时28MS。

DP题一般都要填表格,很多情况下,填第row行表格可能只需要利用第row-1行的信息,所以可以只保留两行的结果,用于缩减空间复杂度。下面这种实现只利用了额外的2*min(row,col)空间:

class Solution {
public:
    int minDistance(string word1, string word2)
    {
        if (word2.size() < word1.size()) {
            swap(word1, word2);
        }
        if (word1.size() == 0)
            return word2.size();
        vector<int> dp1(word1.size() + 1, 0), dp2(word1.size() + 1, 0);
        for (int col = 1; col <= word1.size(); col++)
            dp1[col] = col;
        for (int row = 1; row <= word2.size(); row++) {
            dp1[0] = row – 1; // caution
            dp2[0] = row; // caution
            for (int col = 1; col <= word1.size(); col++) {
                dp2[col] = min(dp1[col], dp2[col – 1]) + 1;
                dp2[col] = min(dp2[col], dp1[col – 1] + (word1[col – 1] == word2[row – 1] ? 0 : 1));
            }
            swap(dp1, dp2);
        }
        return dp1[word1.size()];
    }
};

本代码提交AC,用时19MS,确实要比之前的快一点。

LeetCode Triangle

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题,要求只用O(n)的空间,思路和LeetCode Pascal’s Triangle II有点类似,即从后往前算。 把题目中的三角形推到左边,成下面的样子:

[2],
[3,4],
[6,5,7],
[4,1,8,3]
可以看到除了首尾两个元素,其他元素的递推公式为:dp[col]=min(dp[col-1],dp[col])+triangle[row][col],首尾元素只能从其上面的一个元素下来,所以只有一条路。完整代码如下:

class Solution {
public:
    int minimumTotal(vector<vector<int> >& triangle)
    {
        vector<int> dp(triangle.size(), 0);
        dp[0] = triangle[0][0];
        for (int row = 1; row < triangle.size(); row++) {
            dp[row] = dp[row – 1] + triangle[row][row];
            for (int col = row – 1; col > 0; col–) {
                dp[col] = min(dp[col – 1], dp[col]) + triangle[row][col];
            }
            dp[0] = dp[0] + triangle[row][0];
        }
        return *min_element(dp.begin(), dp.end());
    }
};

本代码提交AC,用时6MS。

LeetCode Minimum Path Sum

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.

Example:

Input:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
Output: 7
Explanation: Because the path 1→3→1→1→1 minimizes the sum.

一个网格,每个格子中都有一个数字,问从左上角走到右下角,求走过的格子数字之和最小值是多少。 和LeetCode Unique Paths II是一样的,一个格子只可能从其左边或者上边的格子而来,所以求两者最小值即可。不过要注意边界问题,完整代码如下:

class Solution {
public:
    int minPathSum(vector<vector<int> >& grid)
    {
        vector<int> dp(grid[0].size(), 0);
        for (int i = 0; i < grid.size(); i++) {
            for (int j = 0; j < grid[i].size(); j++) {
                int top = i == 0 ? INT_MAX : dp[j];
                int left = j == 0 ? INT_MAX : dp[j – 1];
                if (i == 0 && j == 0)
                    dp[j] = grid[i][j];
                else
                    dp[j] = (top < left ? top : left) + grid[i][j];
            }
        }
        return dp[grid[0].size() – 1];
    }
};

本代码提交AC,用时16MS。

LeetCode Unique Paths II

63. Unique Paths II

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

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.

Note: m and n will be at most 100.

Example 1:

Input:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
Output: 2
Explanation:
There is one obstacle in the middle of the 3x3 grid above.
There are two ways to reach the bottom-right corner:
1. Right -> Right -> Down -> Down
2. Down -> Down -> Right -> Right

承接LeetCode Unique Paths,只是把网格中设置了障碍,第一思路是DFS,如果下一步能走,则递归深入搜索,否则返回。完整代码如下:

class Solution {
public:
    void dfs(vector<vector<int> >& obstacleGrid, int x, int y, int& numPath)
    {
        if (obstacleGrid[x][y] == 1)
            return;
        if (x == obstacleGrid.size() – 1 && y == obstacleGrid[0].size() – 1)
            numPath++;
        else {
            if (y + 1 < obstacleGrid[0].size())
                dfs(obstacleGrid, x, y + 1, numPath);
            if (x + 1 < obstacleGrid.size())
                dfs(obstacleGrid, x + 1, y, numPath);
        }
    }
    int uniquePathsWithObstacles(vector<vector<int> >& obstacleGrid)
    {
        int numPath = 0;
        dfs(obstacleGrid, 0, 0, numPath);
        return numPath;
    }
};

但是TLE失败:-(

查题解,恍然大悟,DP解决。设dp[j]为从左上角到obstacleGrid[i][j]共有多少种方案,如果obstacleGrid[i][j]==1,则不可能到达这一点,dp[j]=0;否则,可以从[i][j-1]和[i-1][j]两个点到达[i][j],即dp[j]=dp[j]+dp[j-1]。因为只用了一个数组保存中间结果,所以右边的dp[j]相当于dp[i-1][j],dp[j-1]相当于dp[i][j-1],左边的dp[j]才是dp[i][j]。
$$dp[i][j]=\begin{cases}0\quad\text{if obstacleGrid[i][j]==1}\\dp[i-1][j]+dp[i][j-1]\quad\text{otherwise}\end{cases}$$

完整代码如下:

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int> >& obstacleGrid)
    {
        vector<int> dp(obstacleGrid[0].size(), 0);
        if (obstacleGrid[0][0] == 0)
            dp[0] = 1; // caution!
        for (int i = 0; i < obstacleGrid.size(); i++) {
            for (int j = 0; j < obstacleGrid[i].size(); j++) {
                if (obstacleGrid[i][j] == 1)
                    dp[j] = 0;
                else
                    dp[j] += j == 0 ? 0 : dp[j – 1];
            }
        }
        return dp[obstacleGrid[0].size() - 1];
    }
};

本代码提交AC,用时6MS。