动态规划(leetcode)小结

  1. 适用问题
    常用于寻找符合某种条件的子序列的最优解。

  2. 思路
    枚举数组长度为1,2…时的解,找出递推关系式,注意找当前状态与之前状态的联系,即可求解。
    子序列最优解是一个值或者最优子序列(如最长回文子串),dp[i]表示输入数组长度为i时的最优解。

  3. 类型
    (1) 对子序列顺序做限制,比如连续子序列的最大和/最大乘积,子序列元素必须是间隔1的元素等。
    用dp[i]表示包含当前元素num[i]的局部最优解,最后从整个dp数组找全局最优。

 House Robber: 给定数组表示抢劫每个房子的收益,限制抢劫的房子不能相邻,要求抢劫的最大收益是多少。
Example:
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.
[2,1,1,2]  -->2, + 2=4
//对于限定子序列顺序的问题(如连续子序列,子序列元素不能相邻),直接用dp[i]表示表示待求解的状态是不可行的,
//因为当前的最优解之前的最优解并没有直接的联系,当前的最优解可能需要重新组合之前的序列。这是应该给dp加限定条件,
//用dp[i]表示包含当前元素nums[i]的最优解,也就是局部最优,最后全局最优从整个dp中找。
//类似题目:最大和子序列。
class Solution {
public:
	int rob(vector<int>& nums) {
		if (nums.empty()) return 0;
		vector<int> dp(nums.size(), 0);
		dp[0] = nums[0];
		for (int i = 1; i < nums.size(); i++)
		{
			if(i>=2) dp[i] = dp[i-2];
            if(i>=3) dp[i] = max(dp[i], dp[i-3]);
            dp[i] += nums[i];
		}
		int res = dp[0];
		for (int i = 1; i < dp.size(); i++) res = max(res, dp[i]);
		return res;
	}
};
5. Longest Palindromic Substring 最长回文字串:  
 Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

这次dp[i]不是包含以当前元素为结尾的子序列,而是包含以当前元素为中心的子序列。(循环次数相当于减半,如果使用一概元素为结尾
会time limited)

class Solution {
public:
	string longestPalindrome(string s) {
		if (s.empty()) return "";
		vector<string> dp(s.size(), "");
		for (int i = 0; i < s.size(); i++)
		{
			dp[i] = longest(s, i);
		}
		string res = "";
		for (int i = 0; i < dp.size(); i++) res = res.length() < dp[i].length() ? dp[i] : res;
		
		return res;
	}
	string longest(const string &s, int i)
	{
		//考虑到以该元素为中心的偶数长度子串和奇数长度子串。定义左右指针真相两边搜索
		int left = i, right = i + 1;
		string res = "";
		while (left >= 0 && right < s.length() && s[left] == s[right])
		{
			left--;
			right++;
		}
		//由于left--,right++,所以长度为r-l-1
		if (right - left - 1 > 0) res = s.substr(left + 1, right - left - 1);

		left = i; right = i;
		while (left >= 0 && right < s.length() && s[left] == s[right])
		{
			left--;
			right++;
		}
		//由于left--,right++,所以长度为r-l-1
		if (right - left - 1 > res.length()) res = s.substr(left + 1, right - left - 1);

		return res;
	}
};

同类型:
最大和子序列 https://leetcode.com/problems/maximum-subarray/

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

//dp[i]表示一当前元素结尾的最大和序列。地推:d[i]=max(d[i-1]+num[i], num[i])
 class Solution {
 public:
	 int maxSubArray(vector<int>& nums) {
		 int n = nums.size();
		 if (n == 0)return 0;
		 vector<int> dp(n, 0);
		 for (int i = 0; i < n; i++)
		 {
			 if (i >= 1) dp[i] = max(0, dp[i - 1]);
			 dp[i] += nums[i];
		 }
		 int res = dp[0];
		 for (int i = 0; i < n; i++) res = max(res, dp[i]);
		 return res;
	 }
 };

最大乘积子序列 https://leetcode.com/problems/maximum-product-subarray/

(2) 对数组每个元素都是选或不选,如零钱找零,求最少找零数。
需要设置n+1长度的dp数组。

322. Coin Change:
Example 1:

Input: coins = [1, 2, 5], amount = 11
Output: 3 
Explanation: 11 = 5 + 5 + 1

class Solution {
	//容易找到递推关系式: dp[i] = min(dp[i], dp[i-j] + 1), j为零钱面值,i为总钱数。
	//将dp初始化为n+1,那么无法找零的dp[i]就是n+1;
public:
	int coinChange(vector<int>& coins, int amount) {
		if (coins.empty()) return -1;
		vector<int> dp(amount + 1, amount+1);
		dp[0] = 0;
		for (int i = 1; i <= amount; i++)
		{
			for (int j = 0; j < coins.size(); j++)
			{
				if (i >= coins[j]) dp[i] = min(dp[i], dp[i - coins[j]] + 1);
			}
		}
		return dp[amount] > amount?-1 : dp[amount];
	}
};

