Tag Archives: DP

LeetCode Longest Palindromic Subsequence

LeetCode 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求解,假设dp[i][j]表示原字符串范围[i,j]中的最长回文子序列的长度。则如果s[i]==s[j],则dp[i][j]=dp[i+1][j-1]+2,即是临近内层的最长回文子序列长度加2。如果s[i]!=s[j],则dp[i][j]=max(dp[i][j-1],dp[i+1][j]),即是左端或者右端最长回文子序列长度的最大值。 初始时dp[i][i]=1,即单个字符自成回文,长度为1。 代码如下: [cpp] class Solution2 { public: int longestPalindromeSubseq(string s) { int n = s.size(); vector<vector<int>> dp(n, vector<int>(n, 0)); for (int i = n – 1; i >= 0; –i) { dp[i][i] = 1; for (int j = i + 1; j < n; ++j) { if (s[i] == s[j])dp[i][j] = dp[i + 1][j – 1] + 2; else dp[i][j] = max(dp[i][j – 1], dp[i + 1][j]); } } return dp[0][n – 1]; } }; [/cpp] 本代码提交AC,用时68MS。 上述计算有些区间会重复计算dp[i][j],可以用递归方法求解,并且把计算过的值记录下来,下次遇到查询时直接返回。代码如下: [cpp] class Solution { private: int helper(int l, int r, const string &s, vector<vector<int>> &dp) { if (l > r)return 0; if (l == r)return 1; if (dp[l][r] != 0)return dp[l][r]; if (s[l] == s[r])dp[l][r] = helper(l + 1, r – 1, s, dp) + 2; else dp[l][r] = max(helper(l, r – 1, s, dp), helper(l + 1, r, s, dp)); return dp[l][r]; } public: int longestPalindromeSubseq(string s) { int n = s.size(); vector<vector<int>> dp(n, vector<int>(n, 0)); return helper(0, n – 1, s, dp); } }; [/cpp] 本代码提交AC,用时49MS。 参考:https://discuss.leetcode.com/topic/78603/straight-forward-java-dp-solution/2]]>

HDOJ 1159-Common Subsequence

HDOJ 1159-Common Subsequence

Common Subsequence

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 59402    Accepted Submission(s): 27503

Problem DescriptionA subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = <x1, x2, …, xm> another sequence Z = <z1, z2, …, zk> is a subsequence of X if there exists a strictly increasing sequence <i1, i2, …, ik> of indices of X such that for all j = 1,2,…,k, xij = zj. For example, Z = <a, b, f, c> is a subsequence of X = <a, b, c, f, b, c> with index sequence <1, 2, 4, 6>. Given two sequences X and Y the problem is to find the length of the maximum-length common subsequence of X and Y.
The program input is from a text file. Each data set in the file contains two strings representing the given sequences. The sequences are separated by any number of white spaces. The input data are correct. For each set of data the program prints on the standard output the length of the maximum-length common subsequence from the beginning of a separate line.

Sample Input

abcfbc abfcab
programming contest 
abcd mnp

Sample Output

4
2
0

SourceSoutheastern Europe 2003
RecommendIgnatius   |   We have carefully selected several similar problems for you:  10871176100310581069


求最长公共子序列。DP模板题,使用一个dp数组即可,用pre记录原来左上角的值。 代码如下,如果要求出一个lcs,参考《算法导论》P225,使用一个二维数组direction记录每个格子最大值来自哪个方向,重构lcs时从右下角不停的往前找。

#include <string>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int lcs(const string& a, const string& b)
{
    int n = a.size(), m = b.size();
    if (n == 0 || m == 0)
        return 0;
    vector<int> dp(m + 1, 0);
    for (int i = 1; i <= n; ++i) {
        int pre = 0;
        for (int j = 1; j <= m; ++j) {
            int cur = 0;
            if (a[i – 1] == b[j – 1])
                cur = pre + 1;
            else
                cur = max(dp[j – 1], dp[j]);
            pre = dp[j];
            dp[j] = cur;
        }
    }
    return dp[m]; // 最大值就是右下角的值
}
void construct_lcs(vector<vector<int> >& direction, const string& a, int i, int j, string& lcs)
{
    if (i == 0 || j == 0)
        return;
    if (direction[i][j] == 3) {
        construct_lcs(direction, a, i – 1, j – 1, lcs);
        lcs += string(1, a[i – 1]); // direction下标1对应a下标0,所以i-1
    }
    else if (direction[i][j] == 2) {
        construct_lcs(direction, a, i – 1, j, lcs);
    }
    else {
        construct_lcs(direction, a, i, j – 1, lcs);
    }
}
string lcs_string(const string& a, const string& b)
{
    int n = a.size(), m = b.size();
    if (n == 0 || m == 0)
        return 0;
    vector<int> dp(m + 1, 0);
    vector<vector<int> > direction(n + 1, vector<int>(m + 1, 0)); // 1:left,2:up,3:diagonal
    for (int i = 1; i <= n; ++i) {
        int pre = 0;
        for (int j = 1; j <= m; ++j) {
            int cur = 0;
            if (a[i – 1] == b[j – 1]) {
                cur = pre + 1;
                direction[i][j] = 3;
            }
            else {
                if (dp[j – 1] > dp[j]) {
                    cur = dp[j – 1];
                    direction[i][j] = 1;
                }
                else {
                    cur = dp[j];
                    direction[i][j] = 2;
                }
            }
            pre = dp[j];
            dp[j] = cur;
        }
    }
    string ans;
    construct_lcs(direction, a, n, m, ans); // 从右下角递归往前找,LCRS P225
    return ans;
}
int main()
{
    //freopen("input.txt", "r", stdin);
    string a, b;
    while (cin >> a >> b) {
        cout << lcs(a, b) << endl;
        //cout << lcs(a, b) << "\t" << lcs_string(a, b) << endl;;
    }
    return 0;
}

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

