链表操作大总结!!!


参考左神上课所讲内容,所使用语言为C++

1.单链表的基本操作

包含单链表的创建(头插法,尾插法),链表打印,删除链表结点等操作,要注意边界条件

list.h

#ifndef _LIST_H_
#define _LIST_H_

#include <iostream>
using namespace std;

typedef int ElementType;//数据类型

class Node{
    
    
public:
    Node(){
    
    next = NULL;}
    ElementType data;
    Node* next;
};

class List{
    
    
public:
    List();
    virtual ~List();

    /*链表相关操作*/
    void insertHead(ElementType data);//头插法
    void insertTail(ElementType data);//尾插法
    bool insertNext(Node* node,ElementType data);//把数据插入到结点node的后面
    bool deleteNode(Node* node);//删除结点
    
    void displayList();//从头打印链表元素
    void reverse();//反转链表
    Node* Find(int num);//返回第num个结点
    Node* Search(ElementType data);//查找数据为data的结点

    /*获取链表长度和头尾结点方法*/
    int listLengh(){
    
    return size;}
    Node* headNode(){
    
    return pHead;}
    Node* tailNode(){
    
    return pTail;}
private:
    size_t size;
    Node* pHead;//头结点指针
    Node* pTail;//尾节指针

};
#endif

list.cpp

#include "list.h"

typedef int ElementType;//数据类型

List::List(){
    
    
    size = 0;
    pHead = new Node();
    pTail = pHead;
}

List::~List(){
    
    
    while(size > 0){
    
    
        deleteNode(pHead);
    }
    delete pHead;
    pHead = NULL;
}

/*链表相关操作*/
void List::insertHead(ElementType data){
    
    
    Node* node = new Node();
    node->data = data;
    if(size == 0)
        pTail = node;
    node->next = pHead->next;
    pHead->next = node;
    size++;

}
void List::insertTail(ElementType data){
    
    
    Node* node = new Node();
    node->data = data;
    node->next = pTail->next;///
    pTail->next = node;
    pTail = pTail->next;
    size++;

}
bool List::insertNext(Node* node,ElementType data){
    
    //把数据插入到结点node的后面
    if(node == NULL)
        return false;
    Node* p = new Node();
    p->data = data;
    p->next = node->next;
    node->next = p;

    if(node == pTail)//插入的结点为尾部时,移动尾部指针
        pTail = p;
    size++;
    return true;
}

bool List::deleteNode(Node* node){
    
    //删除结点node后面的结点
    if(NULL == node || NULL == node->next)
        return false;
    Node *p = node->next;
    node->next = node->next->next;
    delete p;
    size--;
    return true;
}

void List::displayList(){
    
    //从头打印链表元素
    Node* p = pHead->next;
    while(p != NULL){
    
    
        cout << p->data << " " ;
        p = p->next;
    }
    cout << endl;

}
void List::reverse(){
    
    //反转链表
    if(size < 2)
        return;
    //pre是p的前序结点,post是p的后序结点
    Node* pre = pHead->next;
    Node* p = pre->next;
    Node* post = NULL;
    //处理尾部结点
    pTail = pre;
    pTail->next = NULL;
    while(p != NULL){
    
    
        post = p->next;
        p->next = pre;
        pre = p;
        p = post;
    }
    //处理头结点
    pHead->next = pre;

}
Node* List::Find(int num){
    
    //返回第num个结点
    if(num < 0 || num > size)
        return NULL;
    Node* p = pHead;
    for(int i = 0;i < num;i++)
        p = p->next;
    return p;
}
Node* List::Search(ElementType data){
    
    //查找数据为data的结点
    if(size > 0){
    
    
        Node* p = pHead->next;
        while(p != NULL){
    
    
            if(p->data == data)
                return p;
            p = p->next;
        }
    }
    return NULL;
}

main.cpp

#include "list.h"
#include <iostream>
using namespace std;

int main(){
    
    
    List* list = new List();
    list->insertTail(1);
    list->insertTail(2);
    list->insertTail(3);
    list->insertTail(4);
    list->insertTail(5);
    list->insertTail(6);
    list->insertHead(7);
    list->insertHead(8);
    list->insertHead(9);
    list->displayList();
    list->deleteNode(list->Find(3));
    list->displayList();
    list->reverse();
    list->displayList();
}

