前言
🔥个人主页:不会c嘎嘎
📚专栏传送门:【数据结构】 、【C++】 、【Linux】、【算法】、【MySQL】
🐶学习方向:C++方向学习爱好者
⭐人生格言:谨言慎行,戒骄戒躁每日一鸡汤:
"你现在的努力,不是为了感动谁,也不是为了证明给谁看,而是为了将来有一天,你能有底气去选择自己想要的生活,而不是被生活选择。别怕路长梦远,总有人山高水长为你而来。"


目录
开篇
今天给大家带来三道面试题分别是:
1.【验证回文串II】https://leetcode.cn/problems/RQku0D/description/ 字节跳动-2024-开发
2.【最长回文串】https://leetcode.cn/problems/longest-palindrome/description/ "谷歌-2024-开发 字节跳动-2023-开发
3.【寻找重复数】https://leetcode.cn/problems/find-the-duplicate-number/description/ 彭博-2024-开发
同学们可以先自己看看能不能独立的做出来,然后再来看看我的解法思路哦!
1.验证回文串II
题目描述
给定一个非空字符串 s,请判断如果 最多从字符串中删除一个字符能否得到一个回文字符串。
核心算法
碰撞双指针
解题思路
相信大家都做过回文串1,回文串就是两边对称的字符串。验证一个字符串是否为回文串只需要从两边向中间一直比较字符,直到两个指针相遇"或直到 left > right,如果全都相同说明是回文串。
1.题目给出了可以删除一个字符,那我们就按照回文串的方式验证当前字符串。
2.当字符相同的时候两边指针向中间逼近,继续比较后续的两个字符。
3.当遇到不相同的字符时,说明我们需要做出一次'删除'选择。此时我们有两种选择:要么删除左指针字符(验证 [left + 1, right] 区间),要么删除右指针字符(验证 [left, right - 1] 区间)。只要其中任意一个区间是回文串,原字符串即符合要求。
流程图
示例:woldbaabdow

1.当left = 2 right = 8的时候,两个字符不相同,此时需要跳过字符

2.跳过下标2的字符,再验证剩下的字符串是否回文。明显这里dbaabd是回文串

3.当两个指针相遇的时候循环结束

