【LeetCode刷题】Day03 链表基础Ⅰ

链表基础

1 链表分类

  • 单链表:链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。链表的入口节点称为链表的头结点也就是head。单链表

  • 双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。既可以向前查询也可以向后查询。双链表

  • 循环链表:链表首尾相连。即最后一个元素的指针指向第一个元素。(约瑟夫环问题)

2 存储方式

  • 链表在内存中可不是连续分布的,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
  • 链表是通过指针域的指针链接在内存中各个节点。

3 链表定义

C++:

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

//构造函数(初始化节点信息)使用
ListNode* head = new ListNode(5);
//等价于下面代码
ListNode* head = new ListNode();
head->val = 5;

Java:

public class ListNode {
    // 结点的值
    int val;

    // 下一个结点
    ListNode next;

    // 节点的构造函数(无参)
    public ListNode() {
    }

    // 节点的构造函数(有一个参数)
    public ListNode(int val) {
        this.val = val;
    }

    // 节点的构造函数(有两个参数)
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

Python:

class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

链表操作及性能分析

  • 删除节点:将删除节点的前一节点指针指向删除节点的下一个节点即可。(C++要释放内存)
  • 添加节点:将插入节点指针指向后一节点,再将插入节点前一节点的指针指向插入节点。
  • 查找结点:从头节点开始一个一个找。
  • 性能分析性能分析

LeetCode203-移除链表元素

题目描述:给定一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点。
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
题目链接https://leetcode.cn/problems/remove-linked-list-elements/

解题思路

  • 直接对链表进行删除操作
  • 设置虚拟头节点后进行删除操作

1 直接删除

  • 需要对头节点进行单独处理
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 删除头结点
        while (head != NULL && head->val == val) { // 注意这里不是if
            ListNode* tmp = head;
            head = head->next;
            delete tmp;
        }

        // 删除非头结点
        ListNode* cur = head;
        while (cur != NULL && cur->next!= NULL) {
            if (cur->next->val == val) {
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            } else {
                cur = cur->next;
            }
        }
        return head;
    }
};

2 设置虚拟头节点

  • 虚拟头节点的设置使得节点删除方式相同,注意返回的是虚拟头节点的next节点
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution(object):
    def removeElements(self, head, val):
        """
        :type head: ListNode
        :type val: int
        :rtype: ListNode
        """
        newHead = ListNode(next=head);
        p = newHead;
        q = newHead.next;
        while(q != None):
            if(q.val == val):
                p.next = q.next;
                q = q.next;
            else:
                q = q.next;
                p = p.next;
        return newHead.next;

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        dummy_head = ListNode(next=head) #添加一个虚拟节点
        cur = dummy_head
        while(cur.next!=None):
            if(cur.next.val == val):
                cur.next = cur.next.next #删除cur.next节点
            else:
                cur = cur.next
        return dummy_head.next

3 心得体会

简单的链表删除题目,因为太久没有接触数据结构,还自己推导了一下怎么写,最开始的时候忘记else了,所以还浪费了挺长时间。虚拟头节点的设置使得代码简便,以后多用这种方法。

LeetCode707-设计链表

题目描述:设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
在链表类中实现这些功能
get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
题目链接https://leetcode.cn/problems/design-linked-list/

解题思路

  • 链表的基本操作
  • 可以对原链表,也可以加一个虚拟头节点

虚拟头节点写法1

class Node():
    def __init__(self, val, next=None):
        self.val = val;
        self.next = next;