LeetCode House Robber II

213. House Robber II213. House Robber II

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, 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: [2,3,2]
Output: 3
Explanation: You cannot rob house 1 (money = 2) and then rob house 3 (money = 2),
             because they are adjacent houses.

Example 2:

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.213. House Robber II

本题是LeetCode House Robber的扩展版,当首尾相连时,怎样偷到最多的钱。 因为首尾相连,所以如果偷了第一家,就不能偷最后一家;或者如果偷了最后一家,就不能偷第一家。所以我们可以把数组分成两个部分,一部分是去掉最后一家,求一个最大值;另一部分是去掉第一家,求一个最大值。最优结果就是这两个最大值的最大值。 代码如下:

class Solution {
public:
    int rob(vector<int>& nums)
    {
        if (nums.size() == 0)
            return 0;
        if (nums.size() == 1)
            return nums[0];
        int ans1 = 0, ans2 = 0;
        vector<vector<int> > dp1(nums.size() + 1, vector<int>(2, 0)), dp2(nums.size() + 1, vector<int>(2, 0));
        for (int i = 1; i < nums.size(); ++i) { // 不偷最后一家
            dp1[i][0] = max(dp1[i – 1][0], dp1[i – 1][1]);
            dp1[i][1] = dp1[i – 1][0] + nums[i – 1];
            ans1 = max(ans1, max(dp1[i][0], dp1[i][1]));
        }
        for (int i = 2; i <= nums.size(); ++i) { // 不偷第一家
            dp2[i][0] = max(dp2[i – 1][0], dp2[i – 1][1]);
            dp2[i][1] = dp2[i – 1][0] + nums[i – 1];
            ans2 = max(ans2, max(dp2[i][0], dp2[i][1]));
        }
        return max(ans1, ans2);
    }
};

本代码提交AC,用时3MS。
更漂亮的代码是:

class Solution {
private:
    int subrob(const vector<int>& nums, int left, int right)
    {
        int robsum = 0, norobsum = 0;
        for (int i = left; i <= right; ++i) {
            int nrs = max(robsum, norobsum);
            int rs = norobsum + nums[i];
            norobsum = nrs;
            robsum = rs;
        }
        return max(robsum, norobsum);
    }
public:
    int rob(vector<int>& nums)
    {
        int n = nums.size();
        if (n == 0)
            return 0;
        if (n == 1)
            return nums[0];
        return max(subrob(nums, 0, n – 2), subrob(nums, 1, n – 1));
    }
};

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

二刷。常规解法:

class Solution {
private:
	int rob_max(vector<int>& nums) {
		int n = nums.size();
		vector<int> dp(n, 0);
		dp[0] = nums[0];
		dp[1] = max(nums[0], nums[1]);
		int ans = max(dp[0], dp[1]);
		for (int i = 2; i < n; ++i) {
			dp[i] = max(nums[i] + dp[i - 2], dp[i - 1]);
			ans = max(ans, dp[i]);
		}
		return ans;
	}
public:
	int rob(vector<int>& nums) {
		int n = nums.size();
		if (n == 0)return 0;
		if (n == 1)return nums[0];
		if (n == 2)return max(nums[0], nums[1]);
		vector<int> money1(nums.begin(), nums.end() - 1), money2(nums.begin() + 1, nums.end());
		return max(rob_max(money1), rob_max(money2));
	}
};

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

LeetCode Delete Operation for Two Strings

LeetCode Delete Operation for Two Strings Given two words word1 and word2, find the minimum number of steps required to make word1 and word2 the same, where in each step you can delete one character in either string. Example 1:

