刷题(1)-链表

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/speargod/article/details/96903936

基本

0.1套路

笔试中,不用在意空间复杂度,各种辅助结构直接上,越快写出越好
面试中,追求时间复杂度O(n)空间复杂度O(1)

0.2基本操作(表示,插入,删除)

表示,插入,删除

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来到最后一个。

1. 反转链表(重要)

题目描述

输入一个链表,反转链表后,输出新链表的表头。 (来源剑指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两个指针成员交换一下就行

2. 合并两个排序的链表(重要)

题目描述

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 (来源剑指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;
			
    }
};

3. 从尾到头打印链表

题目描述

输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。 (来源剑指 6)

思路

利用栈 (简单题)

代码

class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
       stack<int> sta;
	   vector<int> res;
	   while(head){
		   sta.push(head->val);
		   head=head->next;
	   }
	   
	   while(!sta.empty()){
		   res.push_back(sta.top());
		   sta.pop();
	   }
	   return res;
    }
};

4. 链表中倒数第k个节点(重要)

题目描述

输入一个链表,输出该链表中倒数第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;i<k-1 && cur;++i){
			cur=cur->next;
		}
		//链表的长度小于k的情况
		if(!cur)
			return nullptr;
			
		//想象一下k=1 可以写出代码
		node * res = pListHead;
		while(cur->next){
			cur=cur->next;
			res=res->next;
		}
		
		return res;
    }
};

5.复杂链表的复制

题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的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<node*,node *> 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代码

  1. 遍历节点,每个节点都给往后面插一个 值一摸一样的新节点
  2. 再遍历一遍节点 这次一次两步,把每个新节点的random都给搞定
  3. 最后把链表给断开。 (注意几个边界条件)
    时间 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;
        
    }

};

6.删除链表节点

题目描述

给定单向链表的头指针和一个节点指针,在O(1)的时间内删除该节点 (来源剑指18 )

思路

因为要求是O(1),且是单向链表,所以提出一个新方法 “替换法”

  1. 如果该节点不是尾节点,那么可以直接将下一个节点的值赋给该节点,然后令该节点指向下下个节点,即删除下一个节点,时间复杂度为 O(1)。
  2. 如果是尾节点,但是整个链表,只有这一个节点,那直接删除
  3. 如果链表有多个节点,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向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;
    }
    
}

7. 删除链表中重复的节点

a)保留版本
题目描述

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点保留1个,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->3->4->5
tips:这是保留版本,and排好序了

思路

遍历节点保存上一个节点pre和当前节点cur

  1. 假如当前节点和上一节点值相同,那么就对cur执行删除操作 并且pre不前进,cur前进.
  2. 假如不同则两个都前进

代码

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,开始遍历

  1. 假如p-》next的val== p-》next-》next的val,那么开始消除,我们先记下这个值dup,删除直到p-》next的val不等于dup或者next等于nullptr了为止
  2. 假如不等,那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;
		}
    }
};

8. 单向链表相交的一系列问题(重要!!!)

两个单向链表,求第一个相交的点,实际这是3个问题!!!

  1. 怎么判断 1个链表是否有环 并找到环的入口
  2. 怎么找到2个无环单链表第一个相交的节点
  3. 怎么找到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)两个环的入口不等,但是两个链表相交, 那拓扑形状就是从一个环,分出两条线,这样,第一个相交点就是两个环入口的任何一个

9. 打印2个有序链表的公共部分

只有思路

类似于merge的过程,从各自的起点开始出发,哪个值小,就前进一步,相同就打印
(来源左神 )

10. 判断一个链表是否为回文结构

题目描述

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;
	}

11. 链表左右区间划分

题目描述

把单向链表 按某值划分成左边小,中间相等,右边大的形式
(来源左神 )

思路

使用辅助空间
把它放在数组里面 partition 然后再串回来(或者直接在原链表里面把值给改了)。
不使用辅助空间
改6个指针 分别对应less equal more
的起点和终点,然后遍历链表,把相应的节点往里面放,最后3个区首尾相连

12. 两个单链表生成1个相加链表

题目描述

9 3 7 和 6 3 生成 1 0 0 0
(来源左神 )

思路

方法1. 变成2个字符串 937 63 字符串相加 得到1000 生成链表 (用字符串防止数太大,int溢出)
方法2. 两个链表逆序直接加,再把新的链表逆序,即可

猜你喜欢

转载自blog.csdn.net/speargod/article/details/96903936