【LeetCode 笔记】链表

LeetCode 链表部分练习题

  • 前言
  • 21. 合并两个有序链表
  • 24. 两两交换链表中的节点
  • 83. 删除排序链表中的重复元素
  • 82. 删除排序链表中的重复元素 II
  • 61. 旋转链表
  • 86. 分隔链表
  • 92. 反转链表 II
  • 141. 环形链表
  • 160. 相交链表
  • 234. 回文链表
  • 328. 奇偶链表
  • 1290. 二进制链表转整数
  • 143. 重排链表
  • 148. 排序链表
  • 2. 两数相加
  • 1290. 二进制链表转整数
  • 设计LRU缓存结构


前言

(1) LeetCode 的链表默认是没有头结点的,为了方便处理,在做题的时候习惯加上头结点。其实现方法如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 // 题目所给“头结点”记为“head”
 ...
	 struct ListNode *dummy = malloc(sizeof(struct ListNode));
	 dummy->next = head;
 ...

(2) 申请链表的一个结点都需要将指针初始化,否则会出现类似错误:
执行结果截图


21. 合并两个有序链表

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

个人思路:

  • 用两个指针l1、l2分别指向两个链表,新申请一个结点mergeH作为待合并链表
  • 比较 l1->val 与 l2->val,较小者插入mergeH
  • 每次插入,被取下结点的链表指针向后移动,另一个不动,直到全部为空为止
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    if(!l1 || !l2){
        return l1 == NULL ? l2 : l1;
    }

    struct ListNode *dummy = malloc(sizeof(struct ListNode)), *p = dummy;
    dummy->next = NULL;
    while(l1 || l2){
        if(l1 == NULL){
            p->next = l2;
            break;
        }
        if(l2 == NULL){
            p->next = l1;
            break;
        }
        if(l1->val < l2->val){
            p->next = l1;
            p = p->next;
            l1 = l1->next;
        } else {
            p->next = l2;
            p = p->next;
            l2 = l2->next;
        }
    }

    return dummy->next;
}

guanpengchn 大佬的思路:

  • 标签:链表、递归
  • 这道题可以使用递归实现,新链表也不需要构造新节点,我们下面列举递归三个要素
  • 终止条件:两条链表分别名为 l1l2,当 l1 为空或 l2 为空时结束
  • 返回值:每一层调用都返回排序好的链表头
  • 本级递归内容:如果 l1val 值更小,则将 l1.next 与排序好的链表头相接,l2 同理
  • O ( m + n ) O(m+n) O(m+n) m m ml1 的长度, n n nl2 的长度
    【LeetCode 笔记】链表_第1张图片
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    if(!l1 || !l2){
        return l1 == NULL ? l2 : l1;
    }
    if(l1->val <= l2->val){
        l1->next = mergeTwoLists(l1->next, l2);
        return l1;
    } else {
        l2->next = mergeTwoLists(l1, l2->next);
        return l2;
    }
}

24. 两两交换链表中的节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
给定 1->2->3->4, 你应该返回 2->1->4->3.

思路:

  • 首先判断链表是否为空或者只有一个结点,若是,则无需操作,直接返回
  • 用两个指针L1、L2分别指向当前pre指针指向的下一个结点和下下个结点
  • 交换L1和L2
  • pre移动到交换后的最后一个结点处(下一次被交换的两个结点的前一个节点)
struct ListNode* swapPairs(struct ListNode* head){
    if(!head || !head->next)
        return head;
    struct ListNode *dummy = malloc(sizeof(struct ListNode));
    dummy->next = head;
    struct ListNode *pre = dummy;
    while(pre->next && pre->next->next){
        struct ListNode *l1 = pre->next, *l2 = pre->next->next;
        l1->next = l2->next;
        l2->next = pre->next;
        pre->next = l2;
        pre = l1;
    }
    return dummy->next;
}

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

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。


输入:1->1->2
输出:1->2


输入:1->1->2->3->3
输出:1->2->3

思路:

  • 指针p遍历链表,遇到下一个结点值与当前结点相同,即p->val == p->next->val时,就将下一个结点“删除”:p->next = p->next->next;
