题目地址:力扣
解法1:纵向扫描(版本1)
思路:既然要找公共子前缀,那么直接遍历整个数组,首先看一个字母是不是,是的话看两个字母是不,以此类推。要注意的是,公共子前缀的最长长度不超过数组中最短的字符串长度。因此可以先通过一轮遍历找出数组中最短的字符串。再进行正式的比较操作。
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
// 假定最短的字符串是数组中的第一个
string shortest = strs[0];
// 遍历一遍找出最短的字符串
for(auto i : strs)
if (i.size() < shortest.size())
shortest = i;
// 若最短的字符串为空则直接返回空
if (shortest != "")
{
// 定义sz为公共子前缀长度,prefix为公共子前缀
int sz = 1;
string prefix;
// 外层循环,直到公共子前缀等于最短字符串
for (; sz <= shortest.size(); ++sz)
{
// 本轮循环中子前缀为最短字符串的前面sz个字符
prefix = shortest.substr(0, sz);
// 遍历strs判断是否是公共子前缀
for (auto i : strs)
// 如果发现有一个不满足,那么子前缀一定是当前长度-1的子串
if (i.substr(0, sz) != prefix)
return shortest.substr(0, sz-1);
}
return prefix;
}
return "";
}
};
解法2:纵向扫描(版本2)
思路:对于数组中每一个字符串,依次比较它们的第一位是否一样,是的话就继续检查第二位,直至检查到某个字符串末尾,时间复杂度是O(mn),空间复杂度O(1)
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
// sz代表当前比较的字符位置,currC代表当前比较字符
int sz = 0;
char currC;
/* 循环条件是sz不超过数组中第一个元素的长度,即最大取第一个元素的长度-1
此时正好对应着下标取到第一个元素的最后一个字符
*/
while (sz < strs[0].size())
{
// currC设置为当前想要比较的字符
currC = strs[0][sz];
// 循环比较字符串中每个元素的当前字符是否都与currC相等
for(auto i : strs)
{
// 若出现有元素的长度小于sz或者当前比较的地方不相等
if (i.size() < sz || i[sz] != currC)
// sz只要大于0,就返回第一个元素从0开始,长度为sz的字符串,若sz=0则返回空串
return sz > 0 ? strs[0].substr(0, sz) : "";
}
// 每轮检查无异常就递增sz
++sz;
}
// 若出现sz大于等于第一个元素的长度,那么直接返回第一个元素
return strs[0];
}
};
解法3:横向扫描
这种方法只需要扫描一遍数组,那么时间复杂度就是O(mn),而且空间复杂度O(1)
思路:先比较数组中的第一个元素和第二个,挑选长度较小的那个从后往前比,可以找到最大的匹配位置max_match(即最长相等处的下标)。依次类推,当max_match等于-1的时候直接返回空串。否则遍历完成后,返回第一个元素的substr(0, max_match+1)
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
// 初始化当前扫描的下标以及最大匹配处下标为第一个元素的长度-1
int idx = strs[0].size() - 1;
int max_match = strs[0].size() - 1;
// 从第二个元素开始遍历
for (int i = 1; i < strs.size() ; ++i)
{
// 开始扫描的位置需要取max_match和当前元素长度-1二者之间较小的那个
idx = (strs[i].size() - 1 < max_match) ? strs[i].size() - 1 : max_match;
// 若idx有更新,同时更新max_match
max_match = idx;
// 从后往前扫描,直到扫描到字符串的第一个字符为止
for (; idx >= 0; --idx)
{
//
if (strs[i-1][idx] != strs[i][idx])
max_match = idx - 1;
}
if (max_match == -1)
return "";
}
// 若没有第二元素则直接返回第一个元素;若有的话返回最长公共前缀
return strs[0].substr(0, max_match + 1);
}
};
解法4:分治法
思路:就是将大问题分解为子问题,要求整个数组的,就可以把数组分成左右两部分,分别求两部分的公共子前缀,再合起来看一下哪个才是总的公共子前缀。以此类推
class Solution {
public:
string divide(vector<string>& strs, int start, int end)
{
// 若头尾相等,则直接返回当前元素
if (start == end)
return strs[start];
// 若头尾相差1,则求两个字符串的公共子前缀并返回
if (end - 1 == start)
{
int i = 0;
for (; i < min(strs[start].size(), strs[end].size()); ++i)
if (strs[start][i] != strs[end][i])
return i == 0 ? "" : strs[start].substr(0, i);
return strs[start].substr(0, i);
}
// 若头尾相差的不止1,则分治求前半部分的和后半部分的
string pre_1 = divide(strs, start, (start+end) / 2);
string pre_2 = divide(strs, (start+end) / 2 + 1, end);
// 若前半部分或后半部分为空串,则直接返回空
if (pre_1 == "" || pre_2 == "")
return "";
// 找到前半部分与后半部分前缀的共同部分
int idx = 0;
for (; idx < min(pre_1.size(), pre_2.size()); ++idx)
if (pre_1[idx] != pre_2[idx])
break;
return idx == 0 ? "" :pre_1.substr(0, idx);
}
string longestCommonPrefix(vector<string>& strs) {
return divide(strs, 0, strs.size()-1);
}
};
Accepted
- 124/124 cases passed (0 ms)
- Your runtime beats 100 % of cpp submissions
- Your memory usage beats 8.48 % of cpp submissions (10.1 MB)