Input: "sea", "eat"
Output: 2
Explanation: You need one step to make "sea" to "ea" and another step to make "eat" to "ea".
Note:
  1. The length of given words won’t exceed 500.
  2. Characters in given words can only be lower-case letters.

给定两个字符串,求最少的删除次数,使得两个字符串相等。 这其实就是求两个字符串的最长公共子序列,因为最少的删除次数之后,两个字符串都变成了lcs,则每个字符串删除的次数就是原始长度减去lcs的长度。 代码如下: [cpp] class Solution { private: int lcs(const string& s1, const string& s2) { int n1 = s1.size(), n2 = s2.size(); vector<int> dp1(n2 + 1, 0), dp2(n2 + 1, 0); int ans = 0; for (int i = 1; i <= n1; ++i) { for (int j = 1; j <= n2; ++j) { if (s1[i – 1] == s2[j – 1])dp2[j] = dp1[j – 1] + 1; else dp2[j] = max(dp1[j], dp2[j – 1]); ans = max(ans, dp2[j]); } dp1.swap(dp2); } return ans; } public: int minDistance(string word1, string word2) { int l = lcs(word1, word2); return word1.size() – l + word2.size() – l; } }; [/cpp] 本代码提交AC,用时52MS。]]>

LeetCode Predict the Winner

LeetCode Predict the Winner Given an array of scores that are non-negative integers. Player 1 picks one of the numbers from either end of the array followed by the player 2 and then player 1 and so on. Each time a player picks a number, that number will not be available for the next player. This continues until all the scores have been chosen. The player with the maximum score wins. Given an array of scores, predict whether player 1 is the winner. You can assume each player plays to maximize his score. Example 1:

Input: [1, 5, 2]
Output: False
Explanation: Initially, player 1 can choose between 1 and 2.
If he chooses 2 (or 1), then player 2 can choose from 1 (or 2) and 5. If player 2 chooses 5, then player 1 will be left with 1 (or 2).
So, final score of player 1 is 1 + 2 = 3, and player 2 is 5.
Hence, player 1 will never be the winner and you need to return False.
Example 2:
Input: [1, 5, 233, 7]
Output: True
Explanation: Player 1 first chooses 1. Then player 2 have to choose between 5 and 7. No matter which number player 2 choose, player 1 can choose 233.
Finally, player 1 has more score (234) than player 2 (12), so you need to return True representing player1 can win.
Note:
  1. 1 <= length of the array <= 20.
  2. Any scores in the given array are non-negative integers and will not exceed 10,000,000.
  3. If the scores of both players are equal, then player 1 is still the winner.