struct ListNode* deleteDuplicates(struct ListNode* head){
    if(!head || !head->next)
        return head;
    struct ListNode *p = head;
    while(p->next){
        if(p->val == p->next->val){
            p->next = p->next->next;
        } else {
            p = p->next;
        }
    }
    return head;
}

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

给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。


输入:1->2->3->3->4->4->5
输出:1->2->5


输入:1->1->1->2->3
输出:2->3

  • 这道题相比于83题,不同之处在于删除所有重复出现的元素,不保留。
  • pre 指向最后一个已经处理好的结点,cur 遍历后续结点
    • cur 遇到重复值 x 时,pre 不动,cur 移到最后一个重复的x处。此时令 pre->next = cur->next 即可删除值为x 的重复结点
    • 否则,precur同步移动
    • 重复上述过程,直到 pre 遍历完所有结点(处理结束)
struct ListNode* deleteDuplicates(struct ListNode* head){
    if(!head || !head->next)
        return head;

    struct ListNode *dummy = malloc(sizeof(struct ListNode)), *cur = head, *pre = dummy;
    dummy->next = head;

    while(pre && pre->next){
        cur = pre->next;
        // 如果cur到最后一位了或者当前cur所指元素没有重复值
        if(!cur->next || cur->next->val != cur->val)
            pre = cur;
        else {
            // 将cur定位到一串重复元素的最后一位
            while(cur->next && cur->next->val == cur->val)
                cur = cur->next;
            // pre->next跳过中间所有的重复元素
            pre->next = cur->next;
        }
    }

    return dummy->next;
}

61. 旋转链表

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL

思路: 这道题说是链表的循环旋转,本质上是将倒数第K个元素作为头,原来的头接到原来的尾上

  • 旋转时注意k与链表长度length的大小
  • 若k <= length,那么分割点 gap = length - k
  • 若k > length,那么分割点 gap = length - k % length
    • 总结:倒数第 k 个 = 正数第 length - k % length 个
//链表长度计算
int getLength(struct ListNode* head){
    struct ListNode* p = head;
    int len = 1;
    while(p->next){
        p = p->next;
        ++len;
    }
    return len;
}

struct ListNode* rotateRight(struct ListNode* head, int k){
    if(!head || !head->next){
        return head;
    }

    int len = getLength(head);
    int gap = len - k % len;  //gap为倒数第几个,从gap处断开,方便后续拼接
    struct ListNode *dummy = malloc(sizeof(struct ListNode)), *p = head, *q = p;
    dummy->next = head;

    for(int i = 1; i < gap; i++){
        p = p->next;	//找到倒数第k个的前一个结点,以便于修改操作
    }
    while(q->next){
        q = q->next;    // 走到表尾
    }
    q->next = dummy->next;	//表尾接到原表头
    dummy->next = p->next;	//原表头变为倒数第k个结点
    p->next = NULL;			//最后元素指空

    return dummy->next;
}

86. 分隔链表

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5

思路:

  • 使用两个链表bigsmall,分别记录结点值 val 比 x 大的和比 x 小的结点
  • 最后将big链表的末尾置空,将big链接在small的后面
struct ListNode* partition(struct ListNode* head, int x){
    struct ListNode* smallLink = malloc(sizeof(struct ListNode)), *small = smallLink;
    struct ListNode* bigLink = malloc(sizeof(struct ListNode)), *big = bigLink;
    smallLink->next = NULL; bigLink->next = NULL;

    while(head != NULL) {
        if(head->val < x){
            small->next = head;
            small = head;
        } else {
            big->next = head;
            big = head;
        }
        head = head->next;
    }
    big->next = NULL;
    small->next = bigLink->next;

    return smallLink->next;
}

92. 反转链表 II

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明: 1 ≤ m ≤ n ≤ 链表长度。
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL

思路:
这道题我想到了一个思路:

  • 用四个指针startendpreStartrearEnd分别指向第 m m m n n n m − 1 m - 1 m1 n + 1 n + 1 n+1 个结点
  • 头插法反转从m到n的结点
  • 将m到n的结点连接回原来的链表
struct ListNode* t = NULL;

struct ListNode* reverse(struct ListNode* head) {
    struct ListNode *L = malloc(sizeof(struct ListNode)), *p;

    L->next = NULL;
    
    while(head != NULL){
        p = head->next;
        head->next = L->next;
        L->next = head;
        head = p;
    }

