1、链表逆序
反转一个单链表(不申请额外空间)(简单) LeetCode206
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
1、迭代
思路:从头到尾依次让后一节点的指针指向前一节点,并且让最终新返回的链表指针应该指向原链表的最后一个节点
步骤:首先声明一个新的头结点指针,再申请一个节点用来存储交换位置时的临时变量,后面便是遍历交。首先将头节点的next指针内容备份,让现在的头结点的next指针指向现在头节点的位置,再将新链表指针的头节点后移,原链表头指针指向下一个位置。
#include <iostream> //加.h会有警告 过时啦!!
#include <stdio.h>
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
ListNode* reverseList(ListNode *head) {
ListNode *new_head=NULL; //指向新链表头结点的指针
while(head){
ListNode *next=head->next; //交换位置时的临时变量
head->next=new_head;
new_head=head;
head=next;
}
return new_head;
}
};
int main()
{
ListNode a(1);
ListNode b(2);
ListNode c(3);
ListNode d(4);
ListNode e(5);
a.next=&b;
b.next=&c;
c.next=&d;
d.next=&e;
e.next=NULL;
ListNode *head=&a;
Solution solve;
head=solve.reverseList(&a);
while(head){
printf("%d\n",head->val);
head=head->next;
}
return 0;
}
输出结果:
2.递归
让新的头结点指针指向原链表的最后一个节点,然后让新的头结点的next等于上一个节点地址,最后让前一个节点的next为NULL.
class Solution {
public:
ListNode* reverseList(ListNode *head) {
if(head==NULL)
return NULL;
if(head->next==NULL)
return head;
ListNode *new_head = reverseList(head->next);
head->next->next = head;
head->next=NULL;
return new_head;
}
};//类定义结尾要打分号,因为这里理论上是可以进行类定义的
反转从位置 m 到 n 的链表 (中等) LeetCode 92
请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
思路:
C++代码:
#include <iostream> //加.h会有警告 过时啦!!
#include <stdio.h>
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
ListNode* reverseListbetween(ListNode *head, int m, int n)
{
int change_len = n-m+1; //逆序链表长度
ListNode *pre_head=NULL; //逆序部分的前一个指针
ListNode *result=head; //逆序结果,理论上来说逆序完的第一个节点应该是现在的头结点,除非从第一个节点开始逆序
while(head && --m) //找到逆序的第一个节点以及其前面的一个节点
{
pre_head = head;
head = head->next;
}
ListNode *new_head = NULL; //逆序的头结点
ListNode *modify_tail = head; // 逆序后的最后一个节点,应该是现在的逆序部分的第一个节点
while(head && change_len--) //依次交换节点顺序
{
ListNode *next = head->next;
head->next = new_head;
new_head = head;
head = next;
}
modify_tail->next = head; //逆序后的尾节点和后面的部分相连
if(pre_head) //判断是否从第一个节点开始逆序,如果不是pre_head->next直接指向new_head
{
pre_head->next = new_head;
}
else //否则result 指向new_head
{
result = new_head;
}
return result;
}
};//类定义结尾要打分号,因为这里理论上是可以进行类定义的
int main()
{
ListNode a(1);
ListNode b(2);
ListNode c(3);
ListNode d(4);
ListNode e(5);
using std::cin;
a.next=&b;
b.next=&c;
c.next=&d;
d.next=&e;
e.next=NULL;
int m,n;
cin>>m>>n;
ListNode *head=&a;
Solution solve;
head=solve.reverseListbetween(&a,m,n);
while(head){
printf("%d\n",head->val);
head=head->next;
}
return 0;
}
运行结果:
2、求两个链表的交点
(简单)LeetCode160
要求:
编写一个程序,找到两个单链表相交的起始节点。
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
方法1:
利用STL的set容器,将A链表的数据内容存进set容器中,再遍历查找B链表中是否有相同元素。若有则返回该元素值,若没有,则返回NULL。
#include <iostream>
#include <stdio.h>
#include <set>
using std::cout;
struct ListNode {
int val;
ListNode *next;
ListNode(int x) :val(x),next(NULL) {}
};
class Solution {
public:
ListNode *getintersectionNode(ListNode *headA, ListNode * headB)
{
std::set<ListNode*> node_set; //创建一个ListNode类型的set容器
while(headA) //将A链表的元素插入set容器中
{
node_set.insert(headA);
headA = headA->next;
}
while(headB)
{
if( node_set.find(headB)!= node_set.end() ) //查找B中是否有相同元素
{
return headB; //若有返回
}
headB = headB->next; //遍历B链表(不要忘了不然会有逻辑错误)
}
return NULL; //没有则返回NULL
}
};
int main()
{
ListNode a1(0);
ListNode a2(9);
ListNode a3(1);
ListNode b1(3);
ListNode c1(2);
ListNode c2(4);
a1.next = &a2;
a2.next = &a3;
a3.next = &c1;
c1.next = &c2;
b1.next = &c1;
Solution solve;
ListNode *result = solve.getintersectionNode(&a1, &b1);
int a = result->val;
cout<<"Inter section is "<< a;
return 0;
}
运行结果:
方法2:
代码如下:
int get_list_len(ListNode *head) //遍历链表计算链表长度
{
int list_len=0;
while(head)
{
list_len++;
head=head->next;
}
return list_len;
}
ListNode *modify_long_list(int long_list_len, int short_list_len,
ListNode *head)
{
int delta= long_list_len - short_list_len; //计算长短链表之差
while(head&&delta)
{
head = head->next; //将较长的链表移动到与短的链表相同长度
delta--;
}
return head;
}
class Solution {
public:
ListNode *getintersectionNode(ListNode *headA, ListNode * headB)
{
int listA_len = get_list_len(headA);
int listB_len = get_list_len(headB);//计算链表长度
if(listA_len > listB_len) //找较长链表并返回其与短链表长度相同的位置
{
headA = modify_long_list(listA_len, listB_len, headA);
}
else
{
headB = modify_long_list(listB_len, listA_len, headB);
}
while(headA && headB) //遍历A、B链表比较,若相同则返回
{
if(headA == headB)
{
return headA;
}
headA = headA->next;
headB = headB->next;
}
return NULL;
}
};
运行结果如上。
3、链表求环
1、给定一个链表,判断链表中是否有环。(简单)LeetCode141
方法1:利用set容器,将链表内容插入set中,如果set中存在相同元素则有环。
#include <iostream> //加.h会有警告 过时啦!!
#include <stdio.h>
#include <set>
using std::cout;
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head==NULL||head->next==NULL){
return false;
}
std::set<ListNode *> node_set;
while(head)
{
if( node_set.find(head) != node_set.end())
{
return true;
}
node_set.insert(head);
head=head->next;
}
return false;
}
};
int main()
{
ListNode a(1);
ListNode b(2);
ListNode c(3);
ListNode d(4);
ListNode e(5);
a.next=&b;
b.next=&c;
c.next=&d;
d.next=&e;
e.next=NULL;
ListNode *head=&a;
Solution solve;
if( solve.hasCycle(&a))
cout<<"ture\n";
else
cout<<"false\n";
return 0;
}
运行结果:
方法2:利用快慢指针,设置两个指针快指针每次走的距离是慢指针的两步,如果快慢指针相遇那么该链表则有环。
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head==NULL||head->next==NULL){
return false;
}
ListNode *fast=head;
ListNode *slow=head;
while(fast&&fast->next){
fast=fast->next->next;
slow=slow>next;
if(fast==last)
{
return true;
}
}
return false;
}
};
运行结果如上。
2、给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。(中等)LeetCode142
**方法1:**利用set容器,找到的第一个元素即为第一个节点
#include <iostream> //加.h会有警告 过时啦!!
#include <stdio.h>
#include <set>
using std::cout;
using std::endl;
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head==NULL||head->next==NULL){
return false;
}
std::set<ListNode *> node_set;
while(head)
{
if( node_set.find(head) != node_set.end())
{
return head;
}
node_set.insert(head);
head=head->next;
}
return NULL;
}
};
int main()
{
ListNode a(1);
ListNode b(2);
ListNode c(3);
ListNode d(4);
ListNode e(5);
a.next=&b;
b.next=&c;
c.next=&d;
d.next=&e;
e.next=&c;
ListNode *head=&a;
Solution solve;
ListNode *node = solve.detectCycle(&a);
if( node )
cout<<node->val<<endl;
else
cout<<"No Circle"<<endl;
return 0;
}
运行结果:
方法2:快慢指针,快慢指针相遇之后,相遇的节点到第一个节点的距离等于头结点到第一个节点的距离
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast=head;
ListNode *slow=head;
ListNode *meet=NULL;
while(fast){
fast=fast->next;
slow=slow->next;
if(!fast){
return NULL;
}
fast=fast->next;
if(fast==slow){
meet=fast;
break;
}
}
if(meet==NULL){
return NULL;
}
while(head&&meet)
{
if(meet==head){
return meet;
}
head=head->next;
meet=meet->next;
}
return NULL;
}
};
运行结果如上。
4、链表划分
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。(中等) LeetCode 86.
**思路:**设置两个头节点分别存储较小的元素和大于等于的元素,再将两个链表连起来。
#include <iostream>
#include <stdint.h>
using std::cout;
struct ListNode{
int val;
ListNode *next;
ListNode(int x): val(x), next(NULL){
}
};
class Solution{
public:
ListNode *PartitionList(ListNode *head,int x){
ListNode less_head(0);
ListNode more_head(0); //设置两个临时头节点
ListNode *less_pre=&less_head;
ListNode *more_pre=&more_head; //设置两个指针分别指向两个头节点
while(head){
if(head->val < x){
less_pre->next = head; //如果链表中的值小于X那么将该节点插到less头结点后面
//less_pre = less_pre->next;
less_pre = head; //插入完成后让less头结点指针指向当前节点
}
else{ //大于等的情况同理
more_pre->next = head;
more_pre = head;
}
head = head->next; //遍历所有节点
}
less_pre->next = more_head.next; //让less头节点指针中的next等于 more投节点的next,让两个链表连起来
more_pre->next = NULL; //more尾部指向NULL
return less_head.next; //返回less头结点中next存的指针(链表第一个节点)
}
};
int main()
{
ListNode a(1);
ListNode b(4);
ListNode c(3);
ListNode d(2);
ListNode e(5);
ListNode f(2);
a.next=&b;
b.next=&c;
c.next=&d;
d.next=&e;
e.next=&f;
f.next=NULL;
ListNode *head=&a;
Solution solve;
ListNode *node = solve.PartitionList(&a,3);
while(node)
{
cout<<node->val<<'\n';
node=node->next;
}
return 0;
}
运行结果:
5、复杂链表的深度拷贝
已知一个复杂链表,节点中有一个指向本链表任意某个节点的随机指针(也可以为空),求这个链表的深度拷贝。
(中等 LeetCode138)
深度拷贝与浅度拷贝的区别主要在于有没有为拷贝出的新对象在堆中重新分配一块内存区域。
浅度拷贝即直接赋值,拷贝的只是原始对象的引用地址,在堆中仍然共用一块内存。
而深度拷贝为新对象在堆中重新分配一块内存,所以对新对象的操作不会影响原始对象。
思路:利用map容器中的键、值关系的对应关系,遍历链表将链表地址和节点位置存进map中。再根据其对应关系让新的链表的next、random指针连接起来。
#include <iostream>
#include <map>
#include <vector>
using namespace std;
struct RandomListNode {
int label;
RandomListNode *next,*random;
RandomListNode(int x): label(x),next(NULL),random(NULL){}
};//结构体或者类类型之后忘记加分号会出现错误39: error: multiple types in one declaration
class Solution {
public:
RandomListNode *copyRandomList(RandomListNode *head){
std::map<RandomListNode *, int> node_map;//新建一个键类型为RandomListNode,值类型为int的map容器
std::vector<RandomListNode *> node_vec; //新建一个RandomListNode类型的vector容器
RandomListNode *ptr = head; //指向链表头部
int i=0;
while(ptr){
node_vec.push_back(new RandomListNode(ptr->label));//在链表值push进vector中
node_map[ptr] = i; //记录原始链表的地址和节点位置到node_map中
ptr = ptr->next; //遍历原始链表
i++; //记录节点位置
}
node_vec.push_back(0);
ptr = head;
i=0;
while(ptr){ //再次遍历链表,连接新链表的next、random指针
node_vec[i]->next = node_vec[i+1]; //连接链表的next指针
if(ptr->random){ //连接random指针
int id = node_map[ptr->random]; //根据map获取id记录节点位置
node_vec[i]->random = node_vec[id];// random连接
}
ptr = ptr->next;
i++;
}
return node_vec[0];//返回vector首地址
}
};
int main()
{
RandomListNode a1(0);
RandomListNode a2(9);
RandomListNode a3(1);
RandomListNode a4(3);
RandomListNode a5(2);
RandomListNode a6(4);
a1.next = &a2;
a2.next = &a3;
a3.next = &a4;
a4.next = &a5;
a1.random = &a3;
a2.random = &a4;
a3.random = &a1;
Solution solve;
RandomListNode *head = solve.copyRandomList(&a1);
while(head){
cout<<head->label<<" ";
if(head->random)
cout<<head->random->label<<'\n';
else
cout<<"NULL"<<'\n';
head = head->next;
}
return 0;
}
运行结果:
5、排序链表的合并
1、将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
(简单 LeetCode 21)
思路:设置临时头结点,比较两个链表的大小将较小的节点接在临时节点后。
#include <iostream>
using namespace std;
struct ListNode{
int val;
ListNode *next;
ListNode(int x):val(x),next(NULL){
}
};
class Solution{
public:
ListNode *mergeTwoList(ListNode *headA, ListNode *headB){
ListNode head(0); //临时头结点
ListNode *ptr = &head;//头结点指针
while(headA&&headB){
if(headA->val < headB->val){ //比较两个链表中的数值大小
ptr->next = headA; //ptr指向小的链表
headA = headA->next;
}
else{
ptr->next = headB;
headB = headB->next;
}
ptr = ptr->next; //指向新的节点
}
if(headA){
ptr->next=headA; //若headA有剩余则将其接在临时链表后
}
if(headB){ //headB同理
ptr->next=headB;
}
return head.next; //返回head next的地址
}
};
int main()
{
ListNode a1(1);
ListNode a2(2);
ListNode a3(4);
ListNode b1(1);
ListNode b2(3);
ListNode b3(4);
a1.next = &a2;
a2.next = &a3;
b1.next = &b2;
b2.next = &b3;
Solution solve;
ListNode *head_node = solve.mergeTwoList(&a1, &b1);
while(head_node){
cout<<head_node->val<<" ";
head_node = head_node->next;
}
return 0;
}
运行结果
2、合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。(LeetCode 23 困难)
方法1:排序后相连,将k*n个节点放入vector中,再将vector排序,再将节点顺序相连。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct ListNode{
int val;
ListNode *next;
ListNode(int x):val(x),next(NULL){
}
};
bool cmp(const ListNode *a,const ListNode *b){
return a->val < b->val;
}
int main()
{
ListNode a1(1);
ListNode b1(1);
ListNode c1(5);
std::vector<ListNode *> node_vec;
node_vec.push_back(&a1);
node_vec.push_back(&b1);
node_vec.push_back(&c1);
std::sort(node_vec.begin(), node_vec.end(), cmp);//调用排序函数进行排序
for(int i=0;i<node_vec.size();i++){
cout<<node_vec[i]->val<<" ";
}
return 0;
}
运行结果:
算法复杂度分析:假设有k个链表,每个链表n个节点,时间复杂度:knlogkn+kn=O(knlogkn)
方法2:分治相连
#include <iostream>
#include <vector>
using namespace std;
struct ListNode{
int val;
ListNode *next;
ListNode(int x):val(x),next(NULL){
}
};
class Solution{
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size()==0){ //若链表为空则返回NULL
return NULL;
}
if(lists.size()==1){ //若只有一个链表则返回该链表
return lists[0];
}
if(lists.size()==2){ //若为两个则按两个排序
return mergeTwoList(lists[0], lists[1]);
}
int mid = lists.size()/2;//记录链表中间位置
std::vector<ListNode*> sub1_lists; //容器1存子链表1
std::vector<ListNode*> sub2_lists; //容器2存子链表2
for(int i = 0;i < mid; i++){ //将链表前半部分存进容器1中
sub1_lists.push_back(lists[i]);
}
for(int i = mid;i < lists.size(); i++){//将链表后半部分存进容器2中
sub2_lists.push_back(lists[i]);
}
ListNode *l1 = mergeKLists(sub1_lists); //再分别对两个子链表进行分治
ListNode *l2 = mergeKLists(sub2_lists);
return mergeTwoList(l1, l2); //将最后两个链表排序
}
ListNode *mergeTwoList(ListNode *headA, ListNode *headB){
ListNode head(0); //临时头结点
ListNode *ptr = &head;//头结点指针
while(headA&&headB){
if(headA->val < headB->val){ //比较两个链表中的数值大小
ptr->next = headA; //ptr指向小的链表
headA = headA->next;
}
else{
ptr->next = headB;
headB = headB->next;
}
ptr = ptr->next; //指向新的节点
}
if(headA){
ptr->next=headA; //若headA有剩余则将其接在临时链表后
}
if(headB){ //headB同理
ptr->next=headB;
}
return head.next; //返回head next的地址
}
};
int main()
{
ListNode a1(1);
ListNode a2(2);
ListNode a3(4);
ListNode b1(1);
ListNode b2(3);
ListNode b3(4);
ListNode c1(2);
ListNode c2(6);
a1.next = &a2;
a2.next = &a3;
b1.next = &b2;
b2.next = &b3;
c1.next = &c2;
Solution solve;
std::vector<ListNode *> lists;
lists.push_back(&a1);
lists.push_back(&b1);
lists.push_back(&c1);
ListNode *head_node = solve.mergeKLists(lists);
while(head_node){
cout<<head_node->val<<" ";
head_node = head_node->next;
}
return 0;
}
*算法复杂度分析:假设有k个链表,每个链表n个节点,时间复杂度:
第一轮,进行k/2次,每次2n个数字;
第二轮,进行k/4次,每次4n个数字;
:
:
最后一轮,进行(k/2^logk)次, 每次处理(2^logkn)个值
相加等于nk+nk+ +nk=O(knlogk)