刚才本来在看Ceph Cookbook这本书,中途休息时想起本周LeetCode上的刷题任务并没有完成,想着换件事情做做,换换脑子。于是乎,打开LeetCode,惊喜的发现,竟然是最大回文子串问题。为啥惊喜,因为这道题实在是算法笔、面试过程中的常客,光我在校招过程中就遇到了4、5次,不可谓不重要!但毕竟太久没有接触算法了,本着不放过任何一个细节的想法,果断解之。
-----------------------------------------------------------------我是问题描述开始标记-------------------------------------------------------------------
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"
-----------------------------------------------------------------我是问题描述结束标记-------------------------------------------------------------------
我觉得这道题的题目描述比第四道求Median的算法题的题目描述好得多,好在哪?好在清晰!而且题目描述中,把本题的主要两种情况都描述出来了,解题者基本只要考虑这两种主要情况即可。
情况1:
输入的字符串中,无连续重复字符;
情况2:
输入的字符串中,有连续重复字符;
此处,做一下引申,对于数据结构中的单向链表来说,有一个头结点的概念,头结点的next指针指向具有实际存储意义的单向链表中的第一个结点,data域可以为0,也可以用以保存单向链表中的结点个数等等。那么问题来了,既然一个单向链表可以存在头结点,也可以不存在头结点,那么有无头结点对于单向链表的操作来说,有什么区别?
最明显的一个区别就是,若是存在头结点,则可以以一套相同的处理机制来对单向链表中的所有具有实际存储意义的结点进行插入删除等操作;若是不存在头结点,则在进行插入删除等操作的过程中,需要针对第一个结点进行特殊处理。
有了上面这种处理思想,本题中,我也想采取同样的思想来规避有无连续重复字符所带来的处理方式上的不同。具体我采取的方式是,在字符串中的每个字符之间插入一个特殊字符,当进行寻找最长回文子串处理时,把特殊字符也当做字符串的一部分来进行处理,具体形式如:
若输入字符串为:abcdcbe,则插入特殊字符后待处理的字符串为:#a#b#c#d#c#b#e#;
同样,若输入字符串为:abbcdef,则插入特殊字符后待处理的字符串为:#a#b#b#c#d#e#f#;
可以看到经过特殊处理后,不论原本是有连续重复字符的字符串还是无连续重复字符的字符串,都变成了无连续重复字符的字符串,因此处理方式就得到了统一。这道题的主要有价值的处理思想就在这里,其他的处理过程没什么难度。具体代码如下:
string longestPalindrome(string s) {
if (s.size() == 0) return "";
else if (s.size() == 1) return s;
int middle_array_size = 2 * s.size() + 1;
int palindromic_substr_max_size = 1;
int longest_palindromic_substr_centre_index = 0;
char *middle_array = new char [middle_array_size];
string result_substr;
memset(middle_array, 0, middle_array_size);
for (int i = 0; i < middle_array_size && middle_array_size - i >= palindromic_substr_max_size / 2; i++)
{
if (i % 2 == 0)
{
middle_array[i] = '#';
}
else
{
middle_array[i] = s[i / 2];
}
}
for (int i = 1; i < middle_array_size - 1; i++)
{
int current_len = 1;
for (int j = 1; i - j >= 0 && i + j < middle_array_size && middle_array[i - j] == middle_array[i + j]; j++)
{
current_len += 2;
}
if (current_len > palindromic_substr_max_size)
{
palindromic_substr_max_size = current_len;
longest_palindromic_substr_centre_index = i;
}
}
for (int i = longest_palindromic_substr_centre_index - palindromic_substr_max_size / 2;\
i <= longest_palindromic_substr_centre_index + palindromic_substr_max_size / 2; i++)
{
if (middle_array[i] == '#')
{
continue;
}
else
{
result_substr.append(1, middle_array[i]);
}
}
return result_substr;
}
可以看到,其实程序执行效率不是很高,查看了下LeetCode上的TopSolutions,有一个C++的8ms 13行代码的解决方案,看了下,主要思想也是对有无连续重复字符做区别处理,不同的是,他并没有采取像我这样通过扩充字符串来进行处理的方式,而是采用若是有连续重复字符时,把所有的连续重复字符当做一个完整的字符来处理,这样的话,也达到了对有无连续重复字符两种情况处理方式上的统一,具体代码如下:
string longestPalindrome(string s) {
if (s.empty()) return "";
if (s.size() == 1) return s;
int min_start = 0, max_len = 1;
for (int i = 0; i < s.size();) {
if (s.size() - i <= max_len / 2) break;
int j = i, k = i;
while (k < s.size()-1 && s[k+1] == s[k]) ++k; // Skip duplicate characters.
i = k+1;
while (k < s.size()-1 && j > 0 && s[k + 1] == s[j - 1]) { ++k; --j; } // Expand.
int new_len = k - j + 1;
if (new_len > max_len) { min_start = j; max_len = new_len; }
}
return s.substr(min_start, max_len);
}
虽然处理的方式不同,但总体来说,两种处理方法的思想是一致的,就是规避代码处理过程中的不一致性,这样可以大大减少程序出问题的可能性。
经验教训:通过和Top Solutions上的答案进行对比,发现自己对细节的把握还不是很到位,需要提升。