    return L->next;
}


struct ListNode* reverseBetween(struct ListNode* head, int m, int n){
    if(!head || m > n)
        return head;

    struct ListNode *H = malloc(sizeof(struct ListNode)); //自己申请的头结点
    H->next = head;
    struct ListNode *start = H, *end = H, *preStart = start, *rearEnd = end;

    int i = 0;
    while(i < n) {
        if(i < m) {
            preStart = start;
            start = start->next;
        }
        end = end->next;
        i++;
    }
    rearEnd = end->next;
    end->next = NULL;
    t = start;
    preStart->next = reverse(start);	//反转链表m~n
    t->next = rearEnd;	//接上n之后的链表
    return H->next;
}

虽然这样做很直观,但是不管是代码量还是对运行内存来说都是比较大的。

大佬的思路:

实现思路 : 以1->2->3->4->5, m = 2, n=4 为例:
· 定位到要反转部分的头节点 2,head = 2;其前驱结点为 1,pre = 1;
· 当前节点的下一个节点 3 调整为前驱节点的下一个节点 1->3->2->4->5,
· 当前结点仍为 2, 前驱结点依然是 1,重复上一步操作…
· 1->4->3->2->5.

struct ListNode* reverseBetween(struct ListNode* head, int m, int n){
	//添加头结点
    struct ListNode* dummy = malloc(sizeof(struct ListNode));
    dummy->next = head;
    
    //pre 作为指向第 m - 1 个结点的指针
    //nxt 遍历 pre 后面第 m 到 n 个需要被交换的结点
    struct ListNode* pre = dummy, *nxt = NULL;
    
    for(int i = 1; i < m; i++) 
        pre = pre->next;
    head = pre->next;	//pre 到达目的地 m - 1,head 指向第 m 个结点
    
    //循环反转第 m 至 n 个结点
    for(int i = m; i < n; i++){
    	//交换head与head->next
        nxt = head->next;
        head->next = nxt->next;
        nxt->next = pre->next;
        pre->next = nxt;
    }
    
    return dummy->next;
}

不得不说一题多解思路的重要性,反观我的代码运行时内存消耗:7 MB,而大佬思路的内存消耗只有 5.6 MB。


141. 环形链表

给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
输入: head = [3,2,0,-4], pos = 1
输出: true
解释: 链表中有一个环,其尾部连接到第二个节点。
【LeetCode 笔记】链表_第2张图片

这道题在两年前做过,但是不会做,最后还是参考了官方答案才明白其含义
思路:

  • 使用双指针——快慢指针遍历链表
  • 用两个运动员绕操场跑步的比喻比较形象——速度不同的两个人,沿着闭合曲线路线跑步,一定能在某个时刻相遇
bool hasCycle(struct ListNode *head) {
    if(!head)
        return false;
        
    struct ListNode *slow = head, *fast = head;
    
    while(slow->next != NULL && fast->next != NULL){
        if(slow->next == fast->next->next)
            return true;	//快慢指针相遇,则存在环
            
        slow = slow->next;
        fast = fast->next->next;
        
        if(slow == NULL || fast == NULL)
            return false;
    }
    return false;
}

注:若链表是单链,该思路同样可用,如: 876. 链表的中间结点。


160. 相交链表

思路一:
王道数据结构里的思路:从链表相交处开始计算,到链表结尾,这段长度对l1和l2是相等的。我们就可以先让长链表指针多走这么几步,然后长短链表同步走,直到检测到l1 == l2。

  • 先求出l1和l2的长度
  • 较长的先遍历,直到走到l1与l2剩余长度相等
  • l1与l2同步移动指针,直到l1 == l2
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if (headA == NULL || headB == NULL) {
        return NULL;
    }
    int lenA = getLength(headA), lenB = getLength(headB);
    while (lenA > lenB) {
        headA = headA->next;
        --lenA;
    }
    while (lenB > lenA) {
        headB = headB->next;
        --lenB;
    }
    while (headA != NULL) {
        if (headA == headB) {
            return headA;
        }
        headA = headA->next;
        headB = headB->next;
    }
    return NULL;
}

int getLength(struct ListNode *head) {
    int count = 1;
    while (head != NULL) {
        head = head->next;
        count++;
    }
    return count;
}

