【算法篇】链表专题

  前言:

  从今天开始要攻克算法专题了,今天是链表篇,关于链表相关的考题,不会太多涉及时间复杂度,而主要考察链表和指针操作;为啥大厂喜欢考察数据结构和算法?因为这些是对基本功的升华,不会考察数组指针、函数指针等,考一个链表,就能考察对指针的理解,我相信不理解指针,链表学起来很费劲!

  一、简介

  我会一个模块一个模块进行学习和练习,练习时我会从leetcode上选题,都知道leetcode吧?是OJ中最权威的平台了,在上面可以找算法题和练习,很好的一个网站,每一个题都会说明leetcode的第几题,方便大家查找和练习。

  二、反转链表

  LeetCode上第206题:Reverse Linked List,官网是英文,但鉴于英文对一些人看起来比较费劲,翻译成中文,如下:

反转单链表。
例子:
输入:1 - > 2 - > 3 - > 4 - > 5 - > NULL
输出:5 - > 4 - > 3 - > 2 - > 1 - > NULL
跟进:
链表可以迭代或递归地反转。你能实现这两个吗?

  可能有人会想到直接去改链表节点里的值,这是不允许,一般都是操作next指针,去改变指针指向;画图进行讲解,如下:

   

  需要三个指针pre/cur/next去反转,将2位置指向pre位置,pre指向1号位置,1号位置指向3号位置,这样就可以进行反转了。代码如下:

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

// 206. Reverse Linked List
// https://leetcode.com/problems/reverse-linked-list/description/
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
    ListNode* reverseList(ListNode* head) {

        ListNode* pre = NULL;
        ListNode* cur = head;
        while(cur != NULL){
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }

        return pre;
    }
};

  我是按照LeetCode的格式进行编写的,然后去LeetCode上去试一下,鉴于可能有人不知道怎么使用LeetCode,我简单进行演示一下怎么使用:

  第一步:百度leetcode,如下:

  

  

    第二步:点击“Create Account”,创建自己的用户,需要填写邮箱,需要点击链接进行激活,否则刷题无法提交;刚开始写的邮箱没给我发邮件,又在个人资料里重新换了邮箱,就可以收到了;

    第三步:在首页找题,如果能记住题目,可以输入题目进行搜索;也可以搜索题号,如206,也可以搜索到,如下图:

 

  

第四步:提交代码,我将上面写的代码放到leetcode,点击右下角的“Submit Solution”,就可以看到下面的“Submission Solution:Accepted”,就表示通过了,如下图:

  

   这样就OK了。

  三、测试程序

  这部分主要说明一下怎么去自己测试程序的运行?主要实现链表的创建、遍历、销毁(C++堆上内存要自己管理)。

  1、创建链表

  将数组传给函数,根据数组实现链表赋值;还会传入n创建多大的链表,代码如下:

// 根据n个元素的数组arr创建一个链表, 并返回链表的头
ListNode* createLinkedList(int arr[], int n){

    if(n == 0)
        return NULL;

    ListNode* head = new ListNode(arr[0]);
    ListNode* curNode = head;
    for(int i = 1 ; i < n ; i ++){
        curNode->next = new ListNode(arr[i]);
        curNode = curNode->next;
    }

    return head;
}

  注意:创建的链表,没有真实的“头结点”,就是只存一个开始指针的节点,所以删除第一个节点要注意!

  2、遍历链表

  通过头结点进行遍历链表,代码如下:

// 打印以head为头结点的链表信息内容
void printLinkedList(ListNode* head){

    ListNode* curNode = head;
    while(curNode != NULL){
        cout << curNode->val << " -> ";
        curNode = curNode->next;
    }

    cout << "NULL" << endl;

    return;
}

  3、销毁链表

  将创建时分配的内存释放,代码如下:

// 释放以head为头结点的链表空间
void deleteLinkedList(ListNode* head){

    ListNode* curNode = head;
    while(curNode != NULL){
        ListNode* delNode = curNode;
        curNode = curNode->next;
        delete delNode;
    }

    return;
}

  4、测试程序

  对反转链表代码进行测试,整体代码如下:

#include <iostream>

using namespace std;

/**
 * Definition for singly-linked list.
 */
struct ListNode {
     int val;
     ListNode *next;
     ListNode(int x) : val(x), next(NULL) {}
};

/// LinkedList 测试辅助函数

// 根据n个元素的数组arr创建一个链表, 并返回链表的头
ListNode* createLinkedList(int arr[], int n){

    if(n == 0)
        return NULL;

    ListNode* head = new ListNode(arr[0]);
    ListNode* curNode = head;
    for(int i = 1 ; i < n ; i ++){
        curNode->next = new ListNode(arr[i]);
        curNode = curNode->next;
    }

    return head;
}

