数据结构(二):链表及面试常考的算法

一、链表介绍

1、定义

链表是物理存储单元上非连续的非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域(内存空间),另一个是指向下一个结点地址的指针域。最后一个节点的指针域指向null(空指针的意思)。链表的入口节点称为链表的头结点也就是head。

// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

链表一般用于实现文件系统、哈希表和邻接表。链表包括单链表和双向链表两种类型。

2、优缺点及使用场景

优点:数据添加和删除方便。

缺点:访问比较耗费时间。

适用场景:数据量较小,需要频繁增加,删除操作的场景。

数组和链表数据结构对比列表如下:

3、增删改查

数据访问:因为数据都是分散存储的,所以想要访问数据,只能从第一个数据开始,顺着指针的指向逐一往下访问。

数据添加:将Blue的指针指向的位置变成Green,然后再把Green的指针指向Yellow。

数据删除:只要改变指针的指向就可以了,比如删除Yellow,只需把Green的指针指向的位置从Yellow变成Red即可。

虽然Yellow本身还存储在内存中,但是不管从哪里都无法访问这个数据,所以也就没有特意去删除它的必要了。今后还需要用到Yellow的存储空间时,只要用新数据覆盖掉就可以了。

4、链表的基本操作

1)InsertAtEnd--在链表的末尾插入指定元素。

2)InsertAtHead--在链接列表的开头/头部插入指定元素。

3)Delete--从链接列表中删除指定元素。

4)DeleteAtHead--删除链接列表的第一个元素。

5)Search--从链表中返回指定元素。

6)isEmpty--如果链表为空,则返回为true。

二、常考算法

1、反转链表

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL。

思路:首先定义一个cur指针,指向头节点,定义一个pre指针,初始化为null。然后用temp指针保存cur->next节点,将cur->next指向pre,完成反转。循环反转节点,直到cur指向null。

//反转链表--双指针法
linkedNode* reverseLink(linkedNode* head){
    linkedNode* temp;
    linkedNode* cur = head;
    linkedNode* pre = NULL;
    while(cur){
        temp = cur->next;
        cur->next = pre;
        pre = cur;
        cur = temp;
    }
    return pre;
}

linkedNode* reverse(linkedNode* pre, linkedNode* cur){
    if(cur == NULL) return pre;
    linkedNode* temp = cur->next;
    cur->next = pre;
    // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
    // pre = cur;
    // cur = temp;
    return reverse(cur,temp);
}

// 反转链表--递归法1 从前往后翻转指针指向
linkedNode* reverseLink2(linkedNode* head){
    // 和双指针法初始化是一样的逻辑
    // ListNode* cur = head;
    // ListNode* pre = NULL;
    return reverse(NULL, head);
}


// 反转链表--递归法2 从后往前翻转指针指向
linkedNode* reverseLink3(linkedNode* head){
    //边缘条件判断
    if(head == NULL) return NULL;
    if(head->next == NULL) return head;

    // 递归调用,翻转第二个节点开始往后的链表
    linkedNode* last = reverseLink3(head->next);
    head->next->next = head;
    head->next = NULL;
    return last;
}

 

2、检测链表中的循环

题意:输入:3->2->0->-4->2,输出:tail connects to node 2。

leecode:环形链表,主要考察两知识点。

1)判断链表是否有环。2)如果有环,如何找到这个环的入口。

思路:判断链表是否有环。可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

如果有环,如何找到环的入口。从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点

#include<iostream>
using namespace std;

struct linkNode{
    int val;
    linkNode* next;
    linkNode(int x):val(x),next(NULL){};
};

// 双指针
linkNode* detectCycle(linkNode* head){

    linkNode* fast = head;
    linkNode* slow = head;
    while(fast != NULL && fast->next != NULL){
        fast = fast->next->next;
        slow = slow->next;

        if(fast == slow){

            linkNode* index1 = fast;
            linkNode* index2 = head;
            while (index1 != index2)
            {
                index1 = index1->next;
                index2 = index2->next;
            }
            return index1;
        }
    }
    return NULL;
}

void printLink(linkNode* head){
    while(head != NULL){
        cout<<head->val<<" ";
        head = head->next;
    }
    cout<<endl;
}

int main(){
    linkNode* n1 = new linkNode(3);
    linkNode* n2 = new linkNode(2);
    n1->next = n2;
    linkNode* n3 = new linkNode(0);
    n2->next = n3;
    linkNode* n4 = new linkNode(-4);
    n3->next = n4;
    n4->next = n2;
    //循环打印链表3->2->0->-4->2->0->-4->...
    printLink(n1);
    linkNode* res = detectCycle(n1);
    cout<<"tail connects to node "<< res->val;
}

 

3、返回链表倒数第N个节点。

输入:1->2->3->4->5,n=3。输出:3。

思路:定义快慢指针slow,fast,开始slow= fast = head;然后只有fast向后走,走n步开始,fast、slow同时走;最后fast到末尾,slow就是倒数第n个。

#include<iostream>
using namespace std;

struct linkedNode{
    int val;
    linkedNode* next;
    linkedNode(){};
    linkedNode(int x):val(x),next(NULL){};
    
};

// 双指针
int returnNthfromEnd(linkedNode* head, int n){
    linkedNode* slow = head;
    linkedNode* fast = head;
    while(n-- && fast!=NULL){
        fast = fast->next;
    }
    while(fast != NULL){
        slow = slow->next;
        fast = fast->next;
    }
    return slow->val;

}

linkedNode* initLink(){
    linkedNode* dummyNode = new linkedNode(0);
    linkedNode* tmp = dummyNode;
    for(int i =0; i<5;i++){
        linkedNode* newNode = new linkedNode();
        newNode->val = i+1;
        newNode->next = NULL;
        tmp->next = newNode;
        tmp = tmp->next;
    }
    return dummyNode->next;
}

int main(){
    linkedNode* res = initLink();
    printLink(res);
    int n = 4;
    int result = returnNthfromEnd(res,n);
    cout << result;

}

4、删除排序链表中的重复项

输入:3->3->5->5->8。输出:3->5->8。

#include<iostream>
using namespace std;

struct linknode
{
    int val;
    linknode* next;
    linknode(int x): val(x), next(NULL){};
};

void printLink(linknode *Head){
    while(Head != NULL){
        cout << Head->val << " ";
        Head = Head->next;
    }
    cout << endl;
}

linknode* removeRepeatNode(linknode *head){
    if(NULL == head) return head;
    linknode * cur = head;
    while(cur->next != NULL){
        if(cur->val == cur->next->val)
            cur->next = cur->next->next;
        else cur = cur->next;
    }
    return head;
}

int main(){
    linknode *n1 = new linknode(3);
    linknode *n2 = new linknode(3);
    linknode *n3 = new linknode(5);
    linknode *n4 = new linknode(5);
    linknode *n5 = new linknode(8);
    n1->next = n2;
    n2->next = n3;
    n3->next = n4;
    n4->next = n5;
    printLink(n1);
    linknode *result = removeRepeatNode(n1);
    printLink(result);
}

猜你喜欢

转载自blog.csdn.net/bb8886/article/details/129938535
今日推荐