类似思路题目:#面试题22. 链表中倒数第 k 个结点

思路二:
参考图解相交链表

  • 这个思路些许类似于双指针的环形链表
  • pA从headA出发,pB从headB出发
  • pA顺着A走,走完A后 接着走B;pB同理
  • 当pA == pB时退出循环,此时找到答案

设 A:a + c,B:b + c,其中 c 为尾部公共部分长度。将两个链表连起来,A->B和B->A,长度:a + c + (b + c) = b + c + (a + c) => (a + c + b) + c = (b + c + a) + c,
若链表AB相交,则a + c + b与b + c + a就会抵消,则会在公共处c起点相遇;
若不相交,a + b = b + a 。因此相遇处是NULL。

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(!headA || !headB)
        return NULL;
    struct ListNode *pA = headA, *pB = headB;
    while(pA != pB){
        pA = (pA != NULL ? pA->next : headB);
        pB = (pB != NULL ? pB->next : headA);
    }
    return pA;
}

234. 回文链表

思路:

  • 找到中间结点,从此处断开,前半部分记作slow,后半部分记作fast
  • 反转后半部分
  • 前后进行比较,全部相等即为回文链表
bool isPalindrome(struct ListNode* head){
    if(!head || !head->next)
        return true;
    
    struct ListNode *fast = head, *slow = head, *L = malloc(sizeof(struct ListNode));
    L->next = NULL;
    while(fast && fast->next) {
        //找中点
        slow = slow->next;
        fast = fast->next->next;

        //同时对前面进行反转(头插法)
        head->next = L->next;
        L->next = head;

        head = slow;
    }

    //slow指向后半部分头结点,head指向前半部分头结点
    if(fast)
        slow = slow->next;
    head = L->next;

    //前半部分和后半部分进行比较
    while(head){
        if(head->val != slow->val)
            return false;
        head = head->next;
        slow = slow->next;
    }
    
    return true;
}

328. 奇偶链表

题目描述:
【LeetCode 笔记】链表_第3张图片

解题思路

  • 设置两个指针分别遍历奇、偶
struct ListNode* oddEvenList(struct ListNode* head){
    if (head == NULL) return NULL;
    struct ListNode* odd = head, *even = head->next, *evenHead = even;
    while (even != NULL && even->next != NULL) {
        odd->next = even->next;
        odd = odd->next;
        even->next = odd->next;
        even = even->next;
    }
    odd->next = evenHead;
    return head;
}

1290. 二进制链表转整数

给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。
请你返回该链表所表示数字的 十进制值
输入:head = [1,0,1]
输出:5
解释:二进制数 (101) 转化为十进制数 (5)

思路:

由于链表中从高位到低位存放了数字的二进制表示,因此我们可以使用二进制转十进制的方法,在遍历一遍链表的同时得到数字的十进制值。

个人理解为模拟计算机的运算:每次算术左移 前一位乘以2,再加上当前位,即为十进制数。

int getDecimalValue(struct ListNode* head){
    struct ListNode* cur = head;
    int ans = 0;
    while (cur != NULL) {
        ans = ans * 2 + cur->val;
        cur = cur->next;
    }
    return ans;
}

143. 重排链表

给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
例如:给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.

思路:
题目的要求里不难看出返回链表的特征:

  • 从中间结点 n / 2 n / 2 n/2处分开,前半部分顺序排列,后半部分逆序排列
  • 二者交叉排列

通过观察,可以将重排链表分解为以下三个步骤:

  1. 首先重新排列后,链表的中心节点会变为最后一个节点。所以需要先找到链表的中心节点:876. 链表的中间结点
  2. 可以按照中心节点将原始链表划分为左右两个链表。
    1. 按照中心节点将原始链表划分为左右两个链表,左链表:1->2->3 右链表:4->5。
    2. 将右链表反转,就正好是重排链表交换的顺序,右链表反转:5->4。反转链表:206. 反转链表
  3. 合并两个链表,将右链表插入到左链表,即可重新排列成:1->5->2->4->3.

简而言之:

  • 先找到中间结点
  • 将后半部分以头插法逆序存放
  • 前后交叉插入新链表中并返回