2. 链表相关问题

【笔试】一切为了时间复杂度,不要态在乎空间复杂度
【面试】时间复杂度依然放在第一位,但是一定要找到空间复杂度最省的方法
【技巧】

  • 额外数据结构记录
  • 快慢指针

2.1 反转单向链表

【题目】实现反转单向链表的函数
【要求】如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度为O(1)
注意是否需要返回头指针

2.1.1 反转单链表

方法一:利用有限个指针。空间复杂度O(1)
方法二:利用栈。空间复杂度O(N)

  • 时间复杂度O(N),空间复杂度O(1)
//反转单链表
Node* reverseList(Node* head){
    
    
	if(head->next == NULL || head->next->next == NULL)//链表为空或只有一个数
		return NULL;
    Node* ret = head;//用来保存头结点,如果头结点是链表数据,可不要此步和最后一步
    head = head->next;
    Node* pre = NULL;
    Node* next = NULL;
    while(head != NULL){
    
    
        next = head->next;
        head->next = pre;
        pre = head;
        head = next;
    }
    ret->next = pre;//把头结点重新插到头部
    return ret;
}

方法二:利用栈

Node* reverseList2(Node* head){
    
    
	Node* p = head->next;
	stack<Node*> sta;
	while(p != NULL){
    
    
	    sta.push(p);
	    p = p->next;
	}
	
	Node* q = head;
	while(!sta.empty()){
    
    
	    q->next = sta.top();
	    q = q->next;
	    sta.pop();
	}
	q->next = NULL;//注意此处,一定要把尾指针置为NULL,否则会陷入死循环
	return head;
}

2.2 对链表进行排序

操作同数组类似

2.3 打印两个有序链表的公共部分

【题目】给定两个有序链表的头指针head1和head2,打印两个链表的公共部分
【要求】如果两个链表的长度之和为N,时间复杂度要求O(N),额外空间复杂度为O(1)
定义两个指针,谁小谁动,相同打印,类似于merge的过程
【思路】因为是有序的,遍历两个列表,结点值相等时打印,不相等时谁小谁移动

void printSameNode(Node* head1,Node* head2){
    
    
	if(head1 == NULL || head2 == NULL)
	    return ;
	Node* cur1 = head1->next;
	Node* cur2 = head2->next;
	while(cur1 != NULL && cur2 != NULL){
    
    
	    if(cur1->data < cur2->data)
	        cur1 = cur1->next;
	    else if(cur1->data > cur2->data)
	        cur2 = cur2->next;
	    else{
    
    
	        cout << cur1->data  << " ";
	        cur1 = cur1->next;
	        cur2 = cur2->next;
	    }
	}
	cout << endl;
	return ;
}

2.4 回文结构

【题目】给定一个单链表的头结点,请判断该链表是否为回文结构
【要求】如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度为O(1)
【例子】1->2->2->1 true ;1->2->1 true ;1->2->3 false
【解题思路】

    • 把链表的全部内容依次放入栈中,再依次弹出栈中元素,并与链表依次进行比较,如果都相等,那么为回文结构,否则不是
    • 时间复杂度:O(N)
    • 空间复杂度:O(N)
  • 快慢指针 + 栈
    • 快指针一次走两步,慢指针一次走一步,当快指针走到结尾时,慢指针走到一半,然后把满指针后面的部分,放到栈中
    • 时间复杂度:O(N)
    • 空间复杂度:O(N/2)
  • 有限个指针
    • 改变链表右侧区域,使整个右半区域反转,最后指向中间结点;利用指针分别从两端移动,每移动一次比较两值是否一样。最后把链表恢复成原来的样子
    • 空间复杂度O(1)

栈实现

bool isPalindrome1(Node* head){
    
    
	Node *p = head->next;
    stack<int> sta;
    while(p){
    
    //第一个循环用来把链表数据存入栈中
    	sta.push(p->data);
        p = p->next;
    }
    p = head->next;
    while(p){
    
    //第二个循环时为了对比链表数据与栈弹出的数据
    	if(p->data != sta.top())
        	return false;
        sta.pop();
        p = p->next;
    }
    return true;
}

栈+快慢指针
空间复杂度:O(N/2)

