算法筑基(四)之链表

117 阅读3分钟

单链表的结构

一种链式存取的数据结构,单链表中的数据是以结点的形式存在,每一个结点是由数据元素和下一个结点的存储的位置组成。

image.png

单链表与数组相比的最大差别是:单链表的数据元素存放在内存空间的地址是不连续的,而数组的数据元素存放的地址在内存空间中是连续的,这也是为什么根据索引无法像数组那样直接就能查询到数据元素。

代码定义

public class ListNode {
      int val;
      ListNode next;
      ListNode() {}
      ListNode(int val) { this.val = val; }
      ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  }

链表的增删改查

插入又分为头插尾插中间插

头插法

1.创建一个新的节点对象node

2.将node节点的next指向head

3.将node赋值给head

4.size加一

public void addFirst(T t){
    Node node = new Node(t);	//节点对象
    node.next = this.head;
    this.head = node;
    this.size++;
}

尾插法

直接让尾节点的next等于新节点

public void addLast(T t){
    this.tail.next = new Node(t);
    this.size++;
}

中间插入

1.找到要插入的位置的节点preNode

2.要插入的节点node的next节点指向preNode的next节点

3.preNode的next节点指向新创建的node节点

public void add(T t,int index){
    if (index <0 || index >size){
        throw new IllegalArgumentException("index is error");
    }
    if (index == 0){
        this.addFirst(t);
        return;
    }
    Node preNode = this.head;
    //找到要插入节点的前一个节点
    for(int i = 0; i < index-1; i++){
        preNode = preNode.next;
    }
    Node node = new Node(t);
    //要插入的节点的下一个节点指向preNode节点的下一个节点
    node.next = preNode.next;
    //preNode的下一个节点指向要插入节点node
    preNode.next = node;
    this.size++;
}

链表结点的删除

删除一个结点要区分删除的节点,头节点和其他节点,

1.如果是头节点,直接将head等于head.next

2.如果是其他节点,需要将pre节点的next指向,pre.next.next

因为我们发现在删除的时候需要考虑头节点,这里面有一个小技巧,就是在头节点之前添加一个虚拟节点,最后在通过虚拟节点的next获取原来的头节点

public void removeElt(T t){
    //构造虚拟头结点,并且下一个结点指向head
    Node dummy = new Node(t,this.head);
    //声明结点指向虚拟头结点
    Node cur = dummy;
    //从虚拟头结点的下一个结点开始遍历
    while(cur.next != null){
            if(cur.next.t.equals(t)){ 
                    cur.next = cur.next.next;
                    this.size--;
            }
            else cur = cur.next;
    }
    //去除虚拟头结点
    this.head = dummy.next;
}

这个小技巧在做实际的算法题中很实用,帮助我们减少了对于头节点的空判断,使逻辑统一,接下来我们看一些实战例题,继续探索对于链表结构的应用。上面文章参考 原文链接:blog.csdn.net/weixin_3660…


高频的面试题:

一、反转链表

1.对于这道题,个人认为双指针法比较好理解

  • 定义两个指针,pre,cur
  • cur指向pre
  • cur,pre同时向后移动
  • 循环上面的步骤,直到pre达到最后的节点
  • 返回pre

动图演示:

链表反转gif.gif

代码实现:

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}

还有一种递归解法,但是鉴于我对递归的解题方法还没有系统性的思维,所以暂时不写, 这里有个老汤哥的讲解比较清晰:感兴趣可以去看看

leetcode-cn.com/problems/re…

二、合并两个有序链表

和前面合并两个有序数组类似,用双指针法,依次比较指向的节点,小的那个指针向后移动,直到其中一个走到结尾,之后将另一个的链表剩余部分全部添加到后面,也就是将当前节点直接追加到新链表的尾节点

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode prehead = new ListNode(-1);

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

        // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
        prev.next = l1 == null ? l2 : l1;

        return prehead.next;
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/merge-two-sorted-lists/solution/he-bing-liang-ge-you-xu-lian-biao-by-leetcode-solu/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。