//找到中间结点
struct ListNode* middleNode(struct ListNode* head){
    struct ListNode* fast = head, *slow = head;
    while(fast != NULL && fast->next != NULL){
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}

//反转链表
struct ListNode* reverseList(struct ListNode* head){
    if(!head || !head->next)
        return head;
    struct ListNode *L = malloc(sizeof(struct ListNode)), *p;
    L->next = NULL;
    while(head != NULL) {
        p = head->next;
        head->next = L->next;
        L->next = head;
        head = p;
    }
    return L->next;
}

//合并两半部分链表
void mergeList(struct ListNode* left, struct ListNode* right){
    struct ListNode *leftTemp, *rightTemp;
    while(right != NULL){
        leftTemp = left->next;
        rightTemp = right->next;

        left->next = right;
        right->next = leftTemp;

        left = leftTemp;
        right = rightTemp;
    }
}

//功能实现函数
void reorderList(struct ListNode* head){
    if(!head || !head->next)
        return head;

    struct ListNode *middle = middleNode(head), *left = head, *right = middle->next;
    middle->next = NULL;
    right = reverseList(right);
    mergeList(left, right);
}

148. 排序链表

题目描述

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

解题思路:归并

  1. 拆(对半拆成两个链表)
  2. 有序合并两个链表
class Solution {
    public ListNode sortList(ListNode head) {
        // 1、递归结束条件
        if (head == null || head.next == null) {
            return head;
        }

        // 2、找到链表中间节点并断开链表 & 递归下探
        ListNode midNode = middleNode(head);
        ListNode rightHead = midNode.next;
        midNode.next = null;

        ListNode left = sortList(head);
        ListNode right = sortList(rightHead);

        // 3、当前层业务操作(合并有序链表)
        return mergeTwoLists(left, right);
    }
    
    //  找到链表中间节点(876. 链表的中间结点)
    private ListNode middleNode(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode slow = head;
        ListNode fast = head.next.next;

        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        return slow;
    }

    // 合并两个有序链表(21. 合并两个有序链表)
    private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode sentry = new ListNode(-1);
        ListNode curr = sentry;

        while(l1 != null && l2 != null) {
            if(l1.val < l2.val) {
                curr.next = l1;
                l1 = l1.next;
            } else {
                curr.next = l2;
                l2 = l2.next;
            }

            curr = curr.next;
        }

        curr.next = l1 != null ? l1 : l2;
        return sentry.next;
    }
}

2. 两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。


输入: (2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 0 -> 8
原因: 342 + 465 = 807

解题思路

  • 定义一个int型变量sum,初始值为0,用来记录两链表在同一位置上的两数相加之和
  • 定义一个int型变量carry,初始值为0,用来记录两链表在同一位置上的两数相加之和的进位数。每一位计算的同时需要考虑上一位的进位问题,而当前位计算结束后同样需要更新进位值
  • 如果两个链表全部遍历完毕后,进位值为1,则在新链表最前方添加节点 1
  • 设置一个头结点dummy以便操作链表
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
    if(l1 == NULL)
        return l2;
    if(l2 == NULL)
        return l1;
        
    struct ListNode *dummy = malloc(sizeof(struct ListNode));
    
    dummy->next = NULL; 		//这里很重要,定义了指针以后,要把它的next赋成NULL,否则会编译出错
    
    struct ListNode *cur = dummy;
    int sum = 0, carry = 0; //sum记录当前两个数相加结果,carry记录结果的进位数
    
    while(l1 || l2){
        int x = (l1 == NULL ? 0 : l1->val);
        int y = (l2 == NULL ? 0 : l2->val);

        sum = x + y + carry;
        carry = sum / 10;	//进位记录
        sum %= 10;			//取个位数,以建立新结点
        cur->next = malloc(sizeof(struct ListNode));
        cur->next->val = sum;
        
        cur->next->next = NULL; //这里很重要,定义了指针以后,要把它的next赋成NULL,否则会编译出错
        
        cur = cur->next;

        if(l1)
            l1 = l1->next;
        if(l2)
            l2 = l2->next;
    }
    
    // 检查最高位的进位
    if(carry == 1){
        cur->next = malloc(sizeof(struct ListNode));
        cur->next->val = carry;
        cur->next->next = NULL; //这里很重要,定义了指针以后,要把它的next赋成NULL,否则会编译出错
    }
    
    return dummy->next;
    
}

