[经典] 最X(长 | 大和 | 大积)Y(子序列 | 子字符串)

Note: 子序列,可以不连续;子字符串,必须连续。

以下题目按在我看看来的难度从易到难排列:

 

最大和子序列(Maximum sum subsequence)

这道题纯属娱乐...应该不会有人出这种题吧。方案是贪心法,遇到正数就放入序列

vector<int> msseq(vector<int> &num) {
    vector<int> result;
    for(int i : num) 
        if(i > 0)
            result.push_back(i);
    return result;
}
View Code

 

最大和子字符串(Maximum sum substring)

暴力方案TC为O(n^2)。更好的方案为,贪心法, 设i从0到n遍历,用gmax代表全局最大和,cursum代表当前子字符串的和。cursum为负,则放弃之前的子字符串;为正则继续向后加,每遇到一个正数,都更新一次gmax。TC = O(n),SC = O(1)。

double msstr(vector<double> &nums) {
    double gmax = 0, cur = 0;
    for(int i : nums) {
        if(cur > 0)
            cur += i;
        else
            cur = i;
        gmax = max(gmax, cur);
    }
    return gmax;
}
View Code

 

最长递增子字符串(Longest increasing substring)

暴力方案TC为O(n^2)。更好的方案为,与最大和子字符串一样,贪心法,设i从0到n遍历,用gmax代表全局最长长度,len代表当前递增substring的长度。遇到递增,则len++;遇到非递增,则更新gmax,并重置len为1。TC = O(n),SC = O(1)

int listr(vector<double> &nums) {
    if(nums.empty())
        return 0;
    int gmax = 1, cur = 1;
    for(int i = 1; i < nums.size(); i++) {
        if(nums[i] > nums[i - 1]) {
            cur++;
            gmax = max(gmax, cur);
        }
        else
            cur = 1;
    }
    return gmax;
}
View Code

 

最大乘积子字符串(Maximum product subsequence)

基本操作与最大乘积子序列差不多,不过是需要连续。用邻点动态规划

double mpstr(vector<double> &nums) {
    double gmax, cmax, cmin, pmax, pmin;
    gmax = cmax = cmin = pmax = pmin = 1;
    for(double i : nums) {
        cmax = max(i, max(pmax * i, pmin * i));
        cmin = min(i, min(pmax * i, pmin * i));
        pmax = cmax;
        pmin = cmin;
        gmax = max(gmax, cmax);
    }
    return gmax;
}
View Code

 

最大乘积子序列(Maximum product subsequence)

这题与最大和子序列差不多吧...如果无负数,遇到大于1的数就放入序列;不过需要考虑正负号,用邻点动态规划

double mpseq(vector<double> &nums) {
    double cmax, cmin, pmax, pmin;
    cmax = cmin = pmax = pmin = 1;
    for(double i : nums) {
        cmax = max(max(i, pmax), max(pmax * i, pmin * i));
        cmin = min(min(i, pmin), min(pmax * i, pmin * i));
        pmax = cmax;
        pmin = cmin;
    }
    return pmax;
}
View Code

 

最长递增子序列(Longest increasing subsequence)

暴力方案TC为O(2^n)。如果用贪心法,分析知道根据第i个包含的信息,无法覆盖前i个数的情况,故第i+1个数的决策没法做;更好的方案为,全局动态规划,用opt[i]记录到i为止最长的且包含 i上字符的最长递增subsequence,opt[i]初始为1,动态转移方程为opt[i] = max(opt[j] + 1, opt[i])。

vector<int> miseq(vector<int> &num) {
    vector<int> result;
    if(num.empty())
        return num;
    vector<int> opt(num.size(), 1), record(num.size(), -1);
    for(int i = 0; i < num.size(); i++) {
        for(int j = 0; j < i; j++) {
            if(num[i] > num[j] && opt[j] + 1 > opt[i]) {
                opt[i] = opt[j] + 1;
                record[i] = j;
            }
        }
    }
    int last = -1, gmax = 0;
    for(int i = 0; i < num.size(); i++) {
        if(opt[i] > gmax) {
            gmax = opt[i];    
            last = i;
        }
    }
    while(last >= 0) {
        result.push_back(num[last]);
        last = record[last];
    }
    reverse(result.begin(), result.end());
    return result;
}
View Code

 

最长匹配子字符串(Longest common substring) 

暴力方案TC为O(m*n*max(m,n))。更好的方案为,邻点动态规划,用opt[i][j]记录到M的i - 1位置与N的j - 1位置为止,且包含i - 1,j - 1的最长匹配子字符串。将0位置设为岗哨,其中opt[0][j] = opt[i][0] = 0,动态转移方程为opt[i][j] = M[i] == N[j] ? opt[i - 1][j - 1] + 1 : 0。TC = O(m*n),SC = O(m * n),用滚动数组可以压缩到O(m) or O(n)。