bool isPalindrome2(Node* head){
    
    
	if(head == NULL || head->next == NULL)
		return true;
	Node* right = head->next;
	Node* cur = head;
	while(cur->next != NULL && cur->next->next != NULL){
    
    
	//cout << "right->data:" << right->data << endl;
	//cout << "cur->data:" << cur->data << endl;
		right = right->next;
	cur = cur->next->next;
	}
	
	stack<Node*> sta;
	while(right != NULL){
    
    //把中点以后的部分放入栈中
		sta.push(right);
		//cout << "放入栈中的数据为:" << right->data << endl;
		//cout << sta.top()->data << endl;
		right = right->next;
	}

	while(!sta.empty()){
    
    
		//cout << sta.top()->data << endl;
		if(head->next->data != sta.top()->data){
    
    
			return false;
		}
		head = head->next;
		sta.pop();
	}
	return true;
}

有限个变量

bool isPalindrome3(Node* head){
    
    
    if(head == NULL || head->next == NULL)
        return true;
    Node* n1 = head;
    Node* n2 = head;
    //find the mid
    while(n2->next != NULL && n2->next->next != NULL){
    
    //查找中间结点
        cout << "n1->data : " << n1->data << endl;
        cout << "n2->data : " << n2->data << endl;
        n1 = n1->next;//n1指向中部
        n2 = n2->next->next;//n2指向尾部
    }
    n2 = n1->next;//n2 right  part first node
    n1->next = NULL;
    Node* n3 = NULL;
    while(n2 != NULL){
    
    //右半区反转
        n3 = n2->next;//n3 save next node
        n2->next = n1;//下一个反转结点
        n1 = n2;//n1 move
        n2 = n3;//n2 move
    }
    n3 = n1;//n3 save the last node
    n2 = head->next;//left first node
    bool res = true;
    while(n1 != NULL && n2 != NULL){
    
    
        if(n1->data != n2->data){
    
    
            res = false;
            break;
        }
        n1 = n1->next;//left to mid
        n2 = n2->next;//right to mid
    }
    n1 = n3->next;
    n3->next = NULL;
    while(n1 != NULL){
    
    //恢复列表
        n2 = n1->next;
        n1->next = n3;
        n3 = n1;
        n1 = n2;
    }
    return res;
}

2.5 将单向链表按某值划分成左边小,右边大的形式

左大右小怎么改
【题目】给定一个单链表的头结点head,结点的值类型为整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分是值小于pivot的结点,中间是对于pivot的结点,右边是大于pivot的结点
【进阶】【要求】调整后所有大于等于小于pivot的结点之间的相对顺序和调整之前一样。时间复杂度O(N),空间复杂度O(1)

【解题思路】

  • 把链表的结点都放入到数组中,再对数组进行处理
  • 利用六个指针,小于pivot的头指针和尾指针,等于pivot的头指针和尾指针,大于pivot的头指针和尾指针。依次对链表进行比较和分配,最后小于区域的尾指针连接等于区域的头指针;等于区域的尾指针连接大于区域的头指针。注意边界条件,可能没有大于pivot的数,或者没有等于、小于pivot的数
//对数组分区域
Node* listPartition(Node* head,const int pivot){
    
    
    Node* sH = NULL;
    Node* sT = NULL;
    Node* eH = NULL;
    Node* eT = NULL;
    Node* mH = NULL;
    Node* mT = NULL;
    
    Node* next = NULL;//来保存下一个节点
    while(head != NULL){
    
    
        next = head->next;//保存断开结点的下一个结点
        head->next = NULL;//把结点断开
        //测试
        //cout << "head->data: " << head->data << endl; 

        if(head->data < pivot){
    
    //虽然以head开头,但是并不影响,如果先大后小,会不会有影响
            if(sH == NULL){
    
    
                sH = head;
                sT = head;
            }else{
    
    
                sT->next = head;
                sT = head;
            }
        }else if(head->data == pivot){
    
    
            if(eH == NULL){
    
    
                eH = head;
                eT = head;
            }else{
    
    
                eT->next = head;
                eT = head; 
            }
        }else {
    
    
            if(mH == NULL){
    
    
                mH = head;
                mT = head;
            }else{
    
    
                mT->next = head;
                mT = head;  
            }
        }
        head  = next;
    }
    //small and equal reconnect
    if(sT != NULL){
    
    //如果有小于区域
        sT->next = eH;
        eT = ((eT == NULL) ? sT : eT);
        cout << "ST不为空" << endl;
        /*
        下一步,谁去连大于区域的头谁就变成eT,
        就是说看有没有等于区域,如果没有就让sT去连,有就让eT连
        */
    }
    if(eT != NULL){
    
    
        cout << "et不为空" << endl;
        eT->next = mH;
    }
    return sH != NULL ? sH : (eH != NULL ? eH : mH);
}

