leetcode(链表专题)

数组模拟链表

#include<iostream>
using namespace std;

const int N = 100;
// 单链表
// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;

// 初始化
void init()
{
	head = -1;
	idx = 0;
}

// 在链表头插入一个数a
void insert(int a)
{
	//先对新结点赋值
	e[idx] = a;
	//新结点的next指针指向前一个结点的next指针的位置
	ne[idx] = head;
	//把head的next位置更新为新结点,并且idx++,因为当前idx已经使用
	head = idx++;
	//  e[idx] = a, ne[idx] = head, head = idx ++ ;
}

// 将头结点删除,需要保证头结点存在
void remove()
{
	head = ne[head];
}

​​​​​19. 删除链表的倒数第 N 个结点

思路

双指针,

第一个点可以能被删除,所以需要一个虚拟头节点。被删除的点位于倒数第n的位置,因为是单链表,即找到倒数n + 1最后,想要删除这个节点必须要保留它的前一个节点使其p->next  =  p->next->next。返回虚拟头节点的next。可以先使一个指针移动n步,然后两个指针

同时移动,第一个指针到达最后的时候,第二个指针恰好在倒数第n + 1的位置
 

code

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    auto p1 = new ListNode(-1);
    p1->next = head;
    auto p2 = p1 , p3 = p1;
    //先走n步
    while(n--)p2 = p2->next;
    //双指针同时向后移动,两个指针的距离是确定的
    while(p2->next)
    {
        p2 = p2->next;
        p3 = p3->next;
    }
    p3->next = p3->next->next;
    return p1->next;
    }
};

21. 合并两个有序链表

思路:使用递归合并。口诀 : 判空返,谁小递归谁,谁小返回谁。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
    if(list1 == nullptr)return list2;
    if(list2 == nullptr)return list1;
    if(list1->val <= list2->val)
    {
        list1->next = mergeTwoLists(list1->next,list2);
        return list1;
    }
    else
    {
        list2->next = mergeTwoLists(list1,list2->next);
        return list2;
    }   
    }
};

83. 删除排序链表中的重复元素

思路:相邻指针。使用相邻指针,对相邻两个结点进行对比,按照题意保留一个相同元素,那么就保留靠前的第一个。如果发现相邻的相同,则使用相同结点靠后的一个结点的下一个结点覆盖前面的结点,即p->next = p->next->next , 如果不同则把当前结点更新为下一个结点p = p->next

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
    if(!head)return nullptr;
    auto p1 = head;
    while(p1)
    {
        if(p1->next && p1->next->val == p1->val)p1->next = p1->next->next;
        else p1 = p1->next;
    }
    return head;
    }
};

82. 删除排序链表中的重复元素 II

思路

因为要删除所有相同的元素,所以head可能被修改,所以创建一个新的虚拟头节点dump。

需要声明一个结点p使得它为dump,一个结点q为dump->next。

如果q->val等于p-next->val的情况下,q就继续前进,直到找到一个与p->val不等的地方停下,判断当前q与p->next->next是否相等。作用就是(如果相等说明中间只有一个点),相反执行p->next = q ,删除中间的内容,最后循环完毕返回dump->next。

code

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
     auto dummy = new ListNode(-1);
        dummy->next = head;
        auto p = dummy;
        while (p->next) {
            auto q = p->next;
            while (q && q->val == p->next->val) q = q->next;
            if (p->next->next == q) p = p->next;
            else p->next = q;
        }
        return dummy->next;
    }
};



 

思路

快慢指针算法,边界判断如果(快指针的next为空)则为奇数个,如果(快指针为空)则为偶数个。

code

class Solution {
public:
	ListNode* middleNode(ListNode* head) {
		auto p1 = head, p2 = head;
		while (p1 && p1->next)
		{
			p2 = p2->next;
			p1 = p1->next->next;
		}
		return p2;

	}
};

206. 反转链表

思路

双指针算法,前后指针逐个翻转,直到最后一个节点,需要考虑边界问题。

code

//迭代法
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
    if(!head)return NULL;
    auto p1 = head, p2 = p1->next;//定义两个相邻指针
    while(p2) //当p2为空表示,当前位置为最后一个节点的后一个位置
    {
        auto p3 = p2->next; //p3存储p2的后继节点
        p2->next = p1; //后面节点指针指向前面的节点
        p1 = p2;//双指针统一向后偏移
        p2 = p3;
    } 
    head->next=NULL;//原始头节点的next直接设为空
    return p1;//因为是双指针,直接返回p2的前一个节点,即p1为反转后链表的头节点
    }
};


//递归法
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (!head || !head->next) return head;
        ListNode *tail = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return tail;
    }
};

234. 回文链表

思路

使用vector来存储链表,然后来检查其中每一个元素,是否组成回文.。

code

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        vector<int> v;
        while(head){
            v.push_back(head->val);
            head = head->next;
        }
        // 判断是否回文
        for(int i=0; i<v.size()/2; ++i){
            if(v[i] != v[v.size()-1-i]){
                return false;
            }
        }
        return true;
    }
};

141. 环形链表

思路

快慢指针,如果快指针被慢指针追上一定是环形链表。

code

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *l1,*l2;
        l1=l2=head;
        while(l1!=NULL && l2!=NULL &&l1->next !=NULL){
            l1 = l1->next->next;
            l2 = l2->next;
            if(l1 == l2)
                return true;
        }
        return false;
    }
};

160. 相交链表

思路 