有关博弈论的题。有两个人A和B,每个人每次能从数组开始或结束位置拿掉一个数,A先拿,谁拿的数字之和越大获胜,问A是否可以获胜。 因为每个人都尽最大的努力想要获胜,所以如果A拿了num[0],那么B从剩余的nums[1~n-1]拿数字的过程相当于A从nums[0~n-1]拿数字的过程,所以最优化拿数字的过程是一样的。 假设子问题为A从区间nums[s,…,e]拿数字尽量使自己赢,那么如果s==e,只有一个元素,则A拿到的元素和就为这个元素nums[s]。否则A可以尝试拿首元素nums[s],此时B尽力从nums[s+1,…,e]中拿使B自己赢,所以A的收益就是nums[s]减去B从nums[s+1,…,e]拿到的收益;A也可以拿尾元素nums[e],此时B尽力从nums[s,…,e-1]中拿使B自己赢,所以A的收益就是nums[e]减去B从nums[s,…,e-1]拿到的收益。A的最大收益就是拿首元素或尾元素获得的收益的较大值,如果大于等于0,说明A可以获胜。 递归代码如下: [cpp] class Solution { private: int helper(vector<int>& nums, int s, int e) { if (s == e)return nums[s]; else return max(nums[s] – helper(nums, s + 1, e), nums[e] – helper(nums, s, e – 1)); } public: bool PredictTheWinner(vector<int>& nums) { int n = nums.size(); return helper(nums, 0, n – 1) >= 0; } }; [/cpp] 本代码提交AC,用时96MS。 还有一种更清晰的DP方法。假设dp[s][e]为A从区间nums[s,…,e]拿获得的最大收益,如果A拿首元素nums[s],则获得的收益为nums[s]-dp[s+1][e],因为B要从剩余的nums[s+1,..,e]拿尽量使自己赢,就相当于A从nums[s+1,…,e]要使自己赢,所以过程都是一样的,dp[s+1][e]就是B的收益。类似的,如果A拿尾元素,则获得的收益为nums[e]-dp[s][e-1]。所以有: $$!dp[s][e]=max(nums[s]-dp[s+1][e],nums[e]-dp[s][e-1])$$ 因为大区间的dp[s][e]会用到较小区间的dp[s+1][e]和dp[s][e-1],所以可以区间长度进行DP,先求出小区间的值,求大区间时直接用小区间的值就好了。 代码如下: [cpp] class Solution { public: bool PredictTheWinner(vector<int>& nums) { int n = nums.size(); vector<vector<int>> dp(n, vector<int>(n, 0)); for (int i = 0; i < n; ++i)dp[i][i] = nums[i]; // 长度为0 for (int len = 1; len < n; ++len) { for (int s = 0; s + len < n; ++s) { dp[s][s + len] = max(nums[s] – dp[s + 1][s + len], nums[s + len] – dp[s][s + len – 1]); } } return dp[0][n – 1] >= 0; } }; [/cpp] 本代码提交AC,用时3MS。比递归版本快了很多。]]>

LeetCode Student Attendance Record II

LeetCode Student Attendance Record II Given a positive integer n, return the number of all possible attendance records with length n, which will be regarded as rewardable. The answer may be very large, return it after mod 109 + 7. A student attendance record is a string that only contains the following three characters:

  1. ‘A’ : Absent.
  2. ‘L’ : Late.
  3. ‘P’ : Present.
A record is regarded as rewardable if it doesn’t contain more than one ‘A’ (absent) or more than two continuous ‘L’ (late). Example 1:
Input: n = 2
Output: 8
Explanation:
There are 8 records with length 2 will be regarded as rewardable:
"PP" , "AP", "PA", "LP", "PL", "AL", "LA", "LL"
Only "AA" won't be regarded as rewardable owing to more than one absent times.
Note: The value of n won’t exceed 100,000.
上一题的基础上增大了难度,这一题给定数组n,问长度为n的学生出席字符串,有多少种可能让该学生获奖。 显然是DP问题,不过是三维DP。令dp[n][i][j]表示长度为n的字符串,包含i个’A’并且以j个’L’结尾的字符串个数。那么有如下的递推公式: dp[n][0][0]=sum(dp[n-1][0]):要求长度为n、包含0个’A’,且以0个’L’结尾的字符串个数;所以前n-1肯定也只包含0个’A’,但是前n-1可以以0、1或2个’L’结尾,因为我第n个字符不放’L’就能保证前n以0个’L’结尾。所以dp[n][0][0]=sum(dp[n-1][0])。其他的类似分析有:
  • dp[n][0][1]=dp[n-1][0][0]
  • dp[n][0][2]=dp[n-1][0][1]
  • dp[n][1][0]=sum(dp[n-1][0])+sum(dp[n-1][1])
  • dp[n][1][1]=dp[n-1][1][0]
  • dp[n][1][2]=dp[n-1][1][1]
代码如下: [cpp] const int kMOD = 1000000007; class Solution { private: long long getSum(vector<int>& v) { long long ans = 0; for(const int &i : v) ans += i; return ans % kMOD; } public: int checkRecord(int n) { vector<vector<int>> dp = {{1, 1, 0}, {1, 0, 0}}; for(int i = 2; i <= n; ++i){ vector<vector<int>> ndp = {{0, 0, 0}, {0, 0, 0}}; ndp[0][0] = getSum(dp[0]); ndp[0][1] = dp[0][0]; ndp[0][2] = dp[0][1]; ndp[1][0] = (getSum(dp[1]) + getSum(dp[0])) % kMOD; ndp[1][1] = dp[1][0]; ndp[1][2] = dp[1][1]; dp = ndp; } return (getSum(dp[0]) + getSum(dp[1])) % kMOD; } }; [/cpp] 本代码提交AC,用时612MS。]]>

hihoCoder 1506-投掷硬币

hihoCoder 1506-投掷硬币

#1506 : 投掷硬币

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

小Hi有一枚神奇的硬币。已知第i次投掷这枚硬币时,正面向上的概率是Pi。 现在小Hi想知道如果总共投掷N次,其中恰好M次正面向上的概率是多少。

输入

第一行包含两个整数N和M。 第二行包含N个实数P1, P2, … PN。 对于30%的数据,1 <= N <= 20 对于100%的数据,1 <= N <= 1000, 0 <= M <= N, 0 <= Pi <= 1

输出

输出一行一个实数表示恰好M次正面向上的概率。注意行末需要包含一个换行符’\n’。 输出与标准答案误差在0.001以内都被视为正确。
样例输入
2 1
0.5 0.5
样例输出
0.500000

