数据结构与算法-链表

770 阅读5分钟

1,链表

  • 动态数组有一个明显的缺点,就是造成内存空间的浪费
  • 那么要使用多少内存空间就开辟多少内存空间,我们就是可以使用 链表
  • 链表 :是链式存储的一种线性表。所有元素的内存地址不一定是连续的
  • 链表存储会创建一个一个node,其中node包含(元素element,指向下一个node的指针next)
  • 链表有 头节点尾节点 ,尾节点在最后指向null

2,链表的设计

  • 创建LinkedList的类来管理链表数据,size用来管理链表的内存空间大小,first用来指向下一个节点

  • node节点包含:element元素,next指向下一个节点

    public class LinkedList { private int size; private Node first; // 私有类, 链表中的节点 private class Node { E element; Node next; // 构造方法 public Node(E element, Node next) { this.element = element; this.next = next; } } }

3,接口的设计

3.1,接口设计列表

    /**     
    * 清除所有元素     
    */    
    void clear();
    /**     
    * 元素的数量     
    */    
    int size();
    /**     
    * 是否包含某个元素     
    * @param element     
    * @return     
    */    
    boolean contains(E element);
    /**     
    * 获取index位置的元素     
    * @param index     
    * @return     
    */    
    E get(int index);
    /**     
    * 设置index位置的元素     
    * @param index     
    * @param element     
    * @return 原来的元素ֵ     
    */    
    E set(int index, E element);
    /**     
    * 在index位置插入一个元素     
    * @param index     
    * @param element     
    */    
    void add(int index, E element);
    /**     
    * 删除index位置的元素     
    * @param index     
    * @return     
    */    
    E remove(int index);
    /**     
    * 查看元素的索引     
    * @param element     
    * @return     
    */    
    int indexOf(E element);

3.2,清除元素clear()

public void clear() {
    size = 0//内存空间清0
    first = null;//地址指向null
}
  • 清空size,first地址指向null

3.3,元素数量

public int size() {
    return size;
}
  • 返回size的数量

3.4,是否包含元素

//ELEMENT_ON_FOUND = -1

public boolean contains(E element) {
    return indexOf(element) != ELEMENT_ON_FOUND;
}
  • 直接调用查询函数,看是否存在

3.5,获取index位置的元素

public E get(int index) {
    //直接在node中获取element        
    return node(index).element;    
}
  • 在node中根据下表取出对应元素

3.6,修改index位置的元素

public E set(int index, E element) {
    // 找到对应节点, node方法中已经判断了索引是否越界
    Node<E> node = node(index);
    // 记录旧元素
    E old = node.element;
    // 覆盖元素
    node.element = element;
    // 返回旧元素
    return old;
}

3.7,在对应index添加元素

public void add(int index, E element) {
    // 检查索引是否越界
    rangeCheckForSize(index);
    // 当插入到0的位置时
    if (index == 0) {
        // 将first指向新节点, 新节点的next指向first之前指向的节点
        first = new Node<E>(element, first.next);
    }else {
        // 找到指定位置前面的节点
        Node<E> prev = node(index - 1);
        // 将前面节点的next指向新节点, 新节点的next指向prev之前指向的节点
        prev.next = new Node<>(element, prev.next);
    }
    size++;
}
  • 在对应index添加元素,只需要创建新的node,插入到指定位置
  • index==0在0位置插入元素,将first指向新节点,新节点的next指向first之前指向的节点
  • index!=0,找到新节点位置之前的oldNode节点,oldNode的next指向新的节点,新节点的next指向oldNode之前指向的节点。

3.8,删除index位置元素

public E remove(int index) {
    // 检查索引是否越界
    rangeCheck(index);
    // 记录需要删除的节点
    Node<E> old = first;
    // 当删除第0个元素时, 将first的next指向索引为`1`的节点即可
    if (index == 0) {
        first = first.next;
    }else {
        // 找到前一个元素
        Node<E> prev = node(index - 1);
        // 记录需要删除的节点
        old = prev.next;
        // 将prev的next指向需要删除节点的后一个节点
        prev.next = old.next;
    }
    // size-1
    size--;
    // 返回删除的元素
    return old.element;
}
  • 当index为0时,只需要将first的next指向索引为“1”的节点
  • 当index不为0时,需要只找到index索引的前一个节点,指向index索引的后一个节点

3.9,查看元素的索引

private static final int ELEMENT_ON_FOUND = -1;
public int indexOf(E element) {
    // 取出头结点
    Node<E> node = first;
    // 当element为null时的处理
    if (element == null) {
        // 遍历节点, 找到存储为null的节点, 返回索引
        for (int i = 0; i < size; i++) {
            if (node.element == null) return i;
            node = node.next;
        }
    }else {
        for (int i = 0; i < size; i++) {
            // 遍历节点, 找到存储的元素与指定元素相等的节点, 返回索引
            if (element.equals(node.element)) return i;
            node = node.next;
        }
    }
    // 没有找到元素对应的节点, 返回ELEMENT_ON_FOUND
    return ELEMENT_ON_FOUND;
 }
  • 遍历节点里所有的元素,找到对应的索引的位置,返回就好

4,算法面试题

4.1,LeetCode--237. 删除链表中的节点

面试题地址:leetcode-cn.com/problems/de…

分析:

  1. 首先分析代码,给我们的是当前要删除的node。我们之前删除节点是先找到要删除节点前面的节点(oldNode)。然后将oldNode的next指向现在节点的后一个节点。
  2. 现在我们只有当前的节点,所以我们需要换一个思路。

解答:

  1. 我们只需要把当前节点的后面一个节点(nextNode)的element,覆盖当前node的element。将当前node的next指向后一个节点的next即可。

    public class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; } }

    public void deleteNode(ListNode* node) {
    node.val = node.next.val; node.next = node.next.next; }