具体代码
cpp
// 辅助函数:验证子串是否为回文
// 注意:这里使用 const string& s 避免字符串拷贝,提高性能
bool check(int left, int right, const string& s) {
while (left < right) {
if (s[left] != s[right]) {
return false;
}
left++;
right--;
}
return true;
}
bool validPalindrome(string s) {
if (s.size() <= 2) return true;
int left = 0, right = s.size() - 1;
while (left < right) {
if (s[left] != s[right]) {
// 遇到不匹配时,尝试删除左边字符 OR 删除右边字符
// 只要有一边成功,结果就为 true
return check(left + 1, right, s) || check(left, right - 1, s);
}
left++;
right--;
}
return true;
}
2.最长回文串
题目描述
给定一个包含大写字母和小写字母的字符串
s,返回 通过这些字母构造成的 最长的 回文串 的长度。在构造过程中,请注意 区分大小写 。比如"Aa"不能当做一个回文字符串。
核心算法
哈希表 + 贪心算法
解题思路
1.分析回文特性 :我们实际上并不用真的构造回文串,只需要计算长度,构成回文串的核心在于:除了最中间的字符外,其他字符必须成对出现 。例如 aabbb 可以组成 abbba,其中 a 和 b 各有一对,剩下的一个 b 放中间。
2.统计词频:既然要看字符能否凑成对,首先利用哈希表(或数组)统计字符串中每个字符出现的次数。
3.计算偶数部分 :遍历哈希表,对于每个字符的数量 count,其能组成回文串的长度贡献为 count / 2 * 2(即取最大的偶数)。
4.处理中心位 :如果在统计过程中,发现有字符的数量是奇数(说明凑完对子后有剩余),或者最终长度小于原字符串长度,说明我们可以选一个字符放在回文串的最中间,此时最终长度 +1。
具体代码
cpp
class Solution {
public:
int longestPalindrome(string s) {
// 使用数组模拟哈希表(ASCII字符范围)
// 也可以使用 unordered_map<char, int> map;
int map[128] = {0};
// 1. 统计每个字符出现的次数
for(char c : s) {
map[c]++;
}
int len = 0;
// 2. 遍历统计结果
for(int i = 0; i < 128; i++) {
// 加上该字符数量的偶数部分
// 比如 5/2 = 2, 2*2 = 4,说明有4个可以组成回文
len += map[i] / 2 * 2;
}
// 3. 判断是否有中心点
// 如果当前计算的长度小于原字符串长度,说明肯定有剩下的字符(奇数个的字符)
// 我们可以在中间放一个,所以长度 +1
if (len < s.size()) {
len++;
}
return len;
}
};
3.寻找重复数
题目描述
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
核心算法
快慢指针(Floyd 判圈算法) & 数组抽象化
解题思路
这道题的难点在于如何在不修改数组且空间复杂度为 O(1)的情况下解决。
1.抽象为链表 : 由于数组中数值范围是 [1, n],而数组下标范围是 [0, n],我们可以建立一种映射关系:将下标 i 看作当前节点,nums[i] 看作指向下一个节点的指针(即 next)。
例如
[1,3,4,2,2]:
0 -> 1
1 -> 3
3 -> 2
2 -> 4
4 -> 2 (这里指向了已经访问过的节点2,形成了环)
因为存在重复的数字(多个下标指向同一个值),所以这个抽象链表中一定存在环 ,且环的入口就是那个重复的数
2.快慢指针找相遇点 : 定义 slow 和 fast 指针,slow 每次走一步,fast 每次走两步。如果有环,它们必然会在环内相遇。
3.数学推导找入口: 假设:
起点到环入口的距离为 a。
环入口到相遇点的距离为 b。
相遇点再走 c步回到环入口(即环周长 L = b + c)。
当两者相遇时:
slow走的距离:a + b
fast走的距离:a + b + kL (k 为在环里绕的圈数)因为
fast速度是slow的两倍:2(a + b) = a + b + kL化简得:a = kL - b
进一步转化为:a = (k-1)L + (L - b) = (k-1)L + c
结论 :这就意味着,一个指针从起点出发(走 a 步),另一个指针从相遇点出发(走 c 步,或者多绕几圈后的 c 步),它们最终会在环的入口点相遇。
流程图
数组模拟链表操作

链表抽象图

具体代码
cpp
int findDuplicate(vector<int>& nums) {
int slow = 0;
int fast = 0;
// 1. 第一阶段:快慢指针寻找环内的相遇点
// 由于必定有重复值,所以必定有环,循环不会死锁
while (true) {
slow = nums[slow]; // 慢指针走一步
fast = nums[nums[fast]]; // 快指针走两步
if (slow == fast) {
break; // 找到相遇点
}
}
// 2. 第二阶段:寻找环的入口(即重复的数)
// 将 slow 重置回起点,fast 停留在相遇点
// 此时两个指针每次都只走一步
slow = 0;
while (true) {
slow = nums[slow];
fast = nums[fast];
// 当它们再次相遇时,所在的节点就是环的入口
if (slow == fast) {
break;
}
}
return slow;
}
结语
本文分享了三个算法面试题的解法:1. 验证回文串II(字节跳动)使用碰撞双指针,当字符不匹配时尝试删除左/右字符验证剩余部分是否为回文;2. 最长回文串(谷歌/字节跳动)通过哈希表统计字符频率,利用贪心算法计算偶数对并处理中心位;3. 寻找重复数(彭博)将数组抽象为链表,应用快慢指针的Floyd判圈算法找到环入口即重复数。
以上就是本期博客的全部内容,感谢各位的阅读以及观看。如果内容有误请大佬们多多指教,一定积极改进,加以学习。