2.6 复制含有随机指针结点的链表

【题目】一种特殊的单链表结点类描述如下

class Node{
    
    
public:
	int data;
	Node* next;
	Node* rand;
	Node(int value){
    
    
		data = value;
	}
};

【要求】rand指针是单链表结点结构中新增的指针,rand可能指向链表中的任意一个结点,也可以指向NULL,给定一个由Node结点类型组成的无环单链表的头结点head,实现一个函数完成单链表的复制,并返回复制的新链表的头结点。

【解题思路】

  • 不考虑空间复杂度:用一个map : key(老链表结点) value(新链表结点),通过老结点的指针指向再确定新的指向

不考虑空间复杂度:



Node* copyList(Node* head){
    
    
	hashMap<Node*,Node*> map;
	Node* cur = head;
	while(cur != NULL){
    
    //把新老结点放入map中
		map.put(cur,new Node*(cur->data))
		cur = cur->next;
	}
	cur = head;
	while(cur != NULL){
    
    
		//cur:老
		//map.get(cur) 新
		map.get(cur).next = map.get(cur.next);
		map.get(cur).rand = map.get(cur.rand);
		cur = cur.next;
	}
	return map.get(head);
}

空间复杂度O(1)

Node* copyList2(Node* head){
    
    
	if(head == NULL)
		return NULL;
	Node* cur = head;
	Node* next = NULL;
	//复制结点并把它连到被复制结点的后面
	//1->1'->2->2'-3->3' .....
	while(cur != NULL){
    
    
		next = cur->next;
		cur->next = new Node*(cur->data);
		cur->next->next = next;
		cur = next;
	}
	cur = head;
	Node* curCopy = NULL;
	//处理结点的rand指向
	while(cue != NULL){
    
    
		next = cur->next->next;
		curCopy = cur->next;
		curCopy->rand = cur->rand != NULL ? cur->rand->next : NULL;//注意这个next
		cur = next;
	}
	
	//分离新老链表
	Node* res = head->next;//老结点的头结点
	cur = head;
	while(cur != NULL){
    
    
		next = cur->next->next;
		curCopy = cur->next;
		cur->next = next;
		curCopy->next = next != NULL ? next.next : NULL;
		cur = next;
	}
	return res;
}

2.7 两个链表相交问题

【题目】给定两个单链表,这两个单链表可能有环,也可能无环。其中头结点分别为head1、head2。实现一个函数,如果两个链表相交,请返回相交的第一个结点;如果不相交返回NULL;
【要求】如果两个链表长度之和为N,时间复杂度要求为O(N),额外空间复杂度为O(1)

【解题思路】

  • 第一步:判断两个链表是否有环,有环返回入环结点,无环返回NULL
    • 方法一:额外数据结构:哈希表:利用集合,如果集合中不存在这个结点,就把这个结点放入集合,如果存在则返回此结点
    • 方法二:利用快慢指针,单链表只有一个指针,如果链表有环,那么链表会走不出来
      • 快指针走两步,慢指针走一步
      • 若有环,则快指针和慢指针会在环内相遇 ,且转的圈数不会超过两圈
        • 快慢指针相遇后,快指针回到开始,满指针留在原地,之后快慢指针每次都走一步,则快慢指针会在入环结点处再次相遇;(结论)
      • 若无环,快指针会走到NULL;
  • 第二步:分情况讨论两个链表相交清空
    • 如果两个链表均无环,两个链表相交,那么从相交的部分开始都相等 ,结尾处也一定相等
      • 遍历两个链表,记录头结点、尾结点以及链表长度,然后比较尾结点,如果尾结点不相等,那么两个链表一定不相交。若相等,让长链表先走 两个链表长度之差 步。再让短链表与长链表一起走,两个链表结点相等时即为相交的第一个结点
  1. 判断链表是否有环,有环返回入环结点,无环返回NULL
