LintCode 解题记录 17.8.7 字符串处理3

LintCode Generate Parentheses

给定n对括号,写一个函数去产生所有符合要求的括号组合。
思路
递归+回溯。
如何判断一个序号组合序列是有效的呢?我最先想到的是堆栈。左括号就入栈,右括号就出栈(前提栈不为空),如果空了就说明此时的组合不是合法的,就结束此次搜索。
还有一种简单的方法。针对从1~2n的序列,我们发现如下规则始终成立:左括号的个数大于等于右括号的个数。那么用两个指针leftNum和rightNum来表示左括号和右括号的余下的个数。如果leftNum > 0,那么此时添加一个左括号可以;如果 rightNum > leftNum && rightNum > 0,才可以添加一个右括号。如果leftNum=rightNum=0,说明搜索结束,此时产生的序列就是符合题意的。
代码

/**** version 1 ****/
    void dfs(vector<string> &res, string tmp, int cnt, stack<int> s, int n) {
        if (cnt == 2*n) {
            if (s.empty()) res.push_back(tmp);
            return;
        }
        for (int i = 0; i < 2; i++) {
            if (i == 0) {
                s.push(cnt);
                dfs(res, tmp + '(', cnt+1, s, n);
                s.pop();
            } else {
                if (s.empty()) break;
                s.pop();
                dfs(res, tmp + ')', cnt+1, s, n);
                s.push(cnt);
            }
        }
    }
    vector<string> generateParenthesis(int n) {
        // Write your code here
        vector<string> res;
        stack<int> s;
        string tmp = "";
        dfs(res, tmp, 0, s, n);
        return res;
    }

  /**** version 2 **** better version ****/
      vector<string> generateParenthesis(int n) {
        // Write your code here
        int leftNum, rightNum;
        leftNum = rightNum = n;
        vector<string> res;
        string tmp = "";
        generate(res, tmp, leftNum, rightNum);
        return res;
    }

    void generate(vector<string> &res, string tmp, int leftNum, int rightNum) {
        if (leftNum == 0 && rightNum == 0) {
            res.push_back(tmp);
            return;
        }
        if (leftNum > 0) {
            generate(res, tmp+'(', leftNum-1, rightNum);
        }
        if (rightNum > leftNum) {
            generate(res, tmp+')', leftNum, rightNum-1);
        }
    }

LintCode Integer to Roman