1.哈希表,通过把第一个链表的每个结点存入哈希表中,再遍历第二个链表来判断是否在哈希表中存在,入股存在即为相交结点。

2.双指针,即两个指针指向两个链表,同时走,如果相交那么就一定相等;如果不相交那么就是两条指针最后都为空。如图

code

//哈希表
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode *> visited;
        ListNode *temp = headA;
        while (temp != nullptr) {
            visited.insert(temp);
            temp = temp->next;
        }
        temp = headB;
        while (temp != nullptr) {
            if (visited.count(temp)) {
                return temp;
            }
            temp = temp->next;
        }
        return nullptr;
 
    }
};

//双指针
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    if(headA == nullptr || headB == nullptr)return nullptr;
    auto p  = headA , q = headB;
    while(p != q){
        p = p? p->next : headB;
        q = q? q->next : headA;
    }
    return p;
    }
};
 

148. 排序链表

思路

(归并排序) 时间:O(nlogn),空间O(1)
自顶向下递归形式的归并排序,由于递归需要使用系统栈,递归的最大深度是 logn,所以需要额外 O(logn)的空间。
所以我们需要使用自底向上非递归形式的归并排序算法。
基本思路是这样的,总共迭代 logn 次:

第一次,将整个区间分成连续的若干段,每段长度是2:[a0,a1],[a2,a3],…[an−1,an−1], 然后将每一段内排好序,小数在前,大数在后;
第二次,将整个区间分成连续的若干段,每段长度是4:[a0,…,a3],[a4,…,a7],…[an−4,…,an−1],然后将每一段内排好序,这次排序可以利用之前的结果,相当于将左右两个有序的半区间合并,可以通过一次线性扫描来完成;
依此类推,直到每段小区间的长度大于等于 n 为止;
另外,当 n 不是2的整次幂时,每次迭代只有最后一个区间会比较特殊,长度会小一些,遍历到指针为空时需要提前结束。

时间复杂度分析:整个链表总共遍历 logn 次,每次遍历的复杂度是 O(n),所以总时间复杂度是 O(nlogn)。
空间复杂度分析:整个算法没有递归,迭代时只会使用常数个额外变量,所以额外空间复杂度是 O(1)

code

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
      int n = 0;
        for (auto p = head; p; p = p->next) n ++ ;

        for (int i = 1; i < n; i *= 2) {
            auto dummy = new ListNode(-1), cur = dummy;
            for (int j = 1; j <= n; j += i * 2) {
                auto p = head, q = p;
                for (int k = 0; k < i && q; k ++ ) q = q->next;
                auto o = q;
                for (int k = 0; k < i && o; k ++ ) o = o->next;
                int l = 0, r = 0;
                while (l < i && r < i && p && q)
                    if (p->val <= q->val) cur = cur->next = p, p = p->next, l ++ ;
                    else cur = cur->next = q, q = q->next, r ++ ;
                while (l < i && p) cur = cur->next = p, p = p->next, l ++ ;
                while (r < i && q) cur = cur->next = q, q = q->next, r ++ ;
                head = o;
            }
            cur->next = NULL;
            head = dummy->next;
        }

        return head;
    }
};

25. K 个一组翻转链表

思路

通过模拟法,模拟整个过程。需要提供双指针来维护修改某一区间的关系(例如相邻节点),

需要注意在修改节点的指针指向之前,要保存原有指向,以便利用。

解题步骤:

1.头节点可能要被改变,所以需要一个虚拟头节点

2.遍历是否够K个

3.交换K个元素,先将内部反转,然后处理连接前面部分,再处理连接后面部分

code

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
    auto dump = new ListNode(-1);
    dump->next = head;
    for(auto p = dump;;)
    {
        auto q = p;
        //计算后面是否有足够的k来支撑
        for(int i = 0; i < k && q;i++) q = q->next;
        //如果不够直接结束
        if(!q)break;
        auto a = p->next,b = a->next;
        //如果有k个节点,需要把内部反转k - 1次
        for(int i = 0; i < k -1 ;i++)
        {
            auto c = b->next;
            b->next = a;
            a = b, b = c;
        }
        auto c = p->next;
        p->next = a, c->next = b;
        p = c;
    }
    return dump->next;
    }
};

Leetcode 146. LRU 缓存机制

思路:使用双链表模拟队列,使用哈希表记录键值对

class LRUCache {
public:
    struct Node {
        int key, val;
        Node *left, *right;
        Node(int _key, int _val): key(_key), val(_val), left(NULL), right(NULL) {}
    }*L, *R;
    unordered_map<int, Node*> hash;
    int n;
 
    void remove(Node* p) {
        p->right->left = p->left;
        p->left->right = p->right;
    }
 
    void insert(Node* p) {
        p->right = L->right;
        p->left = L;
        L->right->left = p;
        L->right = p;
    }
 
    LRUCache(int capacity) {
        n = capacity;
        L = new Node(-1, -1), R = new Node(-1, -1);
        L->right = R, R->left = L;
    }
 
    int get(int key) {
        if (hash.count(key) == 0) return -1;
        auto p = hash[key];
        remove(p);
        insert(p);
        return p->val;
    }
 
    void put(int key, int value) {
        if (hash.count(key)) {
            auto p = hash[key];
            p->val = value;
            remove(p);
            insert(p);
        } else {
            if (hash.size() == n) {
                auto p = R->left;
                remove(p);
                hash.erase(p->key);
                delete p;
            }
            auto p = new Node(key, value);
            hash[key] = p;
            insert(p);
        }
    }
};
 
/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

༄yi笑奈何

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值