笔试中,不用在意空间复杂度,各种辅助结构直接上,越快写出越好
面试中,追求时间复杂度O(n)空间复杂度O(1)
表示,插入,删除
struct listnode{
int val;
listnode *next;
listnode(int _val,listnode *_next): val(_val),next(_next){}
}; //表示
p->next = p->next->next; //删除了p-》next
copy_cur = new node(cur->label);
copy_cur->next =cur->next;
cur->next = copy_cur; //这3步插入新节点 插在cur之后
tips:涉及到删除操作的时候,一定要考虑空表,删除在表头,在表中等操作
慢指针p1走1步 快指针p2走2步
node *p1 = root;
node *p2 = root;
while(p2->next && p2->next->next){
p1 = p1->next;
p2 = p2->next->next;
}
效果是p1走到中点,假如总数是偶数个 p1来到中间两个的第一个,p2来到最后一个的前一个
假如总数是奇数个,p1来到中间那个,p2来到最后一个。
题目描述
输入一个链表,反转链表后,输出新链表的表头。 (来源剑指offer 24)(无头节点)
a) 迭代思路
思路:我们要修改每一个节点的next指针成员,所以每次都是修改当前cur的next,指向pre(所以记忆pre),并且还需要记住cur的next节点。
记忆方法:pre, cur,after三指针。
代码
//第一种方法是:非递归方法
//设计好代码后 要考虑的特殊情况nullptr 一个节点 多个节点
listnode *reversenode(listnode *phead){
listnode *pre=nullptr, *cur=phead, *after = nullptr;
while(cur != nullptr){
after = cur->next; //保存当前的下一个
cur->next=pre; //调整当前next
pre = cur; //前一个前进
cur= after; //当前前进
}
return pre;
}
b)递归方法
tips: 也不巧妙,so easy。大问题,小问题,and做好自己的事情就ojbk了
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
//如果链表为空或者链表中只有一个元素
if(pHead==NULL||pHead->next==NULL) return pHead;
//先反转后面的链表,走到链表的末端结点
ListNode* pReverseNode=ReverseList(pHead->next);
//再将当前节点设置为后面节点的后续节点
pHead->next->next=pHead;
pHead->next=NULL;
return pReverseNode;
}
};
有头节点
没啥特殊的,就是先检查一下头节点为空吗,然后最后让头节点指向反转后的链表头
listnode *reversnode2(listnode *phead){
if(!phead)
return phead;
listnode *pre=nullptr, *cur=phead->next, *after = nullptr;
while(cur != nullptr){
after = cur->next; //保存当前的下一个
cur->next=pre; //调整当前next
pre = cur; //前一个前进
cur= after; //当前前进
}
phead->next = pre;
return phead;
}
反转双向链表
也没有什么特殊的,知道有这么个东西,next和pre两个指针成员交换一下就行
题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 (来源剑指offer 25)
迭代版思路
我们需要3个指针,指针a,b用来跟踪两个链表,指针c用来指向我们归并后的链表的最后一个节点,这样当我们比较a,b指向的节点大小后,让c的next指向比较后的结果,即可。假如有个链表到底了,那么直接将另一个链表中的元素添加进合并链表。在具体的实现过程中,我们通过设置一个傀儡节点(链表中常用的技巧),root来进行操作,其作用在于让合成的新链表有一个着手点。这个节点的值可以随意,我们最终返回的,实际上是root.next;
代码
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
using node = ListNode;
//新建一个头节点,用来存合并的链表。
node root = node(0);
node *cur = &root;
while(pHead1 && pHead2){
if(pHead1->val < pHead2->val){
cur->next=pHead1;
pHead1=pHead1->next;
} else{
cur->next=pHead2;
pHead2 = pHead2->next;
}
cur=cur->next;
}
//把未结束的链表连接到合并后的链表尾部
if(pHead1)
cur->next=pHead1;
if(pHead2)
cur->next=pHead2;
return node.next;
}
};
递归版思路
比较2个的头节点大小,得到真头节点phead(更小的),然后递归处理小问题,再加上一些操作(把这个大问题的结果phead 跟小问题的结果连起来,返回phead,递归出口假如哪一方为空节点,直接返回另一方)
代码
//递归版本 //
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(!pHead1)
return pHead2;
if(!pHead2)
return pHead1;
ListNode* p=nullptr;
if((pHead1->val)< (pHead2->val)){
p=pHead1;
p->next = Merge(pHead1->next,pHead2);
}
else{
p=pHead2;
p->next = Merge(pHead1,pHead2->next);
}
return p;
}
};
题目描述
输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。 (来源剑指 6)
思路
利用栈 (简单题)
代码
class Solution {
public:
vector printListFromTailToHead(ListNode* head) {
stack sta;
vector res;
while(head){
sta.push(head->val);
head=head->next;
}
while(!sta.empty()){
res.push_back(sta.top());
sta.pop();
}
return res;
}
};
题目描述
输入一个链表,输出该链表中倒数第k个结点。 (来源剑指 22 )
简单但重要
思路
快慢指针,注意倒数第k个节点,所以快指针先走 k-1步,而不是k步,并且注意k=0和head是空指针的边界问题,还有假如链表的长度小于k的情况
代码
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
// 认为k==0是个无效的东西
if(!pListHead || k==0)
return nullptr;
using node = ListNode;
node * cur= pListHead;
for(int i=0;inext;
}
//链表的长度小于k的情况
if(!cur)
return nullptr;
//想象一下k=1 可以写出代码
node * res = pListHead;
while(cur->next){
cur=cur->next;
res=res->next;
}
return res;
}
};
题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空) (来源剑指 35)
思路
两种解法:
1.哈希表存
2.把链表double,设置指针,最后断开。
//解法1: 哈希 时间O(n) 空间O(n) //
两次遍历。
第一次遍历copy每个节点,并且一起放进哈希表中。
第二次遍历,拿出关联的那个copy,设置copy的random next
哈希代码
/*
struct RandomListNode {
int label;
struct RandomListNode *next, *random;
RandomListNode(int x) :
label(x), next(NULL), random(NULL) {
}
};
*/
class Solution {
public:
RandomListNode* Clone(RandomListNode* pHead)
{
if(!pHead)
return nullptr;
using node = RandomListNode;
unordered_map dict;
node *cur=pHead;
while(cur){
dict.insert({cur,new node(cur->label)});
cur=cur->next;
}
cur=pHead;
while(cur){
dict[cur]->next=dict[cur->next];
dict[cur]->random=dict[cur->random];
cur=cur->next;
}
return dict[pHead];
}
};
解法2代码
- 遍历节点,每个节点都给往后面插一个 值一摸一样的新节点
- 再遍历一遍节点 这次一次两步,把每个新节点的random都给搞定
- 最后把链表给断开。 (注意几个边界条件)
时间 O(n) 空间 O(1)
class Solution {
public:
RandomListNode* Clone(RandomListNode* pHead)
{
if(pHead ==nullptr)
return nullptr;
return core1(pHead);
}
//让原链表 double
RandomListNode* core1(RandomListNode* pHead){
using node = RandomListNode;
node * cur = pHead;
node* copy_cur=nullptr;
while(cur){
copy_cur = new node(cur->label);
copy_cur->next =cur->next;
cur->next = copy_cur; //这3步插入新节点
cur = copy_cur->next; //向下走
}
//复制节点的 random
cur = pHead;
copy_cur = pHead->next;
while(cur){
if(cur->random){ //避免random指向空
copy_cur->random = cur->random->next;
}
cur = copy_cur->next;
if(cur != nullptr) // 再次注意边界
copy_cur = cur->next;
}
//拆分链表
cur = pHead;
copy_cur = pHead->next;
node * copy_head = copy_cur;
while(cur){
//cur和copy的拆分都需要!
cur->next = copy_cur->next; //拆 cur
cur = cur->next; // cur往下移动
if(cur != nullptr) // 注意边界
copy_cur->next = cur->next; // 拆copy
copy_cur =copy_cur->next; // copy移动
}
return copy_head;
}
};
题目描述
给定单向链表的头指针和一个节点指针,在O(1)的时间内删除该节点 (来源剑指18 )
思路
因为要求是O(1),且是单向链表,所以提出一个新方法 “替换法”
- 如果该节点不是尾节点,那么可以直接将下一个节点的值赋给该节点,然后令该节点指向下下个节点,即删除下一个节点,时间复杂度为 O(1)。
- 如果是尾节点,但是整个链表,只有这一个节点,那直接删除
- 如果链表有多个节点,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向nullptr,再delete,时间复杂度为 O(N)。
ps. delete 之后,别忘了设为nullptr(为了规范)
代码
void core(node **head,node *del ){
//如果是空指针
if(!head || !del)
return;
//要删除的del不是尾结点
if(del->next){
node * res =del->next;
del->val=res->val;
del->next=res->next;
delete res;
res=nullptr;
}
else if(*head ==del){ // 只有这一个指针
delete del;
del=nullptr;
*res=nullptr;
}
else{ // 有多个节点,并且是尾结点,遍历找到前一个
node *cur = *head;
while(cur->next != del){
cur=cur->next;
}
cur->next=nullptr;
delete del;
del=nullptr;
}
}
a)保留版本
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点保留1个,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->3->4->5
tips:这是保留版本,and排好序了
思路
遍历节点保存上一个节点pre和当前节点cur
- 假如当前节点和上一节点值相同,那么就对cur执行删除操作 并且pre不前进,cur前进.
- 假如不同则两个都前进
代码
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if(!pHead) return nullptr;
ListNode* pre = pHead;
ListNode* cur = pHead->next;
while(cur){
if(pre->val==cur->val){
pre->next = cur->next;
cur=cur->next;
}
else{
pre =cur;
cur=cur->next;
}
}
return pHead;
}
};
b)不保留版本
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
tips:这是不保留版本,依旧排好序了
迭代
因为这里重复节点都要被删了,所以就有可能头结点也要被删了.
所以为了方便,我们首先我们先添加一个傀儡节点root,以方便碰到第一个,第二个节点就相同的情况
然后我们保证p一直指向最后一个不重复的节点,所以我们先让p指向root,开始遍历
- 假如p-》next的val== p-》next-》next的val,那么开始消除,我们先记下这个值dup,删除直到p-》next的val不等于dup或者next等于nullptr了为止
- 假如不等,那p就往后移动
代码
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
using node = ListNode;
if(!pHead || !pHead->next)
return pHead;
node temp(0);
node *root=&temp;
root->next=pHead; // 首先添加一个傀儡节点,以方便碰到第一个,第二个节点就相同的情况
node *p=root;
int dup;
while(p->next && p->next->next){
//如果当前cur有下一个 且下一个跟cur值一样,那么
if(p->next->val== p->next->next->val ){
dup=p->next->val;
while(p->next && p->next->val== dup){
p->next=p->next->next; //直到p-》next的val是个新值为止 or 指向nullptr。
}
}
else p=p->next;
}
return root->next;
}
};
递归版
很好理解
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
// 只有0个或1个结点,则返回
if(!pHead || !pHead->next)
return pHead;
// 当前结点是重复结点
if(pHead->val == pHead->next->val){
ListNode * cur = pHead->next;
while(cur && cur->val == pHead->val){
cur =cur->next;
}// 跳过值与当前结点相同的全部结点,找到第一个与当前结点不同的结点
return deleteDuplication(cur);
// 从第一个与当前结点不同的结点开始递归
}
// 当前结点不是重复结点
else{
// 保留当前结点,从下一个结点开始递归
pHead->next = deleteDuplication(pHead->next);
return pHead;
}
}
};
两个单向链表,求第一个相交的点,实际这是3个问题!!!
- 怎么判断 1个链表是否有环 并找到环的入口
- 怎么找到2个无环单链表第一个相交的节点
- 怎么找到2个有环单链表第一个相交的节点
tips: (假如一个有环,一个无环,两个单向链表(因为是单向链表)不可能相交)
这里只写思路: 完整分析
问题1:怎么判断 1个链表是否有环 并找到环的入口
一个 慢指针p1每次走1步,一个快指针p2每次走2步,假如无环,快指针一定遇到终点也就是 next是nullptr,那直接返回nullptr,假如有环,那p1和p2肯定会在环中的某个位置相遇。这个时候把快指针 放回链表开头位置,并且每次走一步,同时慢指针也从那个相遇的位置每次走一步,最后两个指针相遇的位置一定是环的入口
问题2:怎么找到2个无环单链表第一个相交的节点?
2个无环单链表,假如相交,那只能是Y型的
那么找的过程是
链表1 遍历到结尾节点end1,同时记录总结点数len1
链表2 遍历到结尾节点end2, 同时记录总结点数len2
比较end1和end2,不相等返回nullptr,相等进入下一步操作
len大的那个链表,假如是链表1,先走len1-len2步,
然后两个链表一起走,第一个两个节点相等的结果就是第一个相交的节点。
问题3:怎么找到2个有环单链表第一个相交的节点?
分成3种情况
1)两个环的入口相等 那么总体形状是 Y+一个环,
这种情况就跟问题2,两个无环的第一个相交节点类似,只不过现在终点是环的入口
2)两个环的入口不等,且两个链表不相交,
3)两个环的入口不等,但是两个链表相交, 那拓扑形状就是从一个环,分出两条线,这样,第一个相交点就是两个环入口的任何一个
只有思路
类似于merge的过程,从各自的起点开始出发,哪个值小,就前进一步,相同就打印
(来源左神 )
题目描述
1221回文 12331不是回文
(来源左神 )
思路
使用辅助空间:
用一个栈 遍历链表 把值保持在栈中
然后再遍历一次链表 同时跟栈顶元素比较,不一样则返回false,一样则,弹出,比较下一个
不使用辅助空间:
快指针走两步,慢指针走一步,来到中间那个节点位置,然后把后半节点给它反转了,然后从两端开始比较,相同则比下一个,不同则false,同时最后把后半部分节点给它反转回来。
java代码
// need O(1) extra space
public static boolean isPalindrome3(Node head) {
if (head == null || head.next == null) {
return true;
}
//快指针走两步,慢指针走一步,来到中间那个节点位置
Node n1 = head;
Node n2 = head;
while (n2.next != null && n2.next.next != null) { // find mid node
n1 = n1.next; // n1 -> mid
n2 = n2.next.next; // n2 -> end
}
//反转后半部分链表
n2 = n1.next; // n2 -> right part first node
n1.next = null; // mid.next -> null
Node n3 = null;
while (n2 != null) { // right part convert
n3 = n2.next; // n3 -> save next node
n2.next = n1; // next of right node convert
n1 = n2; // n1 move
n2 = n3; // n2 move
}
//两端比较
n3 = n1; // n3 -> save last node
n2 = head;// n2 -> left first node
boolean res = true;
while (n1 != null && n2 != null) { // check palindrome
if (n1.value != n2.value) {
res = false;
break;
}
n1 = n1.next; // left to mid
n2 = n2.next; // right to mid
}
//把后半部分链表再恢复回来
n1 = n3.next;
n3.next = null;
while (n1 != null) { // recover list
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
题目描述
把单向链表 按某值划分成左边小,中间相等,右边大的形式
(来源左神 )
思路
使用辅助空间
把它放在数组里面 partition 然后再串回来(或者直接在原链表里面把值给改了)。
不使用辅助空间
改6个指针 分别对应less equal more
的起点和终点,然后遍历链表,把相应的节点往里面放,最后3个区首尾相连
题目描述
9 3 7 和 6 3 生成 1 0 0 0
(来源左神 )
思路
方法1. 变成2个字符串 937 63 字符串相加 得到1000 生成链表 (用字符串防止数太大,int溢出)
方法2. 两个链表逆序直接加,再把新的链表逆序,即可