赞
踩
上一节中,我们讲过了Java中的ArrayList,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序来实现的。类似于火车一样。
注意:
实际中,链表的结构多种多样,一下情况组合起来就有8种链表结构:
单向或者双向
带头或者不带头
循环或者非循环
虽然有这么多的链表结构,但是我们重点讲解两种:
SingleLinkedList类
// 1、无头单向非循环链表实现 public class SingleLinkedList { class Node { public int val; public Node next; public Node(int val) { this.val = val; } } public Node head; public void createLinkList() { Node node1 = new Node(12); Node node2 = new Node(45); Node node3 = new Node(88); Node node4 = new Node(92); node1.next = node2; node2.next = node3; node3.next = node4; head = node1; } //打印链表 public void display() { Node cur = head; while (cur != null) { System.out.print(cur.val + " "); cur = cur.next; } System.out.println(); } //查找是否包含关键字key是否在单链表当中 public boolean contains(int key) { Node cur = head; while (cur != null) { if (cur.val == key) { return true; } cur = cur.next; } return false; } //得到单链表的长度 public int size() { int count = 0; Node cur = head; while (cur != null) { count++; cur = cur.next; } return count; } //头插法 public void addFirst(int data) { Node node = new Node(data); node.next = head; head = node; } //尾插法 public void addLast(int data) { Node node = new Node(data); if (head == null) { head = node; } Node cur = head; while (cur.next != null) { cur = cur.next; } cur.next = node; } //任意位置插入,第一个数据节点为0号下标 public void addIndex(int index, int data) throws ListIndexOutofException { if (index < 0 || index > size()) { throw new ListIndexOutofException("index位置不合法"); } if (index == 0) { addFirst(data); } if (index == size()) { addLast(data); } int count = 0; Node cur = head; while (count != index - 1) { cur = cur.next; count++; } Node node = new Node(data); node.next = cur.next; cur.next = node; } //删除第一次出现关键字为key的节点 public void remove(int key) { if(head == null) { return; } if(head.val == key) { head = head.next; return; } Node cur = searchprev(key); if(cur == null) { return; } Node del = cur.next; //要删除的节点 cur.next = del.next; } /* 查找key前一个节点 */ private Node searchprev(int key) { Node cur = head; while (cur.next != null) { if(cur.next.val == key) { return cur; } cur = cur.next; } return null; //没有删除的节点 } //删除所有值为key的节点 public void removeAllKey(int key) { if(head == null) { return; } Node pre = head; Node cur = head.next; while (cur != null) { if(cur.val==key) { pre.next = cur.next; } else { pre = cur; } cur = cur.next; } if(head.val==key) { head = head.next; } } public void clear() { head = null; } }
ListIndexOutofException类
public class ListIndexOutofException extends RuntimeException {
public ListIndexOutofException() {
}
public ListIndexOutofException(String message) {
super(message);
}
}
Test类
public class Test { public static void main(String[] args) { SingleLinkedList singleLinkedList = new SingleLinkedList(); singleLinkedList.createLinkList(); singleLinkedList.display(); System.out.println(singleLinkedList.contains(12)); System.out.println(singleLinkedList.size()); singleLinkedList.addFirst(8); //头插 singleLinkedList.display(); singleLinkedList.addLast(9);//尾插 singleLinkedList.display(); singleLinkedList.addIndex(2,999); singleLinkedList.addIndex(4,999); singleLinkedList.display(); singleLinkedList.remove(88); singleLinkedList.display(); singleLinkedList.removeAllKey(999); singleLinkedList.display(); } }
以下是我要讲解的5个关于链表的面试题,包含OJ链接。
题目:链表的回文结构
思路: 由于链表是单向的,所以我们如果要使用双指针的思想解决该题,就需要将后半部分数据的指向进行逆置。也就是
1——>2——>2<——1
要实现后半部分的逆置,我们需要找到链表的中间节点 。在链表的实现里,我们已经使用快慢指针找到链表的中间节点。最后我们再使用双指针,遍历链表,比较前后对称节点的值是否相等。
代码实现:
import java.util.*; /* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class PalindromeList { public boolean chkPalindrome(ListNode head) { if(head == null) { return false; } if(head.next == null) { return true; } //快慢指针找中间节点 ListNode fast = head; ListNode slow = head; while(fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; } //翻转后半部分的指向 ListNode cur = slow.next; while(cur != null) { ListNode curNext = cur.next; cur.next = slow; slow = cur; cur = curNext; } while(slow != head) { if(slow.val != head.val) { return false; } //下面这个if语句是用来判断链表长度为偶数的情况的 if(head.next == slow) { return true; } slow = slow.next; head = head.next; } return true; } }
题目:合并两个有序链表
思路: 要返回一个新的升序链表,那么我们需要申请一个新的头节点,然后通过遍历两个链表,并将其值进行对比,值小的,则添加进新的头节点,并且头节点和值小的链表都往后移动,这样一直比较下去,直到一个链表比较完为止。如果最后还有一个链表不为null,那么直接添加进新的头节点。
代码实现:
/** * Definition for singly-linked list. * 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; } * } */ class Solution { public ListNode mergeTwoLists(ListNode list1, ListNode list2) { ListNode newHead = new ListNode(0);//申请新的头节点 ListNode cur = newHead; //使用cur来间接使用新的头节点 //循环的条件是两个链表都不为null while(list1 != null && list2 != null) { if(list1.val<list2.val) { cur.next = list1; list1 = list1.next; //往后走 cur = cur.next; //往后走 }else { cur.next = list2; list2 = list2.next; cur = cur.next; } } //如果哪个链表不为null,则直接添加进新的头节点 if(list1!=null) { cur.next = list1; } if(list2!=null) { cur.next =list2; } return newHead.next; //返回新头节点的后面部分,因为newHead.val为0 } }
题目:链表中倒数第K个节点
思路: 这道题也是一道数学题,我们仍然使用快慢指针的思想,让fast先走k-1步,然后让slow和fast一起走。知道fast.next为null。
代码实现:
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class Solution { public ListNode FindKthToTail(ListNode head,int k) { //判断K值的合法性以及head是否为null if(k <= 0 || head==null) { return null; } ListNode fast = head; ListNode slow = head; while(k-1 != 0) { fast = fast.next; //这个if语句处理了当k值大于链表长度的情况,复杂度变低 if(fast == null) { return null; } k--; } while(fast.next != null) { fast = fast.next; slow = slow.next; } return slow; } }
题目:链表的中间节点
**思路:**快慢指针,让fast的速度是slow的两倍,走完之后,slow指向的节点就是中间节点。
代码实现:
/** * Definition for singly-linked list. * 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; } * } */ class Solution { public ListNode middleNode(ListNode head) { if(head==null) { return null; } ListNode fast = head; ListNode slow = head; //两个循环条件不能交换位置,不然空指针异常 while(fast!=null && fast.next!=null) { fast = fast.next.next; slow = slow.next; } return slow; } }
题目:反转链表
思路: 使用头插法将后面的节点一个一个插在前面。
代码实现:
/** * Definition for singly-linked list. * 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; } * } */ class Solution { public ListNode reverseList(ListNode head) { if(head==null) { return null; } if(head.next==null) { return head; } ListNode cur = head.next; head.next = null; //修改为null while(cur!=null) { ListNode curNext = cur.next; //一定要保存cur.next,因为cur在改变 cur.next = head; //修改cur的指向,指向原本的头节点 head = cur; //改变头节点,cur成为新的头节点 cur = curNext; //改变cur } return head; } }
以上就是今天要讲的内容,本文仅仅简单介绍了链表的使用以及几个面试题,链表在Java数据结构中非常重要,学习时要多与数学结合起来,多画图,多思考!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。