Node* getLoopNode(Node* head){
    
    
	if(head == NULL || head->next == NULL || head->next->next == NULL)
		return NULL;
	Node* n1 = head->next;//慢指针先走一步
	Node* n2 = head->next->next;//快指针走两步
	while(n1 != n2){
    
    
		if(n2->next == NULL || n2->next->next == NULL){
    
    
			return NULL;
		}
		n1 = n1->next;
		n2 = n2->next;
	}
	n2 = head;//快指针重新回到头部开始走,快慢指针各走一步
	while(n1 != n2){
    
    
		n1 = n1->next;
		n2 = n2->next;
	}
	return n1;
}

2 . 分情况讨论
情况一:两个链表无环情况

Node* noLoop(Node* head1,Node* head2){
    
    
	if(head1 == NULL || head2 == NULL)
		return NULL;
	Node* cur1 = head1;
	Node* cur2 = head2;
	int n = 0;//两个链表长度的差值,一个加,一个减,最后即为差
	while(cur1->next != NULL){
    
    
		n++;
		cur1 = cur1->next;
	}
	while(cur2->next != NULL){
    
    
		n--;
		cur2 = cur2->next;
	}
	
	if(cur1 != cur2)//判断两个无环链表的尾结点是否相同,若不同说明两个链表不相交
		return NULL;
	cur1 = n > 0 ? head1 : head2;//谁长谁的头为cur1
	cur2 = cur1 == head1 ? head2 : head1;//谁短谁的头变为cur2
	n = abs(n);//取绝对值
	while(n != 0){
    
    //让长链表先走n步
		n--;
		cur1 = cur1->next;
	}
	
	while(cur1 != cur2){
    
    //同步走,相遇时停止
		cur1 = cur1->next;
		cur2 = cur2->next;
	}
	return cur1;
}

情况二:两个单链表一个有环,一个无环,则不可能相交
情况三:两个单链表都有环

  • 两个环各自独立;
  • 相交,入环结点相同;
  • 相交,入环结点不同;
//loop1,loop2分别为链表1,2的入环结点
Node* bothLoop(Node* head1,Node* loop1,Node* head2,Node* loop2){
    
    
	Node* cur1 = NULL;
	Node* cur2 = NULL;
	if(loop1 == loop2){
    
    //相交且入环结点相同,这个过程相当于求两个无环单链表相交的过程,结束结点为入环结点
		cur1 = head1;
		cur2 = head2;
		int n = 0;
		while(cur1 != loop1){
    
    //结束结点为入环结点
			n++;
			cur1 = cur1->next;
		}
		while(cur2 == loop2){
    
    
			n--;
			cur2 = cur2->next;
		}
		if(cur1 != cur2)
			return NULL;
		cur1 = n > 0 ? head1 : head2;//谁长谁的头为cur1
		cur2 = cur1 == head1 ? head2 : head1;//谁短谁的头变为cur2
		n = abs(n);//取绝对值
		while(n != 0){
    
    //让长链表先走n步
			n--;
			cur1 = cur1->next;
		}
		while(cur1 != cur2){
    
    //同步走,相遇时停止
			cur1 = cur1->next;
			cur2 = cur2->next;
		}
		return cur1;
	}else{
    
    //入环结点不同
		cur1 = loop1->next;
		while(cur1 != loop1){
    
    //在环内遍历
			if(cur1 == loop2)//环内有点等于loop2 说明两个环相交,不等于说明两个环独立
				return loop1;
			cur1 = cur1.next;
		}
		return NULL;
	}
}
//主函数调用
Node* getIntersectNode(Node* head1,Node* head2){
    
    
	if(head1 == NULL || head2 == NULL)
		return NULL;
	//得到链表的入环结点,没有环返回NULL
	Node* loop1 = getLoopNode(head1);
	Node* loop2 = getLoopNode(head2);
	//判断是否有环并处理
	//两个单链表均无环
	if(loop1 == NULL && loop2 == NULL){
    
    
		return noLoop(head1,head2);
	}
	//一个有环一个无环则不可能相交
	//两个链表均有环
	if(loop1 != NULL && loop2 != NULL){
    
    
		return bothLoop(head1,loop1,head2,loop2);
	}
	return NULL;
}

猜你喜欢

转载自blog.csdn.net/weixin_44515978/article/details/120441445