遇到的问题

Line 70: Char 15: runtime error: member access within misaligned address 0xbebebebebebebebe for type ‘struct ListNode’, which requires 8 byte alignment (ListNode.c)
0xbebebebebebebebe: note: pointer points here

执行结果截图

...
//这里很重要,定义了指针以后,要把它的next赋成NULL,否则会编译出错
dummy->next = NULL;
...
cur->next->next = NULL; 
...

注: 解决方法来源于博主「Eunhyuk_Z」的原创文章 —— LeetCode练习2–【链表】两数相加(中等)

解决方法就是上面代码中提到的很重要的地方,由于结构体内本身存在next指针,而申请结构体空间后同时定义了next指针,此时next指针未指向任何空间,故在测试时可能导致上述错误,所以要定义新指针的nextNULL

类似出现的问题: member access within misaligned address 0x000000000031 for type ‘struct ListNode’, which requires 8 byte alignment
解决方法:参考博主 沧海漂游_ 的文章


1290. 二进制链表转整数

给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。
请你返回该链表所表示数字的 十进制值
示例 1:
【LeetCode 笔记】链表_第4张图片
输入: head = [1,0,1]
输出: 5
解释: 二进制数 (101) 转化为十进制数 (5)

  • 链表不为空。
  • 链表的结点总数不超过 30
  • 每个结点的值不是 0 就是 1

解题思路

采用位运算result << 1 就相当于 result * 2, result |= 1(result |= 0) 相当于 result++(不变)

注:
x & 0 = 0 (与运算,一假则假),x | 1 = 1(或运算,一真则真)

int getDecimalValue(struct ListNode* head){
    struct ListNode* cur = head;
    int ans = 0;
    while (cur != NULL) {
        ans = ans * 2 + cur->val;
        cur = cur->next;
    }
    return ans;
}

总结

关于位运算的讲解:位运算(&、|、^、~、>>、<<)
位运算的思路对于一个能转化为处理二进制数较为方便的问题有较好的作用,如八进制、十六进制、十进制等需要转为二进制计算的题目。

设计LRU缓存结构

题目描述

设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能

  • set(key, value):将记录(key, value)插入该结构
  • get(key):返回key对应的value值

[要求]

  1. set和get方法的时间复杂度为O(1)
  2. 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
  3. 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。

若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返>回-1
对于每个操作2,输出一个答案

import java.util.*;


public class Solution {
    /**
     * lru design
     * @param operators int整型二维数组 the ops
     * @param k int整型 the k
     * @return int整型一维数组
     */
    private Map<Integer, Node> map = new HashMap<>(); // map充当一张查找表,记录缓存元素
    private Node head = new Node(-1, -1);    //头指针
    private Node tail = new Node(-1, -1);    //尾指针
    private int k;    //参数k的拷贝,以便于该类中其他方法使用
    public int[] LRU (int[][] operators, int k) {
        // write code here
        this.k = k;
        head.next = tail;
        tail.pre = head;
        int len = (int)Arrays.stream(operators).filter(x -> x[0] == 2).count();
        int[] res = new int[len];
        for(int i = 0, j = 0; i < operators.length; i++){
            if(operators[i][0] == 1){    //如果是1-->set
                set(operators[i][1], operators[i][2]);
            } else {
                res[j++] = get(operators[i][1]);
            }
        }
        return res;
    }
    
    private void set(int key, int val){
        if(get(key) != -1){
            map.get(k).val = val;
        } else {
            if(map.size() == k){
                int rk = tail.pre.key;
                tail.pre.pre.next = tail;
                tail.pre = tail.pre.pre;
                map.remove(rk);
            }
            Node node = new Node(key, val);
            map.put(key, node);
            moveToHead(node);
        }
    }
    
    private int get(int key){
        if(map.containsKey(key)){
            Node node = map.get(key);
            node.pre.next = node.next;
            node.next.pre = node.pre;
            moveToHead(node);
            return node.val;
        }
        return -1;
    }
    
    private void moveToHead(Node node){
        node.next = head.next;
        head.next.pre = node;
        head.next = node;
        node.pre = head;
    }
    
    static class Node {
        int key, val;
        Node pre, next;
        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }
}

你可能感兴趣的:(LeetCode刷题,leetcode,链表)