(3)输入是 二维数组,dp数组也要设置成二维

64. Minimum Path Sum
Input:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
Output: 7
Explanation: Because the path 13111 minimizes the sum.
class Solution {
	//典型的二维dp问题,递推关系式:d(i,j) = num(i,j) + min{d(i-1,j), d(i,j-1)}
public:
	int minPathSum(vector<vector<int>>& grid) {
		if (grid.empty() || grid[0].empty()) return 0;
		int m = grid.size(), n = grid[0].size();
		vector<vector<int>> dp(m, vector<int>(n, INT_MAX));
		for (int i = 0; i < m; i++)
		{
			for (int j = 0; j < n; j++)
			{
				if (i == 0 && j == 0)
				{
					dp[i][j] = grid[i][j];//因为前面的INT_MAX这里单独处理一下dp[0]0],不大美观。。。
					continue;
				}
				if (i > 0) dp[i][j] = min(dp[i][j], dp[i - 1][j]);
				if (j > 0) dp[i][j] = min(dp[i][j], dp[i][j - 1]);
				dp[i][j] += grid[i][j];
			}
		}
		return dp.back().back();
	}
};

(4)最长非降子序列(不需要连续)

找出最长非降子序列的长度:
[534867] -->
output: 4 ([3,4,6,7])
 //dp[i] = max(1, dp[j]+1) if j<=i-1 && num[i]>=num[j]
 class Solution {
 public:
	 int core(vector<int> nums)
	 {
		 if (nums.empty()) return 0;
		 int n = nums.size();
		 vector<int> dp(n, 0);
		 for (int i = 0; i < n; i++)
		 {
			 dp[i] = 1;
			 if (i >= 1)
			 {
				 for (int j = 1; j <= i; j++)
				 {
					 if (nums[i] >= nums[i - j]) dp[i] = max(dp[i], dp[i - j] + 1);
				 }
			 }
		 }
		 return dp.back();
	 }
 };
  1. Word Break

把字符串切分后能否使是所有字串都能在字典中找到。这个题表面看起来不是求最优子序列的极值,但是在每个位置都有切分不切分两种可能,有点类似于零钱找零,所以我们建立dp数组,表示字符串长度为i时是否可行。

Example 3:

Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: false
//先零字符串长度为1,2,3.。找出递归关系,dp[i]表示当前为之前的字符串额能否符合条件,那么递推关系
	//为 d[i] = has(str[0:i+1]) || has(str[j+1:i+1]), d[j]>0 && 0<= j 
 class Solution {
 public:
	 bool wordBreak(string s, vector<string>& wordDict) {
		 if (s.empty()) return true;
		 if (wordDict.empty()) return false;
		 unordered_set<string> words(wordDict.begin(), wordDict.end());

		 int n = s.length();
		 vector<bool> dp(n, false);
		 for (int i = 0; i < n; i++)
		 {
			 if (words.find(s.substr(0, i + 1)) != words.end())
			 {
				 dp[i] = true;
				 continue;
			 }
			 for (int j = i-1; j>=0; j--)
			 {
				 if (dp[j] && words.find(s.substr(j + 1, i - j)) != words.end())
				 {
					 dp[i] = true;
					 break;
				 }
			 }
		 }
		 return dp.back();
	 }
};
  1. Decode Ways
    编码关系:
'A' -> 1
'B' -> 2
...
'Z' -> 26

问给定数字字符串有多少种解码结果:

Input: "226"
Output: 3
Explanation: It could be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 6).

思路:每一位置上元素,如果单独解码,当前元素和上一元素的解码组合形成一种解码方式,所以解码数d[i]等于d[i-1];如果和上一位形成两位数解码,那么元素和上二元素的解码组合形成一种解码方式,所以
d[i]+=d[i-2]。当然单独解码时当前位不能是’0‘,两位数解码不能大于’26‘。

class Solution {
public:
	int numDecodings(string s) {
		int n2 = 0, n1 = 1, n = 0;
		for (int i = 0; i < s.length(); i++)
		{
			if (s[i] != '0') n += n1;
			if (i > 0 && (s[i - 1] == '1' || (s[i - 1] == '2'&&s[i] <= '6'))) n += n2;
			n2 = n1;
			n1 = n;
			if (i < s.length() - 1) n = 0;
		}
		return n;
	}
};

你可能感兴趣的:(leetcode)