// 打印以head为头结点的链表信息内容
void printLinkedList(ListNode* head){

    ListNode* curNode = head;
    while(curNode != NULL){
        cout << curNode->val << " -> ";
        curNode = curNode->next;
    }

    cout << "NULL" << endl;

    return;
}

// 释放以head为头结点的链表空间
void deleteLinkedList(ListNode* head){

    ListNode* curNode = head;
    while(curNode != NULL){
        ListNode* delNode = curNode;
        curNode = curNode->next;
        delete delNode;
    }

    return;
}


// 206. Reverse Linked List
// https://leetcode.com/problems/reverse-linked-list/description/
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
    ListNode* reverseList(ListNode* head) {

        ListNode* pre = NULL;
        ListNode* cur = head;
        while(cur != NULL){
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }

        return pre;
    }
};

int main(){

    int arr[] = {1, 2, 3, 4, 5};
    int n = sizeof(arr)/sizeof(int);

    ListNode* head = createLinkedList(arr, n);
    printLinkedList(head);

    ListNode* head2 = Solution().reverseList(head);
    printLinkedList(head2);

    deleteLinkedList(head2);

    return 0;
}
View Code

  运行结果如下:

  

  进行了反转,没有问题;

  四、删除链表元素

   1、题目

  LeetCode上第203题目:Remove Linked List Elements,题目如下:

从具有值val的整数链表中删除所有元素。
例子:
输入:1->2->6->3->4->5->6,val = 6
输出:1 - > 2 - > 3 - > 4 - > 5

  2、分析题目

  先来分析一下题目,用图来解释如下:

  

  假如删除值为4的节点,先把4的next指针保存,在3号位置指向5,这完全没有问题;但问题会发生在第一个节点位置,它没有前一个节点,那怎么办呢?在前面创建链表时也说过:没有头结点,所以使用虚拟头结点

  代码如下:

// 203. Remove Linked List Elements
// https://leetcode.com/problems/remove-linked-list-elements/description/
// 使用虚拟头结点
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {

        // 创建虚拟头结点
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;

        ListNode* cur = dummyHead;
        while(cur->next != NULL){
            if(cur->next->val == val){
                ListNode* delNode = cur->next;
                cur->next = delNode->next;
                delete delNode;
            }
            else
                cur = cur->next;
        }

        ListNode* retNode = dummyHead->next;
        delete dummyHead;

        return retNode;
    }
};

  3、测试

  测试程序也是上面的链表创建和遍历,程序如下:

#include <iostream>

using namespace std;

///Definition for singly-linked list.
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

/// LinkedList Test Helper Functions
ListNode* createLinkedList(int arr[], int n){

    if(n == 0)
        return NULL;

    ListNode* head = new ListNode(arr[0]);
    ListNode* curNode = head;
    for(int i = 1 ; i < n ; i ++){
        curNode->next = new ListNode(arr[i]);
        curNode = curNode->next;
    }

    return head;
}

void printLinkedList(ListNode* head){

    if(head == NULL){
        cout << "NULL" << endl;
        return;
    }

    ListNode* curNode = head;
    while(curNode != NULL){
        cout << curNode->val;
        if(curNode->next != NULL)
            cout << " -> ";
        curNode = curNode->next;
    }

    cout << endl;

    return;
}

void deleteLinkedList(ListNode* head){

    ListNode* curNode = head;
    while(curNode != NULL){
        ListNode* delNode = curNode;
        curNode = curNode->next;
        delete delNode;
    }

    return;
}

// 203. Remove Linked List Elements
// https://leetcode.com/problems/remove-linked-list-elements/description/
// 使用虚拟头结点
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {

        // 创建虚拟头结点
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;

        ListNode* cur = dummyHead;
        while(cur->next != NULL){
            if(cur->next->val == val){
                ListNode* delNode = cur->next;
                cur->next = delNode->next;
                delete delNode;
            }
            else
                cur = cur->next;
        }

        ListNode* retNode = dummyHead->next;
        delete dummyHead;

        return retNode;
    }
};

int main() {

    int arr[] = {1, 2, 6, 3, 4, 5, 6};
    int n = sizeof(arr) / sizeof(int);

    ListNode* head = createLinkedList(arr, n);
    printLinkedList(head);

    Solution().removeElements(head, 6);
    printLinkedList(head);

    deleteLinkedList(head);

    return 0;
}
View Code

  运行结果如下:

  

   总结:

  希望通过这篇博客,大家能对基本的链表算法题能轻松应对;欢迎点赞,不懂的欢迎随时评论!多多支持,谢谢!

  

猜你喜欢

转载自www.cnblogs.com/liudw-0215/p/9855221.html