【每日一题】LeetCode - 最长公共前缀

【每日一题】LeetCode - 最长公共前缀

给定一个字符串数组 strs,寻找它们的最长公共前缀。如果不存在公共前缀,则返回空字符串 ""

示例

示例 1
输入: strs = ["flower","flow","flight"]
输出: "fl"

示例 2
输入: strs = ["dog","racecar","car"]
输出: ""
解释:字符串数组中没有共同前缀,因此返回空字符串。

限制条件

  • 数组长度 1 <= strs.length <= 200
  • 字符串长度 0 <= strs[i].length <= 200
  • 所有字符串均为小写英文字母

2. 解题思路与实现方法

方法 1:横向扫描法

将第一个字符串作为初始公共前缀,逐一与数组中的其他字符串对比,通过缩减前缀长度来保证公共性。最终得到的是所有字符串的最长公共前缀。

代码实现
#include<iostream>#include<vector>#include<string>#include<algorithm>usingnamespace std;classSolution{public: string longestCommonPrefix(vector<string>& strs){ string prefix = strs[0];// 初始前缀for(int i =1; i < strs.size();++i){int j =0;// 比较前缀与当前字符串的公共部分while(j < prefix.size()&& j < strs[i].size()&& prefix[j]== strs[i][j]){++j;} prefix = prefix.substr(0, j);// 更新前缀长度if(prefix.empty())break;// 若前缀为空,直接返回}return prefix;}};intmain(){ Solution s; vector<string> strs ={"flower","flow","flight"}; cout << s.longestCommonPrefix(strs)<< endl;return0;}
代码解析
  1. 初始设置:将 strs[0] 作为初始公共前缀 prefix
  2. 逐个比较:遍历剩余的字符串,每次更新 prefix 为其与当前字符串的公共前缀。
  3. 返回结果:当 prefix 缩减为 0 时,立即返回空字符串;否则返回最终公共前缀。
时间复杂度计算
  • 最坏情况:所有字符串长度相等且无公共前缀时,时间复杂度为 O(S),其中 S 是数组中所有字符的总和。
  • 平均情况:由于每次比较时前缀逐渐缩短,通常能达到 O(minLen * n) 的效率,minLen 为字符串中最小长度,n 为字符串个数。
空间复杂度

仅使用常数额外空间,空间复杂度为 O(1)


方法 2:动态规划

此问题可转化为动态规划问题,逐字符检查公共前缀的长度。

  1. 状态定义:定义 dp[i][j] 表示到第 i 个字符串和第 j 个字符时的最长公共前缀长度。
  2. 状态转移方程:如果 strs[i][j] == strs[i-1][j],则 dp[i][j] = dp[i-1][j] + 1,否则 dp[i][j] = 0
  3. 初始条件:第一个字符串为初始公共前缀,依次向后遍历更新。
代码实现
#include<iostream>#include<vector>#include<string>usingnamespace std;classSolution{public: string longestCommonPrefix(vector<string>& strs){if(strs.empty())return"";int n = strs.size();int minLen = INT_MAX;// 寻找最小字符串长度for(const string& s : strs){ minLen =min(minLen,(int)s.length());}int low =1, high = minLen;while(low <= high){int mid =(low + high)/2;if(isCommonPrefix(strs, mid)) low = mid +1;else high = mid -1;}return strs[0].substr(0,(low + high)/2);}private:boolisCommonPrefix(const vector<string>& strs,int len){ string prefix = strs[0].substr(0, len);for(int i =1; i < strs.size(); i++){if(strs[i].find(prefix)!=0){returnfalse;}}returntrue;}};intmain(){ Solution solution; vector<string> strs ={"flower","flow","flight"}; cout << solution.longestCommonPrefix(strs)<< endl;return0;}
代码解析
  1. 初始化:找到最短字符串的长度,设为 minLen
  2. 二分搜索:通过二分法逐步缩小范围,判断当前长度是否是公共前缀。
  3. 判断公共前缀:利用 isCommonPrefix 函数检查当前长度 len 是否为公共前缀。
  4. 返回结果:最终获得的 (low + high) / 2 即最长公共前缀长度。
时间复杂度分析
  • 二分查找的复杂度为 O(log minLen),每次查找调用 isCommonPrefix 的时间复杂度为 O(n * minLen)。因此总复杂度为 O(n * minLen * log minLen)
  • 空间复杂度为 O(1)

方法 3:纵向扫描法

逐列检查每个字符是否相同,遇到不同则停止。这种方法直观易懂,适用于小规模字符串数组。

代码实现
string longestCommonPrefix(vector<string>& strs){if(strs.empty())return"";for(int i =0; i < strs[0].size(); i++){char c = strs[0][i];for(int j =1; j < strs.size(); j++){if(i == strs[j].size()|| strs[j][i]!= c){return strs[0].substr(0, i);}}}return strs[0];}