string lcstr(string M, string N) {
    //找到最长匹配子字符串的长度
    int maxlen = INT_MIN, ri = -1;
    vector<vector<int> > opt(M.size() + 1, vector<int>(N.size() + 1, 0));
    for(int i = 1; i <= M.size(); i++)
        for(int j = 1; j <= N.size(); j++) {
            opt[i][j] = M[i - 1] == N[j - 1] ? opt[i - 1][j - 1] + 1 : 0;
            if(opt[i][j] > maxlen) {
                maxlen = opt[i][j];
                ri = i;
            }
        }
    //将最大值对应的匹配子字符串输出
    string result = "";
    int starti = ri - maxlen;
    for(int i = starti; i < ri; i++)
        result += M[i];
    return result;
}
View Code

 

最长匹配子序列(Longest common subsequence) 

更好的方案为,与最长匹配子字符串类似,邻点动态规划,用opt[i][j]记录到M的i - 1位置与N的j - 1位置为止,且包含i - 1,j - 1的最长匹配子字符串。将0位置设为岗哨,其中opt[0][j] = opt[i][0] = 0,动态转移方程为opt[i][j] = M[i] == N[j] ? opt[i - 1][j - 1] + 1 : max(opt[i - 1][j], opt[i][j - 1])。TC = O(m*n),SC = O(m*n),滚动数组同样可以压缩到O(m) or O(n)。

//只是找长度很简单...
int lenlcstr(string M, string N) {
    //找到最长匹配子字符串的长度
    vector<vector<int> > opt(M.size() + 1, vector<int>(N.size() + 1, 0));
    for(int i = 1; i <= M.size(); i++)
        for(int j = 1; j <= N.size(); j++)
            opt[i][j] = M[i - 1] == N[j - 1] ? opt[i - 1][j - 1] + 1 : max(opt[i - 1][j], opt[i][j - 1]);
    return opt[M.size()][N.size()];
}
//还要返回一个最长匹配字符串
string lcstr(string M, string N) {
    //记录路径
    vector<vector<int> > track(M.size() + 1, vector<int>(N.size() + 1, -1));
    //找到最长匹配子字符串的长度
    vector<vector<int> > opt(M.size() + 1, vector<int>(N.size() + 1, 0));
    for(int i = 1; i <= M.size(); i++)
        for(int j = 1; j <= N.size(); j++) {
            if(M[i - 1] == N[j - 1]) {
                opt[i][j] = opt[i - 1][j - 1] + 1;
                track[i][j] = 0;
            }
            else {
                if(opt[i - 1][j] > opt[i][j - 1]) {
                    track[i][j] = 1;
                    opt[i][j] = opt[i - 1][j];
                }
                else {
                    track[i][j] = 2;
                    opt[i][j] = opt[i][j - 1];
                }
            }
        }
    int i = int(M.size()), j = int(N.size());
    string result = "";
    while(track[i][j] > -1) {
        if(track[i][j] == 0) {
            result += M[i - 1];
            i--;
            j--;
        }
        else if(track[i][j] == 1)
            i--;
        else
            j--;
    }
    reverse(result.begin(), result.end());
    return result;
}
View Code

 

编辑距离(Edit distance)

更好的方案,与LCS类似,邻点动态规划

int edist(string s1, string s2) {
    int m = int(s1.length()), n = int(s2.length());
    vector<vector<int> > opt(m + 1, vector<int> (n + 1, 0));
    for(int i = 0; i <= m; i++)
        opt[i][0] = i;
    for(int j = 0; j <= n; j++)
        opt[0][j] = j;
    for(int i = 1; i <= m; i++)
        for(int j = 1; j <= n; j++) {
            opt[i][j] = min(min(opt[i - 1][j], opt[i][j - 1]), opt[i - 1][j - 1]) + 1;
            if(s1[i - 1] == s2[j - 1])
                opt[i][j] = min(opt[i][j], opt[i - 1][j - 1]);
        }
    return opt[m][n];
}
View Code

 

最长无重复子字符串(Longest substring with no duplicate characters)

用一个unordered_set<char>里存已有的char,遇到加入一个新的char,则在set里找,如果没找到,则加入set,并且count++;如果找到了,则在substring的循环弹出,更新set,并循环count--,直到弹出新加入的char的重复字符。

 

posted @ 2016-03-10 18:46  CarlGoodman  阅读(313)  评论(0编辑  收藏  举报