剑指Offer(数据结构与算法面试题精讲)C++版——day7
剑指Offer(数据结构与算法面试题精讲)C++版——day7
- 题目一:最多删除一个字符得到回文
- 题目二:回文子字符串的个数
- 题目三:删除倒数第k个节点
题目一:最多删除一个字符得到回文
这里我们可以在经典的字符串回文判断方法基础上进行巧妙调整。判断字符串是否为回文,常用的高效方式是使用双指针法,让两个指针分别从字符串的头部和尾部开始,相向而行,朝着字符串的中间遍历。在这个遍历过程中,一旦发现两个指针所指向的字符不相等,就到了关键的决策点。此时,我们需要进行两种尝试操作,分别是删除当前左侧指针所指向的字符,以及删除当前右侧指针所指向的字符。这两种尝试操作的目的,都是为了通过删除一个字符,看能否使剩余的字符串成为回文。除此之外,我们还可以充分利用之前已经遍历过的字符的下标信息,优化代码的逻辑结构和实现方式,最终得到如下代码:
# include <iostream>
# include <algorithm>
using namespace std;
struct result {
bool palindrome;
int start;
int end;
};
result isPalindrome(string s,int i,int j) {
cout<<s<<endl;
int len=s.length();
for(; i<len/2; ++i,--j) {
if(s[i]!=s[j]) {
result res= {false,i,j};
return res;
}
}
result res= {true,-1,-1};
return res;
}
bool checkPalindrome(string s) {
int len=s.length();
result res=isPalindrome(s,0,len-1);
if(res.palindrome) {
return true;
} else {
string str1=s,str2=s;
return isPalindrome(str1.erase(res.start,1),res.start,res.end-1).palindrome
||isPalindrome(str2.erase(res.end,1),res.start,res.end-1).palindrome;
}
}
int main() {
string s="abca";
cout<<"最多删除一个字符后,是否是回文字符串:"<<checkPalindrome(s)<<endl;
return 0;
}
这里积累一个字符串处理的方法erase(int pos, int count)
,可用于删除字符串指定元素。显然这里的时间复杂度为O(n)。
题目二:回文子字符串的个数
这里题目所求的是子字符串的个数,而非种类,这是一个容易混淆的关键要点。其实,这道题的解题思路并不复杂,我们可以直接对原字符串进行从左到右的顺序遍历。在遍历的过程中,每遇到一个字符,我们都将其作为一个关键的边界点,以此为中心向字符串的两边进行扩展操作。在这个扩展的过程中,一个重要的考虑因素就是要判断当前这个字符是作为奇数长度回文子串的中间字符,还是作为偶数长度回文子串的中间字符。如果是偶数长度回文子串的中间字符,那么为了构成完整的偶数对结构,就需要在这个字符的右侧再补充一个字符,从而满足偶数长度回文子串的特征。基于这样的思路和逻辑,我们便可以通过相应的代码实现来解决这个问题,得到如下代码:
# include <iostream>
# include <algorithm>
using namespace std;
int countPalindromeStr(string s,int i,int j) {
int count=0,len=s.length();
while(i>=0&&j<len&&s[i]==s[j]) {
count++;
i--;
j++;
}
return count;
}
int main() {
string s="aaa";
int count=0;
for(int i=0,len=s.length(); i<len; ++i) {
count+=countPalindromeStr(s,i,i);
count+=countPalindromeStr(s,i,i+1);
}
cout<<"输出回文子字符串的个数:"<<count<<endl;
return 0;
}
题目三:删除倒数第k个节点
这道题目属于链表基础类型的题目,通常采用的是快慢指针法来高效解决。具体的操作步骤为:首先选取两个指针,分别命名为 front 和 end。在算法开始执行时,front 和 end 指针都会指向链表的第一个节点。接下来,我们会先让 end 指针向后移动(k - 1)个位置。这么做的目的在于,在后续的同步移动过程中,front 和 end 指针之间能够保持(k - 1)个节点的间隔。当 end 指针完成这(k - 1)个位置的移动后,front 和 end 指针会开始同步向后移动,也就是每当 end 指针向后移动一个节点,front 指针也会相应地向后移动一个节点。这个同步移动的过程会持续进行,直到 end 指针指向链表的最后一个节点。由于 front 和 end 指针之间始终保持着(k - 1)个节点的间隔,所以当 end 指针指向最后一个节点时,front 指针此时所指向的节点就是我们要找的目标节点,也就是链表中的倒数第 k 个节点。利用这种快慢指针的方法,我们可以在一次遍历链表的过程中准确找到倒数第 k 个节点,有效提升了算法的效率。
# include <iostream>
# include <algorithm>
using namespace std;
struct linkNode {
int data;
linkNode * next;
linkNode(int val):data(val),next(nullptr) {}
};
typedef linkNode * linkList;
linkList createLinkList(int arr[],int len) {//使用空头链表
linkList head=new linkNode(0),q=head;
for(int i=0; i<len; ++i) {
linkNode * p=new linkNode(arr[i]);
q->next=p;
q=p;
}
q->next=nullptr;
return head;
}
void printLinkList(linkList head) {
linkList p=head;
while(p!=nullptr) {
cout<<p->data<<" ";
p=p->next;
}
}
int main() {
int arr[]= {1,2,3,4,5,6};
int k=2,start=k;
linkList head= createLinkList(arr,6);
linkNode * front=head->next,*end=head->next;
cout<<"原链表:";
printLinkList(head);
cout<<endl;
while(k--) {
end=end->next;
}
while(end){
front=front->next;
end=end->next;
}
cout<<"输出倒数第"<<start<<"个数:"<<front->data<<endl;
return 0;
}
我是【Jerry说前后端】,本系列精心挑选的算法题目全部基于经典的《剑指 Offer(数据结构与算法面试题精讲)》。在如今竞争激烈的技术求职环境下,算法能力已成为前端开发岗位笔试考核的关键要点。通过深入钻研这一系列算法题,大家能够系统地积累算法知识和解题经验。每一道题目的分析与解答过程,都像是一把钥匙,为大家打开一扇通往高效编程思维的大门,帮助大家逐步提升自己在数据结构运用、算法设计与优化等方面的能力。
无论是即将踏入职场的应届毕业生,还是想要进一步提升自己技术水平的在职开发者,掌握扎实的算法知识都是提升竞争力的有力武器。希望大家能跟随我的步伐,在这个系列中不断学习、不断进步,为即将到来的前端笔试做好充分准备,顺利拿下心仪的工作机会!快来订阅吧,让我们一起开启这段算法学习之旅!