某枚硬币在第i次投掷时,正面向上的概率为Pi,现在投掷N次,问恰好有M次正面向上的概率是多少。 这一题可以看成有N枚硬币,编号1~N,第i枚硬币正面向上的概率为Pi,把所有硬币一起抛向空中,问落地之后出现M枚正面向上的概率。 我一开始是这么做的,每一枚硬币都有可能正面向上或者反面向上,所以枚举所有2^N种情况,累加所有出现M次正面向上的概率。其实就是DFS,代码如下: [cpp] #include<iostream> #include<cstdlib> #include<algorithm> #include<vector> #include<string> #include<unordered_map> using namespace std; double ans = 0; int n, m; void dfs(const vector<double>& probs, double curprob, int step, int up) { if (up == m) { double oneans = curprob; for (int i = step; i < n; ++i)oneans *= (1 – probs[i]); ans += oneans; return; } if (step < n) { dfs(probs, curprob*probs[step], step + 1, up + 1); dfs(probs, curprob*(1 – probs[step]), step + 1, up); } } int main() { //freopen("input.txt", "r", stdin); scanf("%d %d", &n, &m); vector<double> probs(n, 0); for (int i = 0; i < n; ++i) scanf("%lf", &probs[i]); dfs(probs, 1, 0, 0); printf("%lf\n", ans); return 0; } [/cpp] 不出所料,本代码提交TLE,只能过30%的数据。 其实每一枚硬币都有正面向上或者反面向上的选择,马上想到背包问题,对于每个商品有选或者不选这两种情况,所以本题实际上也是换了个马甲的背包问题。 令dp[i][j]表示前i枚硬币出现j枚正面向上的概率。那么对于第i枚硬币,有两种选择,如果第i枚硬币正面向上,则前i-1枚硬币只能有j-1枚正面向上,即dp[i][j]+=dp[i-1][j-1]*probs[i]。如果第i枚硬币反面向上,则前i-1枚硬币需要有j枚正面向上,所以dp[i][j-1]+=dp[i-1][j-1]*(1-probs[i])。最终返回dp[n][m]。 代码如下: [cpp] #include<iostream> #include<cstdlib> #include<algorithm> #include<vector> #include<string> #include<unordered_map> using namespace std; int main() { //freopen("input.txt", "r", stdin); int n, m; scanf("%d %d", &n, &m); vector<double> probs(n + 1, 0); for (int i = 1; i <= n; ++i)scanf("%lf", &probs[i]); vector<vector<double>> dp(n + 1, vector<double>(n + 1, 0)); dp[0][0] = 1.0; for (int i = 1; i <= n; ++i) { for (int j = 1; j <= i; ++j) { //现在有i枚硬币,所以正面向上的个数j不超过i dp[i][j] += dp[i – 1][j – 1] * probs[i]; // head dp[i][j – 1] += dp[i – 1][j – 1] * (1 – probs[i]); // tail } } printf("%lf\n", dp[n][m]); return 0; } [/cpp] 本代码提交AC,用时48MS。]]>

hihoCoder 1502-最大子矩阵

hihoCoder 1502-最大子矩阵

#1502 : 最大子矩阵

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

给定一个NxM的矩阵A和一个整数K,小Hi希望你能求出其中最大(元素数目最多)的子矩阵,并且该子矩阵中所有元素的和不超过K。

输入

第一行包含三个整数N、M和K。 以下N行每行包含M个整数,表示A。 对于40%的数据,1 <= N, M <= 10 对于100%的数据,1 <= N, M <= 250 1 <= K <= 2147483647 1 <= Aij <= 10000

输出

满足条件最大的子矩阵所包含的元素数目。如果没有子矩阵满足条件,输出-1。
样例输入
3 3 9
1 2 3
2 3 4
3 4 5
样例输出
4

给定一个N*M的矩阵,要从中找出一个元素最多的子矩阵,使得该子矩阵的和不超过K。 如果确定了子矩阵的左上角点和右下角点,则唯一确定了该子矩阵。所以我们可以用一种完全暴力的方法来试一下这个题。两层循环确定左上角点(i,j),两层循环确定右下角点(u,v),再两层循环累加从(i,j)到(u,v)的元素和,如果和不超过K,则更新最大元素个数,否则继续循环。复杂度为$$O(n^6)$$,TLE,但是能过40%的数据。 类似这样求子矩阵的和,子数组的和等问题,一般都需要求从0到i的累加和。网上有一题也是求最大子矩阵的问题,但是那道题定义子矩阵的大小是用子矩阵的和来定义的,而本题是用子矩阵中元素个数来定义的。但是原理差不多,可以参考。 定义一个二维数组sum[i][j]表示固定子矩阵的左上角坐标为(1,1)(假设矩阵的下标从1开始),当右下角坐标为(i,j)时,这个子矩阵的和是多少。则有: sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+matrix[i][j] 这个很好理解,相当于求面积,sum[i][j-1]+sum[i-1][j]加了两遍sum[i-1][j-1],所以要减掉一个,最后再加上sum[i][j]独有的matrix[i][j]。 有了sum[i][j]之后,怎样求任意一个子矩阵的和呢?假设我们要求第i,j两行、u,v两列交成的子矩阵的和,应该怎样计算呢。这个也很好算,也是面积的加加减减,如下图所示,把(j,v)的面积减去(j,u)左边以及(i,v)上边的面积,但是(i,u)左上的面积减了两次,所以还要加上。总结成公式就是: cursum=sum[j][v]-sum[j][u-1]-sum[i-1][v]+sum[i-1][u-1]
      |               |