4.2,leetcode-剑指 Offer 24. 反转链表

面试题链接:leetcode-cn.com/problems/fa…

分析:

  • 本题给我们一个头节点,经过反转以后,我们要返回新的头节点。

解答一:递归

    public ListNode reverseList(ListNode head) {

        if(head == null || head.next == null) return head;//当head或head.next为null,直接返回head
        ListNode newHead = reverseList(head.next);//调用reverseList,反转head之后的元素        
        head.next.next = head;//反转head        
        head.next = null;//head.next指向null。        
        return newHead;    
    }
  1. 调用reverseList反转函数进行反转,反转head之后的所有元素
  2. 反转head。head.next为head之后的节点(nextNode)。nextNode.next指向head。
  3. 把head的next指向null。

解答二:插头法

public ListNode reverseList(ListNode head) {
       ListNode newHead = null;       
       while(head != null){           
            ListNode temp = head.next;           
            head.next = newHead;           
            newHead = head;           
            head = temp;       
        }       
        return newHead;
}
  • 创建临时的node为newHead=null,为head的下一个节点

  • 把head.next先指向newHead

  • newHead指向当前的head

  • 当前的head指向temp所在的node。

4.3,leetcode-141.链表是否有环

题目链接:leetcode-cn.com/problems/li…

分析:

  • 传入一个头节点,判断链表是否有环,返回bool值

    public boolean hasCycle(ListNode head) {        
        if (head == null || head.next == null ) return false;        
        //创建慢node        
        ListNode slow = head;        
        //创建快node        
        ListNode fast = head.next;
        while (fast != null && fast.next != null) {            
            //如果相等有环            
            if(slow == fast) return true;            
            //慢node一次走一个            
            slow = slow.next;            
            //快node一次走两个            
            fast = fast.next.next;        
        }        
        return false;    
    }

解答:快慢指针法

  • 创建慢node为slow,快node为fast
  • 慢node的slow一次走一个next,快node的fast一次