3. 方法对比与总结

方法时间复杂度空间复杂度适用场景
横向扫描法O(S)O(1)字符串较多
二分法+动态规划O(n * minLen * log minLen)O(1)大规模字符串
纵向扫描法O(S)O(1)小规模字符串
  • 横向扫描法纵向扫描法都适合中小规模数据,横向法代码更简洁。
  • 二分法结合动态规划提升了效率,适用于需要更高效率的情况。

Read more

哈希的介绍

哈希的介绍

1. unordered系列关联式容器     下面来看哈希,首先看关联式容器unorder_map和unorder_set,它们底层是哈希表,用法和map set一样。下面浅浅过一下,它是单向迭代器,因为没有rbegin和rend。也就是红黑树和哈希表实现的map和set用法几乎相同,区别是:1.unorder系列是单向迭代器。2.unorder系列遍历出来不是有序的。下面演示一下: 它只能去重,不能排序,它也是有multi版本的。再演示一下unorder_map: 2.哈希     下面正式看哈希,什么是哈希呢?我们以前遇到的搜索有这样几类:首先是暴力查找,在一个数组里都查,这样非常慢。于是有人衍生出了有序数组的二分查找,但它的前提是排序,而且增删查改不方便,过程中为了保证有序会涉及大量的数据挪动。因此衍生出了平衡搜索树,此时基础上又出现了新的搜索,这种搜索叫哈希(散列)。它的本质是存储的值跟存储位置建立出一个映射关系,什么意思呢,先来看一个计数排序的样例: 有上面这样的一组值,最小的值是15,最大的值是30,总共开了16个空间。然后存映射关系(次数),15映射第一个位

By Ne0inhk
从零开始打造高性能数据结构——手把手教你实现环形缓冲

从零开始打造高性能数据结构——手把手教你实现环形缓冲

◆ 博主名称: 小此方-ZEEKLOG博客 大家好,欢迎来到小此方的博客。 ⭐️个人专栏:《C语言》_小此方的博客-ZEEKLOG博客 算法_小此方的博客-ZEEKLOG博客  ⭐️踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰。 目录 一,普通队列的劣势 1. 空间浪费严重(“假溢出”问题) 2. 需要频繁移动元素(若避免浪费) 3. 扩容成本高 4. 无法解决“假溢出”导致的提前扩容 二,环形缓冲结构分析  1. “循环”取模实现指针回绕  2.“循环”,轮流入座而不是排长队 三,实现环形缓冲 1,MyCircularQueue(k): 构造器   1,结构体搭建   2,初始化 3,为什么选择k+1块空间而不是k块空间?

By Ne0inhk
【LeetCode 704 & 34_二分查找】二分查找 & 在排序数组中查找元素的第一个和最后一个位置

【LeetCode 704 & 34_二分查找】二分查找 & 在排序数组中查找元素的第一个和最后一个位置

场景应用 在算法学习中,二分查找是一种高效的查找算法,其时间复杂度为 O ( l o g n ) O(log n) O(logn),适用于有序数组的查找场景。在实际场景中,当只需判断目标值是否存在于有序数组中,且数组内元素唯一时,用最简单的基础二分查找就足够,比如在按学号有序排列的唯一学生ID数组中查找某学生是否存在、在无重复的商品编码有序列表中检索指定编码是否存在;而当有序数组中存在重复的目标值,且需要确定目标值的范围边界时,就需要用查找左右边界的二分查找,比如在按时间戳排序的重复打卡记录中找某员工首次和末次打卡的位置、在成绩有序数组中找某分数出现的起始和结束排名、在商品销量统计的有序数组中找某一销量值对应的首个和最后一个商品下标。 * 场景应用 * 一、二分查找 * 1.1 题目链接 * 1.2 题目描述 * 1.3 题目示例 * 1.4 算法思路 * 1.5 核心代码 * 1.6 示例测试(总代码) * 二、

By Ne0inhk
《算法题讲解指南:优选算法-双指针》--01移动零,02复写零

《算法题讲解指南:优选算法-双指针》--01移动零,02复写零

🔥小叶-duck:个人主页 ❄️个人专栏:《Data-Structure-Learning》 《C++入门到进阶&自我学习过程记录》 ✨未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游 目录 一、双指针算法介绍   1、对撞指针   2、快慢指针 01、移动零 题目链接: 题目描述: 题目示例: 算法思路: 算法流程: C++ 代码演示: 算法总结: 02、复写零 题目链接: 题目描述: 题目示例: 算法思路: 算法流程: C++代码演示: 算法总结及流程解析: 结束语 一、双指针算法介绍       在正式讲解本次的算法题之前我们先来看看算法中一个非常常用的方法——双指针。双指针有两种形式,一种对撞指针,一种是左右指针。   1、对撞指针

By Ne0inhk