1.内容介绍
本篇文章记录在leetcode中String主题下面的题目和自己的思考以及优化过程,具体内容层次按照{题目,分析,初解,初解结果,优化解,优化解结果,反思}的格式来记录,供日后复习和反思[注:有些题目的解法比较单一,就没有优化过程]。题目的顺序按照leetcode给出的题目顺序,有些题目在并不是按照题目本身序号顺序排列的,也不是严格按照难易程度来排列的。
因此,这篇文章并不具有很强的归类总结性,归类总结性知识将会在其他文章记录,本篇重点在记录解题过程中的思路,希望能对自己有所启发。
2.题目和解题过程
2.1 Longest Palindrome String
- 题目:Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.Example:
Input: "babad" Output: "bab" Note: "aba" is also a valid answer.
Example:Input: "cbbd" Output: "bb"
- 分析:咋一看题,有点蒙,题目要求:回文字符串,最长的。假设在1000个字符长度的字符串中搜索回文串,该怎么搜索呢?直接从头开始搜索?判断回文串的核心是确定首尾位置,然后向中间移动并对比首尾位置的字符是否相同,而现在并不知道哪些连续的子串是回文串,也就不知道回文串确切的长度,要知道,搜索一定是按照某种模式(逻辑)来进行的,所以不管效率高低,首先得确定有哪些可以用的搜索模式。最简单的就是按照从首字符开始枚举回文串长度在[1, length]范围进行逐个长度进行判断,然后再逐字符向后迭代重复枚举检查。显然,复杂度太高,但是搜索模式似乎不太可能改,因此优化的方向大概是根据回文串的特点结合当前搜索结果进行快速过滤不可能出现在回文串中的字符。
- 初解:对于字符串中每个字符,假设它为回文串的首位置,然后取字符串最后一个字符为回文串尾位置,判断是否是回文串,若是则记录,否则将尾部向前移动一个位置,重复判断直到首尾相遇,然后将首位置向后移动一个位置,直到字符串结束。从长向短搜索的一个好处是如果已经搜索到一个较长的回文串,那么就不必再继续在原位置向后搜索。
class Solution { public: string longestPalindrome(string s) { if(s.size() <= 1) return s; string res =""; int length = 0; for(int i = 0; i < s.size(); ++i) { if((s.size() - i) < length) break; for(int j = s.size() - 1; j > i; --j) { int tmp_length = j - i + 1; if(tmp_length < length) break; if(s[i] == s[j]) { int start = i, end = j; while(start < end) { if(s[++start]!=s[--end]) break; } if(start >= end) { res = s.substr(i,tmp_length); length = tmp_length; break; } } } } if(res == "") res = s[0]; return res; } };
- 初解结果:
- 优化解法:
- 优化结果:
- 反思:
2.2 ZigZag Conversion
-
题目:
- 分析:这道题属于花式打印的题目类型,所以只要找到打印的规律就能求解。
- 初解:最简单的解法,直接从每一行每一列观察打印的规律,发现收尾两行的字符数量相等,而中间的行多出了一些字符,如果将这些多出来的字符P,S,I分别看做与左边相邻的3个字符是一个group的话,就可以按照组的长度将字符分组,并将同一个组内的字符根据位置分配到相应的行上,最终再将各行相加即可。
class Solution { public: string convert(string s, int numRows) { if(numRows <= 1) return s; vector<string> res(numRows,""); int group_length = numRows + numRows - 2; for(int rows = 0; rows < numRows; ++rows) { int revise = 0; if(rows > 0 || rows < numRows-1) revise = rows; for(int i = 0; i < s.size(); ++i) { if(((i+revise)%group_length)==0 || ((i-revise)%group_length)==0) res[rows]+=s[i]; } } string total = ""; for(auto iter:res) total+=iter; return total; } };
- 初解结果:
- 优化解法:初解中在求解每一行的字符时都需要遍历整个字符串,因此效率低,如果仅仅遍历一次字符串就能将各个字符分配到对应的行上,这就能节省很多时间。这代表处理每一个字符时就能计算出该字符属于哪一行中的,也就是说找到行变化的规律就能在O(n)的时间复杂度下解决问题,在这个思路下按照原来的字符串顺序观察出现在每行上的顺序,可以看到行的编号是在[0, numRows]之间来回震荡的,结果不言而喻。
class Solution { public: string convert(string s, int numRows) { if(numRows <= 1 || s.size()<=1) return s; vector<string> res(numRows,""); int row=0, step=1; for(int i=0; i < s.size(); ++i) { res[row]+=s[i]; if (row == 0) step = 1; else if (row == numRows - 1) step = -1; row += step; } string total = ""; for(auto iter:res) total+=iter; return total; } };
- 优化结果:
- 反思:花式打印要观察行或者列的变化规律来解决问题。