class MyLinkedList(object):

    def __init__(self):
        self.dummyhead = Node(val=0);

    def get(self, index):
        """
        :type index: int
        :rtype: int
        """
        p = self.dummyhead;
        count = -1;
        while(p != None):
            if (count == index):
                return p.val;
            p = p.next;
            count += 1;
        return -1;

    def addAtHead(self, val):
        """
        :type val: int
        :rtype: None
        """
        p = Node(val=val,next=self.dummyhead.next);
        self.dummyhead.next = p;

    def addAtTail(self, val):
        """
        :type val: int
        :rtype: None
        """
        q = Node(val=val);
        p = self.dummyhead;
        while(p.next != None):
            p = p.next;
        p.next = q;


    def addAtIndex(self, index, val):
        """
        :type index: int
        :type val: int
        :rtype: None
        """
        p = self.dummyhead;
        count = -1;
        q = Node(val=val);
        if(index <= 0):
            self.addAtHead(val);
            return;
        while(p != None):
            if(index > 0 and count == index -1):
                q.next = p.next;
                p.next = q;
            count += 1;
            p = p.next;


    def deleteAtIndex(self, index):
        """
        :type index: int
        :rtype: None
        """
        p = self.dummyhead;
        count = -1;
        while(p != None):
            if(count == index - 1 and p.next != None):
                p.next = p.next.next;
            p = p.next;
            count += 1;



# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)

虚拟头节点写法2

# 单链表
class Node(object):
    def __init__(self, x=0):
        self.val = x
        self.next = None

class MyLinkedList(object):

    def __init__(self):
        self.head = Node()
        self.size = 0 # 设置一个链表长度的属性,便于后续操作,注意每次增和删的时候都要更新

    def get(self, index):
        """
        :type index: int
        :rtype: int
        """
        if index < 0 or index >= self.size:
            return -1
        cur = self.head.next
        while(index):
            cur = cur.next
            index -= 1
        return cur.val

    def addAtHead(self, val):
        """
        :type val: int
        :rtype: None
        """
        new_node = Node(val)
        new_node.next = self.head.next
        self.head.next = new_node
        self.size += 1

    def addAtTail(self, val):
        """
        :type val: int
        :rtype: None
        """
        new_node = Node(val)
        cur = self.head
        while(cur.next):
            cur = cur.next
        cur.next = new_node
        self.size += 1

    def addAtIndex(self, index, val):
        """
        :type index: int
        :type val: int
        :rtype: None
        """
        if index < 0:
            self.addAtHead(val)
            return
        elif index == self.size:
            self.addAtTail(val)
            return
        elif index > self.size:
            return

        node = Node(val)
        pre = self.head
        while(index):
            pre = pre.next
            index -= 1
        node.next = pre.next
        pre.next = node
        self.size += 1
        
    def deleteAtIndex(self, index):
        """
        :type index: int
        :rtype: None
        """
        if index < 0 or index >= self.size:
            return
        pre = self.head
        while(index):
            pre = pre.next
            index -= 1
        pre.next = pre.next.next
        self.size -= 1
	
# 双链表
# 相对于单链表, Node新增了prev属性
class Node:
    
    def __init__(self, val):
        self.val = val
        self.prev = None
        self.next = None


class MyLinkedList:

    def __init__(self):
        self._head, self._tail = Node(0), Node(0)  # 虚拟节点
        self._head.next, self._tail.prev = self._tail, self._head
        self._count = 0  # 添加的节点数

    def _get_node(self, index: int) -> Node:
        # 当index小于_count//2时, 使用_head查找更快, 反之_tail更快
        if index >= self._count // 2:
            # 使用prev往前找
            node = self._tail
            for _ in range(self._count - index):
                node = node.prev
        else:
            # 使用next往后找
            node = self._head   
            for _ in range(index + 1):
                node = node.next
        return node

    def get(self, index: int) -> int:
        """
        Get the value of the index-th node in the linked list. If the index is invalid, return -1.
        """
        if 0 <= index < self._count:
            node = self._get_node(index)
            return node.val
        else:
            return -1

    def addAtHead(self, val: int) -> None:
        """
        Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
        """
        self._update(self._head, self._head.next, val)

    def addAtTail(self, val: int) -> None:
        """
        Append a node of value val to the last element of the linked list.
        """
        self._update(self._tail.prev, self._tail, val)

    def addAtIndex(self, index: int, val: int) -> None:
        """
        Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
        """
        if index < 0:
            index = 0
        elif index > self._count:
            return
        node = self._get_node(index)
        self._update(node.prev, node, val)

    def _update(self, prev: Node, next: Node, val: int) -> None:
        """
            更新节点
            :param prev: 相对于更新的前一个节点
            :param next: 相对于更新的后一个节点
            :param val:  要添加的节点值
        """
        # 计数累加
        self._count += 1
        node = Node(val)
        prev.next, next.prev = node, node
        node.prev, node.next = prev, next

    def deleteAtIndex(self, index: int) -> None:
        """
        Delete the index-th node in the linked list, if the index is valid.
        """
        if 0 <= index < self._count:
            node = self._get_node(index)
            # 计数-1
            self._count -= 1
            node.prev.next, node.next.prev = node.next, node.prev