-----(i,u)----------(i,v)----
      |               |
      |               |
-----(j,u)----------(j,v)----
      |               |
通过cursum公式,我们很容易就能求出任意一个子矩阵的和了。但是要求和不超过K,且元素个数最多的子矩阵,还是免不了枚举起点和终点。所以最后求解的时候,我还是枚举了起点(i,j)和终点(u,v),然后通过上述公式求到子矩阵的和cursum,如果cursum不超过K,则更新最大子矩阵元素个数。 但是,这种方法还是TLE了!在我几近绝望的时候,我试着把v的遍历顺序由u→m改成了由m→u。什么意思呢,因为要求元素个数最多,如果从u列到最大的m列的子矩阵和都不超过K,则此时的元素个数肯定是在这个循环里最大的,所以直接跳过了v从u→m-1的那些列。但是如果v的遍历顺序是u→m,则开始的好多v的子矩阵和都不超过K,这样的话,v就需要遍历很久才能找到一个超过K的子矩阵和,最坏的情况是,遍历到最大值m时的子矩阵和也不超过K,所以还不如从最大值往小的遍历。这么简单的一改,居然AC了!不知道会不会是数据太弱了,毕竟复杂度都是一样的$$O(n^4)$$。 代码如下: [cpp] #include<iostream> #include<cstdio> #include<vector> #include<algorithm> #include<unordered_map> #include<string> #include<climits> using namespace std; int main() { //freopen("input.txt", "r", stdin); int n, m, k; scanf("%d %d %d", &n, &m, &k); vector<vector<int>> matrix(n + 1, vector<int>(m + 1, 0)); int minvalue = INT_MAX; for (int i = 1; i <= n; ++i) { for (int j = 1; j <= n; ++j) { scanf("%d", &matrix[i][j]); minvalue = min(minvalue, matrix[i][j]); } } if (minvalue > k)printf("-1\n"); else if (minvalue == k)printf("1\n"); else { int ans = INT_MIN; vector<vector<int>> sum(n + 1, vector<int>(m + 1, 0)); for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { sum[i][j] = sum[i – 1][j] + sum[i][j – 1] – sum[i – 1][j – 1] + matrix[i][j]; } } for (int i = 1; i <= n; ++i) { for (int j = i; j <= n; ++j) { //for (int u = 1; u <= m; ++u) { // for (int v = u; v <= m; ++v) { // TLE // int cursum = sum[j][v] – sum[j][u – 1] – sum[i – 1][v] + sum[i – 1][u – 1]; // if (cursum <= k)ans = max(ans, (j – i + 1)*(v – u + 1)); // else break; // } //} for (int u = 1; u <= m; ++u) { for (int v = m; v >= u; –v) { int cursum = sum[j][v] – sum[j][u – 1] – sum[i – 1][v] + sum[i – 1][u – 1]; if (cursum <= k) { ans = max(ans, (j – i + 1)*(v – u + 1)); break; } } } } } printf("%d\n", ans); } return 0; } [/cpp] 本代码提交AC,用时169MS。]]>

LeetCode Wiggle Subsequence