把一个数字转成罗马数字的写法。该数字大小不会超过3999。
思路
弄清楚罗马数字的写法,这题就很简单了。
代码

    string intToRoman(int n) {
        // Write your code here
        char c1[][5] = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
        char c2[][5] = {"X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
        char c3[][5] = {"C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
        char c4[][5] = {"M", "MM", "MMM"};
        string res = "";
        int cnt = 0;
        while (n) {
            if (n >= 1000) {
                res += c4[n/1000-1];
                n %= 1000;
            } else if (n >= 100) {
                res += c3[n/100-1];
                n %= 100;
            } else if (n >= 10) {
                res += c2[n/10-1];
                n %= 10;
            } else if (n >= 1) {
                res += c1[n-1];
                break;
            }
        }
        return res;
    }

LintCode Letter Combinations of a Phone Number

给定一个数字序列(不包含01),问你可以翻译成多少种字母组合。数字和字母的映射关系就是九宫格输入法。
思路
dfs
代码

    const vector<string> num2letter{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", 
        "tuv", "wxyz"};

    vector<string> letterCombinations(string& digits) {
        // Write your code here

        vector<string> res;
        string tmp = "";
        int n = digits.size();
        if (n) dfs(res, tmp, 0, digits, n);
        return res;
    }

    void dfs(vector<string> &res, string tmp, int idx, string digits, int n) {
        if (idx == n) {
            res.push_back(tmp);
            return;
        }
        int curr = digits[idx] - '0';
        for (int i = 0; i < num2letter[curr].size(); i++) {
            tmp += num2letter[curr][i];
            dfs(res, tmp, idx+1, digits, n);
            tmp.pop_back();
        }
    }

LintCode Longest Common Prefix

求k个字符串的最长公共前缀
思路
直接暴力搜索就可以。因为LCP一定是每一个字符串的子串。所以就可以选取某一字符串,逐个比较其字符和剩下所有字符串的字符是否相等,相等就跳到下一个字符,如果不相等或者发现一个长度更小的字符串,就返回当前结果。
其实我做的时候我最先想到了Merge K Sorted List这道题,于是也把这道题往这方面想了,当然也能做出来,算是又温习了一下这种问题的分治方法吧。
代码

    string longestCommonPrefix(vector<string> &strs) {
        // write your code here
        if (strs.size() == 0) return "";
        string res = "";
        res = findPrefix(strs, 0, strs.size()-1);
        return res;
    }

    string findPrefix(vector<string> &strs, int l, int r) {
        if (l == r) return strs[l];
        int mid = (l+r) >> 1;
        string pre1 = findPrefix(strs, l, mid);
        string pre2 = findPrefix(strs, mid+1, r);
        string res = findTwoPrefix(pre1, pre2);
        return res;
    }

    string findTwoPrefix(string str1, string str2) {
        string ret = "";
        for (int i = 0; i < min(str1.size(), str2.size()); i++) {
            if (str1[i] == str2[i]) ret += str1[i];
            else break;
        }
        return ret;
    }

LintCode Longest Common Substring

求两个字符串的最长公共子串。同理最长公共子序列。
正如题中提到了substring differs with subsequences.
思路
动态规划来解决。我们考虑状态dp[i][j]来表示字符串1的前i个字符和字符串2的前j个字符的最长公共子串(包含第i个字符和第j个字符)。其实这道题的思路有点像最大子序列和。接下来考虑str1[i-1]和str2[j-1],如果str1[i-1]==str2[j-1],那么dp[i][j] = dp[i-1][j-1]+1,如果不相等那么dp[i][j] = 0。然后我们只要在这个过称中寻找到dp[i][j]的最大值即可。
代码

int longestCommonSubstring(string &A, string &B) {
    // write your code here
    int m = A.size(), n = B.size();
    vector<vector<int> > local(m + 1, vector<int>(n + 1, 0));
    int maxx = 0;
    for (int i = 1; i < m + 1; i++) {
        for (int j = 1; j < n + 1; j++) {
            local[i][j] = A[i - 1] == B[j - 1] ? local[i - 1][j - 1] + 1 : 0;
            maxx = max(maxx, local[i][j]);
        }
    }
    return maxx;
}

同理,我们也来研究一下最大子序列的问题。这道题仍然是动态规划,我们用dp[i][j]来表示字符串1的前i位和字符串2的前j位的最大子序列长度。同样考虑str1[i-1]是否和str2[j-1]相等。如果相等,那么分别加上这一位一定形成了更长的子序列,所以此时dp[i][j] = dp[i-1][j-1]+1。如果不想等,那么思考从哪几个子状态中可以一步跳转到该状态呢?于是我们得到如下递推 dp[i][j] = max(dp[i-1][j], dp[i][j-1]);

LintCode Longest Palindromic Substring

求给定字符串的最长回文子串
思路
O(n2)的方法
从回文串的中心向两边遍历来寻找最长回文串,注意回文串有偶数长度的也有奇数长度的。所以对于每一位都要考虑这个因素。
代码

    string longestPalindrome(string s) {
        // write your code here
        if (s.size() == 0) return "";
        int l = 0, r = 0;
        int startIdx = 0, len = 0;
        for (int i = 0; i < s.size(); i++) {
            if (i < s.size()-1 && s[i] == s[i+1]) { //偶数长度
                l = i;
                r = i+1;
                SearchPalindrome(s, l, r, startIdx, len);
            }
            l = r = i; //奇数长度
            SearchPalindrome(s, l, r, startIdx, len);
        }
        return s.substr(startIdx, len);
    }
    void SearchPalindrome(string s, int left, int right, int &startIdx, int &len) {
        int step = 1;
        bool tag = true;
        while (left-step >= 0 && right+step < s.size()) {
            if (s[left-step] == s[right+step]) {
                step++;
            } else {
                tag = false;
                if (right-left+2*step-1 > len) {
                    len = right-left+2*step-1;
                    startIdx = left-step+1;
                }
                break;
            }
        }
        if (tag) {
                if (right-left+2*step-1 > len) {
                    len = right-left+2*step-1;
                    startIdx = left-step+1;
                }
        }
    }

思路2
此题也可以用动态规划的方法来解决。维护变量dp[i][j]表示字符串从i到j之间是否为回文串。那么如果i == j,意思就是单个字符,肯定是回文串。如果i和j之间相差为1,意思就是两个字符,就判断这两个字符是否相等来决定是否为回文串。如果i和j之间相差为2,那么dp[i][j] = dp[i+1][j-1] && s[i] == s[j]。
代码就不写了,提及此思路的原因就是想多跟动态规划套套近乎:D

思路3
O(n)的解法,是马拉车算法Manachers’ Algorithm。
算法介绍参考博客 :http://www.cnblogs.com/grandyang/p/4475985.html
代码

    string longestPalindrome(string s) {
        string t ="$#";
        for (int i = 0; i < s.size(); ++i) {
            t += s[i];
            t += '#';
        }
        int p[t.size()] = {0}, id = 0, mx = 0, resId = 0, resMx = 0;
        for (int i = 0; i < t.size(); ++i) {
            p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
            while (t[i + p[i]] == t[i - p[i]]) ++p[i];
            if (mx < i + p[i]) {
                mx = i + p[i];
                id = i;
            }
            if (resMx < p[i]) {
                resMx = p[i];
                resId = i;
            }
        }
        return s.substr((resId - resMx) / 2, resMx - 1);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值