注意:

  • 注意节点的定义方式
  • 注意链表类中属性要有链表长度size,方便后续操作
  • 注意对边界条件(index的数值,是否有空指针等)的检查

心得体会

很久没有写过链表的基本操作了,大体上知道怎么写,但是避免不了一些边界条件检查忘记了,还有对属性定义不规范等问题。要多温习,形成一套写代码的规范,避免以后再次需要调试代码才能写正确,每次写的代码还都不太一样那种现象。

LeetCode206-反转链表

题目描述:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
题目链接https://leetcode.cn/problems/reverse-linked-list/

解题思路

  • 双指针法
  • 递归法
  • 头插法

1 双指针法

  • 定义两个指针,将后指针next赋给前指针即可
  • 要加一个临时变量记录cur->next原来的指向,cur才能正常向后移动
  • 注意终止条件
#双指针
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        cur = head   
        pre = None
        while(cur!=None):
            temp = cur.next # 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur.next = pre #反转
            #更新pre、cur指针
            pre = cur
            cur = temp
        return pre

2 递归法

  • 按照双指针法的思路来对照写
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        
        def reverse(pre,cur):
            if not cur: # if cur is None
                return pre
                
            tmp = cur.next
            cur.next = pre

            return reverse(cur,tmp)
        
        return reverse(None,head)

//C++从前向后递归写法
class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur){
        if(cur == NULL) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
        // pre = cur;
        // cur = temp;
        return reverse(cur,temp);
    }
    ListNode* reverseList(ListNode* head) {
        // 和双指针法初始化是一样的逻辑
        // ListNode* cur = head;
        // ListNode* pre = NULL;
        return reverse(NULL, head);
    }

};
//c++从后向前递归写法(调试画图看懂,自己也不会写)
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        // 边缘条件判断
        if(head == NULL) return NULL;
        if (head->next == NULL) return head;
        
        // 递归调用,翻转第二个节点开始往后的链表
        ListNode *last = reverseList(head->next);
        // 翻转头节点与第二个节点的指向
        head->next->next = head;
        // 此时的 head 节点为尾节点,next 需要指向 NULL
        head->next = NULL;
        return last;
    }
}; 

3 头插法

  • 学数据结构时反转链表老师讲的头插法
  • 定义虚拟头节点,将头节点后的节点依次插入头节点前即可反转链表
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution(object):
    def reverseList(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        dummyhead = ListNode(next=head);
        if (dummyhead.next == None):
            return head;
        while(head.next != None):
            p = dummyhead;
            while(p != head.next):
                p = p.next;
            head.next = head.next.next;
            p.next = dummyhead.next;
            dummyhead.next = p;
        return dummyhead.next;

4 心得体会

看到题目一开始想用双指针,但是没有想到用临时变量存储cur->next就可以让cur往后走了,这点没想到不应该,太久没定义临时变量了,以后要多想去定义临时变量。后来想到用头插法,一开始先讲尾节点插入头节点前面,那是不对的,出来还是正序,注意应该是头节点后一个节点依次插入头节点前。
对于题解中的递归方法,理解不到位,二刷可以再体会一下。

总结

day3的到今天才写完,还是比较基础,这段时间不太舒服,一直不想学习(也可能是单纯的找理由摆烂,已经落下很多天了。计划还是每天写一个,按自己进度来吧,但是不能再落下了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值