LeetCode 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?
一个抖动序列是指第一个数比第二个数小,第二个数比第三个数大,第三个数比第四个数小…或者第一个数比第二个数大,后续类似的交替的大小关系。 现给定一个数组,要求数组中最长的抖动序列的长度。 首先可以用贪心的方法,每次我们选择数据的拐点(局部最大或最小值点)。比如下图中,第一选了A,后续一直是上升的趋势,则选择最大的D能获得更长的抖动序列长度。如果此时选较小的C的话,则下一个只能选H了,这样就漏了E和G。 贪心的代码如下: [cpp] class Solution { public: int wiggleMaxLength(vector<int>& nums) { int n = nums.size(); if (n < 2)return n; int ans = 1, i = 0, j = 1; bool up = false; while (j < n && nums[j] == nums[i])++j; if (nums[j] > nums[i])up = true; else up = false; while (j < n) { if (nums[j] > nums[i] && up) { ++ans; up = false; while (j + 1 < n&&nums[j + 1] >= nums[j])++j; } else if (nums[j] < nums[i] && !up) { ++ans; up = true; while (j + 1 < n&&nums[j + 1] <= nums[j])++j; } i = j++; } return ans; } }; [/cpp] 本代码提交AC,用时3MS。 还可以用动态规划的方法,假设up[i]表示第i个元素处于抖动序列的最后一个位置,且是上升的趋势时,前[0,i]的抖动子序列的最大长度。类似的,down[i]表示第i个元素处于抖动序列的最后一个位置,且是下降趋势时,前[0,i]的抖动子序列的最大长度。 则对于第i个元素,可以枚举所有i前面的元素j跳到i,如果nums[i]>nums[j],则i处于抖动序列的上升末尾,则从j到i构成的抖动序列中,j是处于下降趋势的,所以有up[i]=max(up[i],down[j]+1)。类似的,如果nums[i]<nums[j],则有down[i]=max(down[i],up[j]+1)。最后返回1+max(up[n-1],down[n-1])。 代码如下: [cpp] class Solution { public: int wiggleMaxLength(vector<int>& nums) { int n = nums.size(); if (n < 2)return n; vector<int> up(n, 0), down(n, 0); for (int i = 1; i < n; ++i) { for (int j = 0; j < i; ++j) { if (nums[i] > nums[j])up[i] = max(up[i], down[j] + 1); else if (nums[i] < nums[j])down[i] = max(down[i], up[j] + 1); } } return 1 + max(up[n – 1], down[n – 1]); } }; [/cpp] 本代码提交AC,用时6MS。 可以对上述代码优化。j无需枚举所有i前面的元素,而是i直接根据和前一个元素i-1的大小关系进行更新。如果nums[i]>nums[i-1],则i处于上升末端,所以i-1必须处于下降末端,有up[i]=down[i-1]+1,down[i]不变,即down[i]=down[i-1]。如果nums[i]<nums[i-1],则i处于下降末端,所以i-1必须处于上升末端,有down[i]=up[i-1]+1,up[i]不变,即up[i]=up[i-1]。如果nums[i]==nums[i-1],则up[i]和down[i]都不变,分别等于up[i-1]和down[i-1]。 为什么这里可以不用枚举所有i前面的元素了呢,因为上述三种情况中,只更新了该更新的,不能更新的情况都等于前一个元素的结果了,比如第一种情况更新了up[i],则down[i]保持了down[i-1]的值,所以后续如果要用到down[i-1]的值,也可以直接用down[i]的值了。 代码如下: [cpp] class Solution { public: int wiggleMaxLength(vector<int>& nums) { int n = nums.size(); if (n < 2)return n; vector<int> up(n, 0), down(n, 0); up[0] = down[0] = 1; for (int i = 1; i < n; ++i) { if (nums[i] > nums[i – 1]) { up[i] = down[i – 1] + 1; down[i] = down[i – 1]; } else if (nums[i] < nums[i – 1]) { down[i] = up[i – 1] + 1; up[i] = up[i – 1]; } else { up[i] = up[i – 1]; down[i] = down[i – 1]; } } return max(up[n – 1], down[n – 1]); } }; [/cpp] 本代码提交AC,用时3MS。 还可以对上述代码进行空间的优化,因为up[i]和down[i]都只用到了前一个结果up[i-1]和down[i-1],所以只需保留前一个结果就行,不需要两个数组。代码如下: [cpp] class Solution { public: int wiggleMaxLength(vector<int>& nums) { int n = nums.size(); if (n < 2)return n; int up = 1, down = 1; for (int i = 1; i < n; ++i) { if (nums[i] > nums[i – 1]) { up = down + 1; } else if (nums[i] < nums[i – 1]) { down = up + 1; } } return max(up, down); } }; [/cpp] 本代码提交AC,用时0MS,是效率最好的一种解法了。 参考:https://leetcode.com/articles/wiggle-subsequence/,完整介绍了所有方法。]]>

LeetCode Target Sum

LeetCode 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:
  1. The length of the given array is positive and will not exceed 20.
  2. The sum of elements in the given array will not exceed 1000.
  3. Your output answer is guaranteed to be fitted in a 32-bit integer.

