线性表的顺序存储:连续的内存空间;
线性表的链式存储:不连续的内存空间;
链表概念
链表由一系列结点组成,每个节点包含两个域,最后一个节点的指针域为空,头结点不保存信息;
链表框架
linklist.h文件
//linklist.h:定义链表结点和方法。
#include <string>
using namespace std;
struct Info//节点存放的数据
{
string name; //姓名
int id; //学号
};
//链表定义
struct Node//节点结构体
{
Info val;//数据对象
Node *next;//节点的指针(Node -> next)
//Node(Info x):val(x),next(NULL) {}
};
class LinkList//链表类
{
public:
//构造函数
LinkList();
//在链表头部插入结点
void InsertHead(Info val);
//插入结点
void Insert(Info val,int pos);
//删除结点
void Remove(Info val);
//得到链表长度
int Length();
//链表反序
void Reverse();
//查找结点位置
int Find(Info val);
//打印链表
void Print();
//析构函数
~LinkList();
private:
Node *head;//头结点
int length;//长度
};
linklist.cpp
//linklist.cpp:链表方法的实现。
#include "stdafx.h"
#include <iostream>
#include "linklist.h"
using namespace std;
//构造函数
LinkList::LinkList() //相当于初始化函数
{
head = NULL; //头结点为空(head->val = NULL,head ->next = NULL)
length = 0;
}
//析构函数
LinkList::~LinkList()
{
Node *temp;
for(int i=0;i<length;i++)
{
temp=head; //把头结点给临时指针
head=head->next; //让下一个节点成为头结点
delete temp; //删除临时指针
}
}
//得到链表长度
int LinkList::Length()
{
return length;
}
//在链表头部插入结点
void LinkList::InsertHead(Info val)
{
Insert(val,0);
}
//插入结点
void LinkList::Insert(Info val,int pos) //插入的值和位置
{
if(pos<0)//如果插入位置小于0则返回
{
cout<<"pos must be greater than zero"<<endl;
return;
}
int index = 1;
Node *temp = head;//辅助指针变量,等于头结点
Node *node = new Node(val);//新建节点,值为val
if(pos == 0)//如果是在头结点之前插入
{
node->next = temp;//新节点指向头结点
head = node;//并且把
length++;
return;
}
while(temp!=NULL && index<pos)
{
temp=temp->next; //不断进行移动
index++;
}
if(temp == NULL)//链表的长度小于插入位置的情况!
{
cout<<"Insert failed"<<endl;
return;
}
node->next = temp->next; //要特别注意的是,先顾后面再顾前面!
temp->next = node;
length++;//维护这个长度
}
//删除结点
void LinkList::Remove(Info val)
{
int pos = Find(val);
if(pos == -1)
{
cout<<"Delete failed"<<endl;
return;
}
if(pos == 0)
{
head = head->next;
length--;
return;
}
Node *temp = head;
for (int i = 1 ; i < pos ; i++)
{
temp = temp - > next;
}
temp->next = temp->next->next;
length--;
}
//查找结点位置
int LinkList::Find(Info val)
{
Node *temp = head;
for(int i = 0; i<length ;i++)
{
if(temp->val.name == val.name && temp->val.id == val.id)//要注意,比较结构体时不能直接全部比较,里面的值要一个一个比较
{
return i;
}
temp = temp->next;
}
return -1;
//链表反序
void LinkList::Reverse()
{
if(head==NULL)
return;
Node *curNode=head,*nextNode=head->next,*temp; //定义当前节点、下一个节点、临时指针
while(nextNode!=NULL)
{
temp=nextNode->next;
nextNode->next=curNode;
curNode=nextNode;
nextNode=temp;
}
head->next=NULL;//设置链表尾
head=curNode;//设置链表头
}
//打印链表
void LinkList::Print()
{
if(head == NULL)
{
cout<<"LinkList is empty"<<endl;
return;
}
Node *temp = head;
while(temp!=NULL)
{
cout<<temp->val.name<<","<<temp->val.id<<endl;
temp=temp->next;
}
cout<<endl;
}
算法题
链表算法题技巧总结
原创6plus 最后发布于2017-07-28 21:06:53 阅读数 457 收藏
展开
1.旋转链表,遍历一遍求链表长度,将链表首尾相接再断开(61.rorate)
2.将两条排序链表连接,直接连接即可(21.mergetwolist)
3.当提示越界时,应该注意左式不为NULL,右式可为NULL,如
p->next==p->next->next
条件满足p->next!=NULL即可
或者是两次引用成员,如p->next->val,如果p->next==NULL,则这种写法越界
(83.Remove Duplicates from Sorted List,82. Remove Duplicates from Sorted List II)
4.题目没对额外空间有什么要求,一般都是新建一个结点,即new一条新链表
5.拆分链表重新组装时注意尾结点是否指向NULL(86. Partition List)
6.涉及删除元素时,新建结点应设置在头结点之前,避免只存在一个结点的情况(19. Remove Nth Node From End of List)
原文链接:https://blog.csdn.net/gcola007/article/details/76262991
设某链表中最常用的操作是在链表的尾部插入或删除元素,则选用下列( )存储方式最节省运算时间
正确答案: D
单向链表
单向循环链表
双向链表
双向循环链表
迅速从表头到表尾,单向链表于单向循环链表显然不行,双向不循环也不行,最快还是双向循环。
数组指针
#include<stdio.h>
void main() {
int a[10] = {};
printf("%d\n", a);
printf("%d\n", a+1); //与前面一个相差4
printf("%d\n", &a);
printf("%d\n", &a + 1); //与前面一个相差40
}
1.a代表数组首元素的首地址,直接打印出其地址;
2.a+1代表数组第二个元素的地址,所以上面的地址加4;
3. &a代表整个数组的首地址,和1是一样的;
4. &a+1,是上面3的地址加4*10,代表下一个数组的首地址(越界)
#include <stdio.h>int main(void){
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a+1);
int *p1 = a;
int *p2 = &a[0];
int *p3 = (int *)(&a);
if(p1 == p2){
printf("p1 == p2\n");
}else{
printf("p1 != p2\n");
}if(p1 == p3){
printf("p1 == p3\n");
}else{
printf("p1 != p3\n");
}if(p2 == p3){
printf("p2 == p3\n");
}else{
printf("p2 != p3\n");
}
printf("%d %d\n",*(a+1),*(ptr-1));
int *p4 = ++p1;
int *p5 = ++p3;
if(p4 == p5){
printf("p4 == p5\n");
}else{
printf("p4 != p5\n");
}
return 0;
}
123相等,1代表第一个元素的指针,2和1是一个意思,3数组首地址与第一个元素的首地址相等。
ptr代表下一个数组的指针,已经越界,减1也就是往前移4个字节,代表最后一个元素的手指针。
45也是相等的,因为这里(int *)为强制类型转换,在做转换之前的±1是不同的做了之后就一样了。
int a[4] = { 1, 2, 3, 4 };
cout << sizeof(a) << endl;
cout << sizeof(*a) << endl;
int *ptr = (int*)(&a + 1);
printf("%d", *(ptr - 1));
a代表数组首元素的首地址,打印长度是整个数组的长度;
*a表示指向首地址,即表示首地址的内容,所以sizeof(*a)表示首元素所占内存空间的大小;
&a为数组首地址,&a+1为越界的下一个数组地址,并进行了强制转换
ptr再减1,不会回到原数组首地址,而是减4字节,即打印最后一个数组元素
链表的合并(重要)
已知两个链表list1 和list2 内的数据都是有序(从小到大)的,请把它们合并成一个链表,保持里面的数据依然有序(从小到大),要求用递归的方法实现()。下面是定义的链表的节点:
struct Node {
int data;
Node *next;
};
typedef struct Node Node;
请写出函数Node * MergeRecursive(Node *head1, Node *head2)的实现。
Node * MergeRecursive(Node *head1, Node *head2)
{
if (head1 == NULL)
return head2;
if (head2 == NULL)
return head1;
Node *head = NULL;
if (head1->data < head2->data){
head = head1;
head->next = MergeRecursive(head1->next, head2);
}
else {
head = head2;
head->next = MergeRecursive(head1, head2->next);
}
return head;
}
链表倒序
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
ListNode* curnode = head;
ListNode* nextnode = head->next;
while (nextnode != NULL) //这里必须是这样!!
{
ListNode* temp = nextnode->next;
nextnode->next = curnode;
curnode = nodenext;
nextnode = temp;
}
//curnode->next = NULL;
head->next = NULL;//链表尾
head = curnode; //重新定义链表头
//head->next = NULL;
}
把链表的值逆序到容器中,返回容器(只是值倒序了,和上题不同)
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> result;
vector<int>::iterator iter;
ListNode* temp = head;
while(temp ->next != NULL)
{
vector.insert(result.begin(), temp->val);
temp = temp ->next;
}
return result;
}
难度不大,但是运用了两块知识,这种题,做过要比没做过快很多很多;
输出倒数第k个节点
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* head, unsigned int k) {
ListNode* temp = head;
int length = 0;
while (temp->next != NULL)
{
temp = temp->next;
length = length + 1;
}
length++;
temp = head;
for (int i = 0; i < length - k; i++)
{
temp = temp->next;
}
return temp;
}
笨办法:先算总长度,才能知道正序的序号,要注意因为是从0开始的,长度要比最后的序号大一;
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* head, unsigned int k) {
ListNode *slownode = head;
ListNode *fastnode = head;
for (int i = 0; i < k; i++)
{
fastnode = fastnode->next;
}
while (fastnode->next != NULL)
{
fastnode = fastnode->next;
slownode = slownode->next;
}
return slownode;
}
使用两个指针是一种常用的技巧,有时会时问题简化很多;
单链表是否有环(返回bool),如果有起点是哪里(返回node),环长度是多大(返回length)?(最后一个节点next不是空,而是前面某个节点)
方法一:用一个set存放每个节点地址
方法二:追击问题
设出发点到环起点的距离为a,环起点到相遇点的距离为b;
慢指针一共走了 s = a + b ;
快指针一共走了 2s = a + b + nk ;
两个解得 a + b = nk ;
把慢指针放到出发点,走a步走到了起点,快指针本来走了b步走到了起点,现在快指针又走了a步,转了n圈之后又回到了起点;所以此时快慢相遇点就是环的起点;
让一个不动,另一个再走相遇时就是长度;
判断:
bool ExistLoop(ListNode* head)
{
ListNode* slow = head;
ListNode* fast = head;
//如果无环,则fast先走到终点
//当链表长度为奇数时,fast->Next为空
//当链表长度为偶数时,fast为空
while( fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
//如果有环,则fast会超过slow一圈
if(fast == slow)
{
break;
}
}
if(fast == NULL || fast->next == NULL )
return false;
else
即使是判断也是需要两个指针,因为一个的话会造成循环,另外,fastnode是一次走两个,奇偶时尾节点判断方式不同。
Int/ListNode* EntryNodeOfLoop(ListNode* head)
{
ListNode* slownode = head;
ListNode* fastnode = head;
ListNode* meetnode;
while (fastnode != NULL && slownode != NULL)
{
slownode = slownode->next;
fastnode = fastnode->next->next;
if (fastnode == slownode)
{
meetnode = fastnode;
break;
}
}
slownode = head;
while (slownode != fastnode)
{
slownode = slownode->next;
fastnode = fastnode->next;
}
return slownode;
int length = 1;
fastnode = fastnode->next;
while (slownode != fastnode)
{
fastnode = fastnode->next;
length++;
}
return length;
}
两个单向链表,找其交点
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
//求出两个链表的长度,然后弥补长度差,一起走直到节点重合
int len1 = 0;
int len2 = 0;
ListNode* temp = head1;
while (temp->next != NULL)
{
temp = temp->next;
len1++;
}
len1++;
temp = head2;
while (temp->next != NULL)
{
temp = temp->next;
len2++;
}
len2++;
ListNode* temp2 = head2;
if (len1 > len2)
{
temp = head1;
temp2 = head2;
for (int i = 0; i < len1 - len2; i++)
{
temp = temp->next;
}
for (int i = 0; i < len2; i++)
{
temp = temp->next;
temp2 = temp2->next;
if (temp == temp2)
{
return temp;
}
}
}
else if (len1 == len2)
{
temp = head1;
temp2 = head2;
for (int i = 0; i < len2; i++)
{
temp = temp->next;
temp2 = temp2->next;
if (temp == temp2)
{
return temp;
}
}
}
else
{
temp = head1;
temp2 = head2;
for (int i = 0; i < len2 - len1; i++)
{
temp2 = temp2->next;
}
for (int i = 0; i < len1; i++)
{
temp = temp->next;
temp2 = temp2->next;
if (temp == temp2)
{
return temp;
}
}
}
}
}
正常遍历做法,写法简单
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) {
ListNode* temp1 = head1;
ListNode* temp2 = head2;
while (temp1 != NULL)
{
while (temp2 != NULL)
{
if (temp1 == temp2)
{
return temp1;
}
temp2 = temp2->next;
}
temp1 = temp1->next;
}
return NULL;
};
方法二:x,y,第一个先走x-y步,再一起走
int getLength(ListNode *head)//返回链表的长度,走一步r加1
{
int r = 0;
for (; head ;head = head -> next ,++r)
{
return r;
}
}
ListNode *getIntersectionNode (ListNode *headA, ListNode *headB)
{
int lenA = getLength(headA), lenB = getlength(headB);
if (lenA >= lenB)
{
for(int i = lenA- lenB ; i ; --i ,headA = headA -> next);
}
else
{
for(int i = lenB- len A ; i ; --i,headB = headB -> next);
}
for ( ; headA && headB && head != headB ; headA = headA -> next ,headB = headB -> next );
return (headA == head B)? headA : 0 ;//相遇有交点,否则没有
}
方法三:第一个首位相接,连成一个环,使用例三的方法
一个单链表除了next指针外还有一个random指针指向任何一个元素,请复制它。
方法1: map<旧地址,新地址>,先按照普通方式复制链表,在两个链表同时走复制random
方法2:每个纠结点后面插入一个自身的副本,
复制random指针:
一个旧节点a的副本是a->next
a->random的副本是a->random->next
新节点random指针a->next->random=a->random-next
拆分:
旧节点链表是奇数项;
新节点链表是偶数项;
链表里存放整数,给定x把比x小的节点放到>=x之前
ListNode * partition (List *head , int x)
{
ListNode *h1 = 0, *t1 = 0, *h2 = 0, *t2 = 0;//两个自链表,一个小于x,一个大于x
for (; head ;head = head ->next)
{
if (head -> val < x) //第一个开始,如果小于x
{
if(t1)//第一个链表表尾不为空,则将表尾连接到head,head成为表尾
{
t1 = t1 ->next = head;
}
else//为空
{
h1 = t1 =head;
}
}
else if (t2)
{
t2 = t2 ->next = head;
}
else
{
h1 = t2 = head;
}
}
if (t2)//如果链表二为空
{
t2 ->next = 0;
}
if (t1)//如果t1为空,将两个相连
{
t1 ->next = h2;
}
return h1 ? h1 :h2 ;
}
环形链表插值
有一个整数val,如何在节点值有序的环形链表中插入一个节点值为val的节点,并且保证这个环形单链表依然有序。
给定链表的信息,及元素的值A及对应的nxt指向的元素编号同时给定val,请构造出这个环形链表,并插入该值。
测试样例:
[1,3,4,5,7],[1,2,3,4,0],2
返回:{1,2,3,4,5,7}
ListNode* insert(vector<int> A, vector<int> nxt, int val) {
// write code here
if(A.empty())
return nullptr;
ListNode* insert_node=new ListNode(val); //新建要插入的节点
ListNode* head=new ListNode(A[0]); //以A的第一个值新建头结点
ListNode* p=head; //临时节点
for(int i=0;i<A.size()-1;i++)
{
p->next=new ListNode(A[nxt[i]]);//按照元素编号的顺序构建新链表
p=p->next;
}
if(val<A[0]) //如果要插入的比最小值还小
{
p->next=insert_node; //临时节点指向新插入的节点?
insert_node->next=head; //新插入的节点作为头节点
return insert_node;//返回插入的节点
}
p->next=head;//临时节点作为头节点
p=nullptr; //临时头节点为空指针
ListNode* q=head;//新建一个临时头节点
while(1)
{
if(val > q->val)//值比临时头节点大
{
p=q;
q=q->next;
}
else
{
p->next=insert_node;
insert_node->next=q;
break;
}
}
return head;
}
2.链表的分化
对于一个链表,我们需要用一个特定阈值完成对它的分化,使得小于等于这个值的结点移到前面,大于该值的结点在后面,同时保证两类结点内部的位置关系不变。
给定一个链表的头结点head,同时给定阈值val,请返回一个链表,使小于等于它的结点在前,大于等于它的在后,保证结点值不重复。
测试样例:
{1,4,2,5},3
{1,2,4,5}
答:先将原链表针对val值进行分化为两个链表,然后另外两个链表配接起来。
class Divide {
public:
ListNode* listDivide(ListNode* head, int val) {
if(head==nullptr||head->next==nullptr)
return head;
ListNode* cur = head;
ListNode* pre = nullptr;
ListNode* smaller = new ListNode(-1);
ListNode* bigger = new ListNode(-1);
ListNode* s_trail=smaller;
ListNode* b_trail=bigger;
while (cur != nullptr)
{
if (cur->val <= val)
{
pre->next = cur->next;//删除节点
cur->next = s_trail->next;//接到值小于num的链表中
s_trail->next = cur;
s_trail = cur;
}
if (cur->val > val)
{
pre->next = cur->next;
cur->next = b_trail->next;
b_trail->next = cur;
b_trail = cur;
}
cur = pre->next;//cur指向下一个节点
}
head->next = smaller->next;
s_trail->next = bigger->next;
b_trail->next = nullptr;
delete smaller;
delete bigger;
return head;
}
};
3.打印两个链表的公共结点
现有两个升序链表,且链表中均无重复元素。请设计一个高效的算法,打印两个链表的公共值部分。
给定两个链表的头指针headA和headB,请返回一个vector,元素为两个链表的公共部分。请保证返回数组的升序。两个链表的元素个数均小于等于500。保证一定有公共值
测试样例:
{1,2,3,4,5,6,7},{2,4,6,8,10}
返回:[2.4.6]
答:此题利用merge的思路,由于两个链表是有序的,我们从最左边开始比较,更小的一方往右走一步。如果值相等就加入vector两方一起走一步。
class Common {
public:
vector<int> findCommonParts(ListNode* headA, ListNode* headB) {
// write code here
vector<int> outv;
if (headA == NULL || headB == NULL)
return outv;
struct ListNode *pa = headA;
struct ListNode *pb = headB;
while ((pa!=NULL) && (pb!=NULL))
{
if (pa->val < pb->val) //谁小谁后移
pa = pa->next;
else if (pb->val < pa->val)
pb = pb->next;
else if (pa->val == pb->val)//相同放入数组,并同时后移
{
outv.push_back(pa->val);
pa = pa->next;
pb = pb->next;
}
}
return outv;
}
};
链表的k逆序
有一个单链表,请设计一个算法,使得每K个节点之间逆序,如果最后不够K个节点一组,则不调整最后几个节点。例如链表1->2->3->4->5->6->7->8->null,K=3这个例子。调整后为,3->2->1->6->5->4->7->8->null。因为K==3,所以每三个节点之间逆序,但其中的7,8不调整,因为只有两个节点不够一组。
给定一个单链表的头指针head,同时给定K值,返回逆序后的链表的头指针。
链表指定值清除
现在有一个单链表。链表中每个节点保存一个整数,再给定一个值val,把所有等于val的节点删掉。
给定一个单链表的头结点head,同时给定一个值val,请返回清除后的链表的头结点,保证链表中有不等于该值的其它值。请保证其他元素的相对顺序。
测试样例:
{1,2,3,4,3,2,1},2
{1,3,4,3,1}
答:就是一个简单的链表删除操作,需要注意的是如果待删除值在头部的时候需要特殊处理。
class ClearValue {
public:
ListNode* clear(ListNode* head, int val) {
// write code here
if(!head)
return nullptr;
ListNode* p=head;
ListNode* q=head;
while(q)
{
if(q==head&&q->val==val)
{
head=head->next;
q=head;
}
else{
if(q->val==val)
{
p->next=q->next;
q=q->next;
}
else
{
p=q;
q=q->next;
}
}
}
return head;
}
};
链表的回文结构
请编写一个函数,检查链表是否为回文。
给定一个链表ListNode* pHead,请返回一个bool,代表链表是否为回文。
测试样例:
{1,2,3,2,1}
返回:true
{1,2,3,2,3}
返回:false
答:当我们遇到这种逆序问题时,我们首先会想到的是“栈”这种数据结构。所以这道题也可以这样用。顺序遍历一遍链表然后入展,然后不断出栈进行匹配。如果不相等则返回false。
class Palindrome {
public:
bool isPalindrome(ListNode* pHead) {
// write code here
if(!pHead)
return false;
stack<ListNode*> sta;
ListNode* p=pHead;
while(p)
{
sta.push(p);
p=p->next;
}
p=pHead;
while(p)
{
ListNode* temp=sta.top();
sta.pop();
if(temp->val!=p->val)
return false;
p=p->next;
}
return true;
}
};
复杂链表的复制
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点)。
答:复杂链表的复制主要的问题在于定位它的那个随机节点位置时,需要从链表头开始经过O(n)时间才能找到。最终的时间复杂度为O(n²)进一步优化就是采取哈希表将每个节点的随机节点位置放入哈希表中,这样定位它就只需要O(1)的时间了。最终的时间复杂度可以到O(n)但是需要额外的O(n)的空间复杂度用来存储哈希表。还有一种方法是 先对链表的每个结点进行复制插入到插入到每个节点后面,然后将各个复制的结点的随机指向位置也进行复制一遍。最终会得到一个原链表的复制 A-A1-B-B1-C-C1 最后一步就是分化这个链表使其变为A-B-C和A1-B1-C1。
/*
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;
RandomListNode* p=pHead;
while(p)
{
RandomListNode* cloneNode=new RandomListNode(p->label);
cloneNode->next=p->next;
p->next=cloneNode;
cloneNode->random=nullptr;
p=cloneNode->next;
}
p=pHead;
while(p)
{
RandomListNode* pcloned=p->next;
if(p->random)
pcloned->random=p->random;
p=pcloned->next;
}
RandomListNode* CloneHead=nullptr;
RandomListNode* pNode=pHead;
RandomListNode* pcloneNode=nullptr;
CloneHead=pcloneNode=pHead->next;
pHead->next=CloneHead->next;
pNode=pNode->next;
while(pNode)
{
pcloneNode->next=pNode->next;
pcloneNode=pNode->next;
pNode->next=pcloneNode->next;
pNode=pNode->next;
}
return CloneHead;
}
};
链表判定是否有环
如何判断一个单链表是否有环?有环的话返回进入环的第一个节点的值,无环的话返回-1。如果链表的长度为N,请做到时间复杂度O(N),额外空间复杂度O(1)。
给定一个单链表的头结点head(注意另一个参数adjust为加密后的数据调整参数,方便数据设置,与本题求解无关),请返回所求值。
答:判定链表是否有环,我们可以使用快慢指针的技术,快指针一次走两步,慢指针一次走一步。然后共同走一遍链表,如果没有环的话,快指针会优先慢指针到达链表尾部。如果快慢指针相遇的话,则说明有环。如果要求入环节点的话,将快指针放到链表头,然后一次走一步,快慢指针继续走,相遇的地方就是入环口。由图中简单证明一下发现将快指针重置为链表头走m的长度和环的周长R减去相遇点离入环点的距离r是相等的。(m=R-r)
class ChkLoop {
public:
int chkLoop(ListNode* head, int adjust) {
if(head==NULL||head->next==NULL)
return -1;
ListNode* fast = head;
ListNode* slow = head;
while (fast != nullptr&&fast->next != nullptr)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
break;
}
if (fast == slow)
{
fast = head;
while (fast != slow)
{
fast = fast->next;
slow = slow->next;
}
return slow->val;
}
return -1;
}
};
无环单链表相交
现在有两个无环单链表,若两个链表的长度分别为m和n,请设计一个时间复杂度为O(n + m),额外空间复杂度为O(1)的算法,判断这两个链表是否相交。
给定两个链表的头结点headA和headB,请返回一个bool值,代表这两个链表是否相交。保证两个链表长度小于等于500。
答:单链表相交的话,遍历两个链表之后的末尾应该在同一个位置。
class CheckIntersect {
public:
bool chkIntersect(ListNode* headA, ListNode* headB) {
if(headA==NULL||headB==NULL)
return false;
ListNode *pa,*pb;
pa=headA;
pb=headB;
while(pa->next!=NULL)
pa=pa->next;
while(pb->next!=NULL)
pb=pb->next;
if(pa==pb)
return true;
else
return false;
}
};
有环单链表相交判断
如何判断两个有环单链表是否相交?相交的话返回第一个相交的节点,不想交的话返回空。如果两个链表长度分别为N和M,请做到时间复杂度O(N+M),额外空间复杂度O(1)。
给定两个链表的头结点head1和head2(注意,另外两个参数adjust0和adjust1用于调整数据,与本题求解无关)。请返回一个bool值代表它们是否相交
答:情况1 两个链表部分重叠长度不同,最终入环节点在同一个位置。
情况2 两个链表是分叉的,相交部分为
class ChkIntersection {
public:
ListNode* wherestart(ListNode* head)
{
if(!head)
return nullptr;
ListNode* fast=head;
ListNode* slow=head;
while(fast!=nullptr&&fast->next!=nullptr)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
break;
}
if(fast==slow)
{
fast=head;
while(fast!=slow)
{
fast=fast->next;
slow=slow->next;
}
return slow;
}
return nullptr;
}
bool chkInter(ListNode* head1, ListNode* head2, int adjust0, int adjust1) {
// write code here
if(!head1||!head2)
return false;
ListNode* h1=wherestart(head1);// 前面链表入环节点
ListNode* h2=wherestart(head2);//后面链表入环节点
if(h1==h2) //如果两个链表的入环节点在同一个位置的话
return true;
ListNode* p=h1;
do{
p=p->next;
}while(p!=h1&&p!=h2); //如果入环节点不在同一个位置的话,如果第一个入环节点绕了一圈还没遇到另一个入环节点的话
if(p==h2)
return true;
else
return false;
}
};
单链表相交判断
给定两个单链表的头节点head1和head2,如何判断两个链表是否相交?相交的话返回true,不想交的话返回false。
给定两个链表的头结点head1和head2(注意,另外两个参数adjust0和adjust1用于调整数据,与本题求解无关)。请返回一个bool值代表它们是否相交。
答:这道题就是对所有情况做一个总结:
1.两个链表都无环的相交
2.一个链表有环 一个链表无环肯定不能相交
3.一个链表有环,另一个链表也有环的相交 (两种情况)
class ChkIntersection {
public:
ListNode* chkLoop(ListNode* head) {
if(headNULL||head->nextNULL)
return NULL;
ListNode fast,slow;
fast=slow=head;
while(fast!=NULL&&fast->next!=NULL){
slow=slow->next;
fast=fast->next->next;
if(fastslow)
break;
}
if(fastslow){
fast=head;
while(fast!=slow){
fast=fast->next;
slow=slow->next;
}
return slow;
}else{
return NULL;
}
}
bool chkInter(ListNode head1, ListNode head2, int adjust0, int adjust1) {
// write code here
ListNode *start1,*start2;
start1=chkLoop(head1);
start2=chkLoop(head2);
if(start1NULL&&start2NULL){
start1=head1;
start2=head2;
while(start1->next!=NULL)
start1=start1->next;
while(start2->next!=NULL)
start2=start2->next;
if(start1start2)
return true;
else
return false;
}else if(start1!=NULL&&start2!=NULL){
if(start1start2)
return true;
ListNode *p;
p=start1;
do{
p=p->next;
}while(p!=start1&&p!=start2);
if(p==start2)
return true;
else
return false;
}else{
return false;
}
}
};
13.链表的插入排序
答:以后再写吧
14.链表的归并排序
答:链表的归并排序,在划分子序列时,由于无法随机定位到中间位置。所以需要利用到“快慢指针”的技术。一个指针一次走一步,一个指针一次走两步。当快指针走到链表尾部时,慢指针此时位置为中间位置。数组划分子序列由于可以随机定位到中间位置所以划分耗费的时间是O(1),而采用快慢指针则需要O(n),所以归并排序采用递归树分析时,每一层要多O(n)的时间。最终时间为2*nlogn。忽略常系数的话也就是O(nlogn)。
代码以后再写吧
15.链表的快速排序
答:链表的快速排序 由于单链表的物理存储特性限制,所以它只能单向遍历。所以我们之前提到过的两种partion方法,只有一种能用,那种从两边向中间靠的无法使用。
list_node* get_position(list_node* beg, list_node* end)
{
list_node* p = beg;
list_node* q = p->next;
int val = beg->num;
while (q!=end)
{
if (q->num < val)
{
swap(p->next->num, q->num);//
p = p->next;
}
q = q->next;
}
swap(p->num, beg->num);
return p;
}
void quick_sort(list_node* beg,list_node* end)
{
if (beg != end)
{
list_node* pos = get_position(beg, end);
quick_sort(beg, pos);
quick_sort(pos->next, end);
}
}
16.链表是否适合二分查找?
答:不适合 二分查找需要定位到中间点,而链表的存储特性使得它必须顺序遍历到中点。这就需要使用一种快慢指针的技术。两个指针,一个指针一次走一步,一个指针一次走两步。快指针走到尾时,慢指针此时的位置就是中间的位置。所以对链表进行二分查找时,划分一次分别需要n、n/2、n/4,n/8…1 ,加起来总重会是O(n)级别的效率。这样二分查找就不比顺序查找快了。
17.从尾到头打印链表
答:反过来打印,我们会想到有一种数据结构栈是“后进先出”的。所以我们可以直接遍历链表然后将每个节点入栈,然后不断出栈打印节点。
18.链表倒数第k个结点
答:设定两个指针,一个指针先走k步,然后两个指针一起走,先走的指针到达链表尾时,后面的指针所指的位置就是倒数第k个节点。需要注意的是要考虑链表是否有k个节点,如果不足k个节点的话,我们直接返回空节点。
ListNode* FindKthToTail(ListNode* pListHead,unsigned k)
{
if(!pListHead)
return nullptr;
ListNode* p=pListHead;
ListNode* q=nullptr;
for(int i=0;i<k-1;i++)//所以这里只要走k-1步就行
{
if(p->next!=nullptr)
p=p->next;
else
return nullptr;
}
q=pListHead;
while(p->next) //p走到尾结点 p算是倒数第1个节点
{
p=p->next;
q=q->next;
}
return q;
}
19.删除链表节点
给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点。
答:因为我们删除节点时,需要得到待删除节点前一个位置。而题目只给我们待删除节点,按正常思路肯定是无法删除的。我们可以换个方式,我们把待删除节点的下一个节点的值覆盖待删除待删除节点,然后我们删除待删除节点后面的那个节点。
这里需要考虑几种情况:1.待删除节点为尾结点 2.待删除节点为中间节点 3.链表只有一个节点
void DeleteNode(ListNode** pListHead,ListNode* pToBeDeleted)
{
if(!pListHead||!pToBeDeleted)
return;
if(pToBeDeleted->next)//待删除节点后面还有节点
{
pToBeDeleted->val=pToBeDeleted->next->val;
ListNode* temp=pToBeDelted->next;
pToBeDeleted->next=pToBeDeleted->next->next;
delete temp;
temp=nullptr;
}else if((*pListHead)==pToBeDeleted)//链表只有一个节点
{
delete pListHead;
PListHead=nullptr;
*pListHead=nullptr;
}else //待删除节点为链表尾节点
{
ListNode* p=*pListHead;
while(p->next!=pToBeDeleted)
{
p=p->next;
}
p->next=pToBeDeleted->next;
delete pToBeDeleted;
pToBeDeleted=nullptr;
}
}l
20.反转链表
答:反转链表有两种思路,第一种定义三个指针 一个指向当前节点的前一个节点,一个指向当前节点,一个指向当前节点的后一个节点。修改时只要after = current->next;current->next = before;before = current; current = after;
list_node* reverse(list_node* li)
{
if (!li || !(li->next))
return nullptr;
list_node* before = nullptr;
list_node* current = li;
list_node* after = nullptr;
while (current)
{
after = current->next;
current->next = before;
before = current;
current = after;
}
return before;
}
反转链表的另外一种思路是:既然是逆序,我们很容易想到栈这种后进先出的数据结构,首先我们将链表顺序入栈。然后依次出栈,第一个元素出栈时,让它的next指向当前的栈顶。然后再第二个元素出栈,继续next指向栈顶。这样直到栈为空,链表就是逆序的了。需要注意的是当栈为空时,之前出栈的那个节点的next就该为nul了。同时还需要将头指针改变指向。
ListNode* ReverseList(ListNode* pHead) {
if(!pHead||!(pHead->next))
return pHead;
stack<ListNode*> sta;
ListNode* h=pHead;
ListNode* tail=nullptr;
while(h)
{
sta.push(h);
tail=h;
h=h->next;
}
while(!(sta.empty()))
{
ListNode* temp=sta.top();
sta.pop();
if(!(sta.empty()))
temp->next=sta.top();
else
temp->next=nullptr;
}
pHead=tail;
return pHead;
}
21.合并两个排序的链表
答:利用merge的思路
list_node* sorted_list_merge(list_node* l1, list_node* l2)
{
if (!l1 || !l2)
{
if (!l1&&l2)
return l2;
if (!l2&&l1)
return l1;
return nullptr;
}
list_node* p1 = l1;
list_node* p2 = l2;
list_node* head =new list_node;
list_node* tail =head;
while (p1&&p2)
{
if (p1->num <= p2->num)
{
tail->next = p1;
tail =tail->next;
p1 = p1->next;
}
else
{
tail->next = p2;
tail = tail->next;
p2 = p2->next;
}
}
if (p1)
{
tail->next = p1;
}
if (p2)
{
tail->next = p2;
}
return head;
}
22.两个链表的第一个公共节点
答:两个链表第一个公共节点有两种解决方法,一种是使用“栈”,借用两个栈先将两个链表分别入栈,然后再不断出栈比较,直到遇到最后一个位置相同的节点。这种解决方法需要额外的O(max(m,n))的空间复杂度。时间复杂度为O(m+n)。
第二种方法是先算出两个链表的长度,计算出它们的长度差,然后更长的那个链表先走步数为(长度差)这么多,然后两个链表再一起走,第一个位置相同的节点就是两个链表的第一个公共节点。
unsigned GetListLength(ListNode* pHead)
{
unsigned int length=0;
ListNode* pNode=pHead;
while(pNode)
{
++length;
pNode=pNode->next;
}
return length;
}
ListNode* FindFirstCommonNode(ListNode* pHead1,ListNode* pHead2)
{
if(!pHead1||!pHead2)
return nullptr;
unsigned int length1=GetListLength(pHead1);
unsigned int length2=GetListLength(pHead2);
int lengthdif=length1-length2;
ListNode* longer=head1;
ListNode* shorter=head2;
if(length1<length2)
{
lengthdif=length2-length1;
longer=head2;
shoerter=head1;
}
for(int i=0;i<lengthdif;i++)
longer=longer->next;
while((longer!=nullptr)&&(shorter!=nullptr)&&longer!=shorter)
{
shorter=shorter->next;
longer=longer->next;
}
return longer;
}
原文链接:https://blog.csdn.net/qq_31984717/article/details/84584494