给定一个数组nums,和目标结果S,要求给数组中的每个数加上符号+或-,使得和为S。问共有多少种加符号的方案。 第一反应是DFS,遍历每个数,对其加上+或者-,更新S,然后递归。当遍历完所有数,且和等于0时,找到一种方案。 代码如下: [cpp] class Solution { private: void dfs(vector<int>& nums, int step, int S, int& ans) { if (step == nums.size() && S == 0) { ++ans; return; } if (step == nums.size())return; dfs(nums, step + 1, S – nums[step], ans); dfs(nums, step + 1, S + nums[step], ans); } public: int findTargetSumWays(vector<int>& nums, int S) { int ans = 0; dfs(nums, 0, S, ans); return ans; } }; [/cpp] 本代码提交AC,用时1072MS。居然需要这么长时间,果断优化。 思来想去,这本质是一个背包问题,常规0/1背包是对每个商品要还是不要,这题的背包是,对每个数字,是加正号还是负号。 假设dp[i][j]表示前i个数字之和为j的不同加符号方案数,则有如下递归公式 dp[i][j]=dp[i-1][j-nums[i]]+dp[i-1][j+nums[i]] 右边第一项是指对第i个数字,要赋加号,那么就要保证前i-1个数字之和是j-nums[i],这样(j-nums[i])+nums[i]才会等于j。类似的,如果要对第i个数字赋减号,就要保证前i-1个数字之和是j+nums[i]。所以总的就是这两种情况加和。 需要注意的是,因为可以赋负号,所以加和的结果可能是负数(范围是[-sum,sum]),为了dp[i][j]中的j能用下标访问,统一把所有和加上总和sum,做一个偏移(范围是[0,2*sum]),最终结果就是dp[n][S+sum]。 DP的代码如下: [cpp] class Solution { public: int findTargetSumWays(vector<int>& nums, int S) { int n = nums.size(), sum = 0; for (int i = 0; i < n; ++i)sum += nums[i]; if (S<-sum || S>sum)return 0; //if (S == -sum || S == sum)return 1; // nums={1,0},S=1; 1=1+0=1-0 vector<vector<int>> dp(n + 1, vector<int>(2 * sum + 1, 0)); dp[0][sum] = 1; for (int i = 1; i <= n; ++i) { for (int j = 0; j < 2 * sum + 1; ++j) { //考虑第i个数字是+还是- //i从1开始,所以第i个数字的下标是i-1 if (j >= nums[i – 1])dp[i][j] += dp[i – 1][j – nums[i – 1]]; if (j + nums[i – 1] < 2 * sum + 1)dp[i][j] += dp[i – 1][j + nums[i – 1]]; } } return dp[n][S + sum]; } }; [/cpp] 本代码提交AC,用时19MS,加速将近100倍! DP的问题一般都可以优化空间,比如上述代码可以用两个数组互相滚动赋值来代替n+1个数组。 上面的DP公式理解起来还是有点费劲,下面介绍另一种DP思路,非常简单。 假设我们已经求到前i-1个数字赋不同的符号可以得到的不同和的方案数了,也就是dp[pre]数组,现在要求对第i个数字赋不同符号可能得到的不同和的方案数。那么我们可以遍历dp[pre]数组,只要某个和j对应的方案数不为0(dp[pre][j]!=0),则说明前i-1个数能够组合出和j,且和为j的方案数正好是dp[pre][j],那么我们只需要在和j的基础上考虑加nums[i]或者减nums[i],如果对应的j+nums[i]或j-nums[i]没有超出范围,则这一轮和为j+nums[i]的方案数要加上上一轮和为j的方案数,这一轮和为j-nums[i]的方案数要加上上一轮和为j的方案数。 最后返回dp[cur][S+sum]。非常好理解的一个思路。用这种思路还可以求出从给定数组中取任意多个数相加,可以得到的不同和的方案数,其实和这个题类似,只不过把每个数字的状态改为了取和不取。 代码如下: [cpp] class Solution { public: int findTargetSumWays(vector<int>& nums, int S) { int n = nums.size(), sum = 0; for (int i = 0; i < n; ++i)sum += nums[i]; if (S<-sum || S>sum)return 0; vector<vector<int>> dp(2, vector<int>(2 * sum + 1, 0)); int i = 0, pre = (i & 1) ? 1 : 0, cur = (i & 1) ? 0 : 1; dp[pre][sum] = 1; // 取0个数字,加和为0的方案有1种。把和0偏移到sum位置 while (i < n) { pre = (i & 1) ? 1 : 0; cur = (i & 1) ? 0 : 1; for (int j = 0; j < 2 * sum + 1; ++j) { if (dp[pre][j] != 0) { if (j + nums[i] < 2 * sum + 1)dp[cur][j + nums[i]] += dp[pre][j]; // 在j的基础上加nums[i] if (j – nums[i] >= 0)dp[cur][j – nums[i]] += dp[pre][j]; // 在j的基础上减nums[i] } } fill(dp[pre].begin(), dp[pre].end(), 0); // 清空pre数组,因为下一轮pre要用作cur数组 ++i; } return dp[cur][S + sum]; } }; [/cpp] 本代码提交AC,用时13MS。用了两个滚动数组,空间效率高一点。 ]]>