学习目标:
掌握单向链表
学习内容:
- 单向链表
- 带哨兵的单向链表
- 力扣相关练习
链表
链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构。
单向链表
1 定义链表
public class SinglyLinkList {
private Node head = null; //头指针
private static class Node {
int value; //值
Node next; //下一个节点
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
}
}
2 头插元素
public void addFirst(int value) {
head = new Node(value, head);
}
3 尾插元素
//查找链表最后一个元素
private Node findLast() {
if (head == null)//空链表
return null;
Node p = head;
while (p.next != null) {
p = p.next;
}
return p;
}
public void addLast(int value){
Node last = findLast();
if(last == null){ //空链表直接头插
addFirst(value);
return;
}
last.next = new Node(value,null);
}
4 循环遍历
4.1 基于while遍历
//基于while
public void loop(Consumer<Integer> consumer) {
Node p = head;
while (p != null) {
consumer.accept(p.value);
p = p.next;
}
}
4.2基于for遍历
//基于for
public void loop2(Consumer<Integer> consumer) {
for (Node p = head; p != null; p = p.next) {
consumer.accept(p.value);
}
}
4.3 基于iterator遍历
//基于iterator
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
Node p = head;
@Override
public boolean hasNext() {
return p!=null;
}
@Override
public Integer next() {
int value = p.value;
p = p.next;
return value;
}
};
}
4.4 递归遍历
public void loop(Node node) {
recursion(head);
}
private void recursion(Node node) {
if (node == null) return;
System.out.println(node.value);
recursion(node.next);
}
5 根据索引查询元素
private Node findNode(int index) {
int i = 0;
for (Node p = head; p != null; p = p.next, i++) {
if (i == index) {
return p;
}
}
return null;
}
public int get(int index) {
Node node = findNode(index);
if (node == null) {
throw new IllegalArgumentException(String.format("index [%d]", index));
}
return node.value;
}
6 根据索引位置插入元素
public void insert(int index, int value) {
if (index == 0) addFirst(value);
Node prev = findNode(index - 1); //找上一个节点
if (prev == null) { //找不到上一个节点
throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
}
prev.next = new Node(value, prev.next);
}
7 删除第一个元素
public void removeFirst(){
if(head==null){
throw new IllegalArgumentException(String.format("index 不合法"));
}
head = head.next;
}
8 按索引删除元素
public void remove(int index) {
if (index == 0) removeFirst();
Node prev = findNode(index - 1); //上一个节点
if (prev == null) {
throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
}
Node removed = prev.next;
if (removed == null) {
throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
}
prev.next = removed.next;
}
优化单向链表-哨兵节点
在基础的实现方法中需要频繁地判断节点是否为空,添加哨兵节点后,链表中至少会有一个哨兵节点,因此可以省去许多判空操作。
1 定义链表
需要添加一个哨兵节点,节点的value无所谓,head要指向哨兵节点。
public class SinglyLinkList {
private Node head = new Node(666,null); //头指针指向哨兵节点
private static class Node {
int value; //值
Node next; //下一个节点
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
}
}
2 头插元素
public void addFirst(int value) {
insert(0,value);
}
3 尾插元素
//查找链表最后一个元素
private Node findLast() {
Node p = head;
while (p.next != null) {
p = p.next;
}
return p;
}
public void addLast(int value){
Node last = findLast();
last.next = new Node(value,null);
}
4 循环遍历
添加哨兵节点后,遍历起点要从head变为head.next,即哨兵节点。
4.1 基于while遍历
//基于while
public void loop(Consumer<Integer> consumer) {
Node p = head.next;
while (p != null) {
consumer.accept(p.value);
p = p.next;
}
}
4.2 基于for遍历
//基于for
public void loop2(Consumer<Integer> consumer) {
for (Node p = head.next; p != null; p = p.next) {
consumer.accept(p.value);
}
}
4.3 基于iterator遍历
//基于iterator
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
Node p = head.next;
@Override
public boolean hasNext() {
return p!=null;
}
@Override
public Integer next() {
int value = p.value;
p = p.next;
return value;
}
};
}
5 根据索引查询元素
private Node findNode(int index) {
int i = -1;
for (Node p = head; p != null; p = p.next, i++) {
if (i == index) {
return p;
}
}
return null;
}
public int get(int index) {
Node node = findNode(index);
if (node == null) {
throw new IllegalArgumentException(String.format("index [%d]", index));
}
return node.value;
}
6 根据索引位置插入元素
public void insert(int index, int value) {
Node prev = findNode(index - 1); //找上一个节点
if (prev == null) { //找不到上一个节点
throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
}
prev.next = new Node(value, prev.next);
}
7 删除第一个元素
public void removeFirst(){
remove(0);
}
8 按索引删除元素
public void remove(int index) {
Node prev = findNode(index - 1); //上一个节点
if (prev == null) {
throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
}
Node removed = prev.next;
if (removed == null) {
throw new IllegalArgumentException(String.format("index [%d] 不合法", index));
}
prev.next = removed.next;
}
力扣链表相关练习
1.反转单向链表
解法一:新建链表逐个存取
public static ListNode reverseList1(ListNode o1) {
ListNode n1 = null;
ListNode p = o1;
while (p != null) {
n1 = new ListNode(p.val, n1);
p = p.next;
}
return n1;
}
解法二:递归改节点的next
思路:首先找到最后一个节点,接着在退出递归的过程中依次完成相邻节点逆序
public static ListNode reverseList2(ListNode p) {
if (p == null) {
return null;
}
//找到最后一个节点
if (p.next == null) {
return p;
}
ListNode last = reverseList2(p.next);
//和其下一个节点相邻节点逆序
p.next.next = p;
p.next = null;
return last;
}
解法三:
思路:逐渐将旧链表的第二个元素头插
public static ListNode reverseList3(ListNode o1) {
if (o1 == null || o1.next == null) return o1;
ListNode n1 = o1;
while (o1.next != null) {
ListNode p = o1.next;
o1.next = p.next;
p.next = n1;
n1 = p;
}
return n1;
}
解法四:
思路:不断将旧链表头部头插到新链表
public static ListNode reverseList4(ListNode o1){
ListNode n1 = null;
while(o1!=null){
ListNode o2 = o1.next;
o1.next = n1;
n1 = o1;
o1 = o2;
}
return n1;
}
2.根据值删除链表节点
解法一:遍历
public static ListNode removeElements(ListNode head, int val) {
//哨兵节点
ListNode s = new ListNode(-1, head);
ListNode p1 = s;
ListNode p2 = s.next;
while (p2 != null) {
if (p2.val == val) {
p1.next = p2.next;
p2 = p2.next;
} else {
p1 = p1.next;
p2 = p2.next;
}
}
return s.next;
}
解法二:递归
思路:
- 如果
p == val
,返回p.next
递归的结果 - 如果
p != val
,返回p
拼接上p.nex
t递归的结果
public static ListNode removeElements2(ListNode p,int val){
if(p==null){
return null;
}
if(p.val==val){
return removeElements2(p.next,val);
}else {
p.next = removeElements2(p.next,val);
return p;
}
}
3.删除倒数第n个节点
解法一:遍历
思路:快慢指针
public static ListNode removeNthFromEnd(ListNode p, int n) {
ListNode s = new ListNode(-1,p);//哨兵
ListNode p1 = s;
ListNode p2 = s;
int t = n + 1; //p1,p2指针的距离
while(t>0){
p2 = p2.next;
t--;
}
while(p2!=null){
p2 = p2.next;
p1 = p1.next;
}
p1.next = p1.next.next;
return s.next;
}
解法二:递归
public static ListNode removeNthFromEnd(ListNode p, int n) {
//哨兵用来处理删除首结点的情况
ListNode s =new ListNode(-1,p);
recursion(s,n);
return s.next;
}
private static int recursion(ListNode p, int n) {
if (p == null) return 0;
int nth = recursion(p.next, n);
if (nth == n) {
p.next = p.next.next;
}
return nth + 1;
}
4.删除有序链表的重复节点(保留重复的第一个节点)
方法1:遍历
public static ListNode deleteDuplicates(ListNode p){
ListNode p1 = p;
ListNode p2 = p.next;
while(p2!=null){
if(p1.val==p2.val) {
p1.next = p2.next;
p2 = p2.next;
}else{
p1 = p1.next;
p2 = p2.next;
}
}
return p;
}
方法2:递归
public static ListNode deleteDuplicates2(ListNode p){
ListNode s = new ListNode(-1,p);
recursion(s);
return s.next;
}
public static int recursion(ListNode p){
if(p==null){
return 0;
}
int nextValue = recursion(p.next);
if(p.val == nextValue){
p.next = p.next.next;
}
return p.val;
}
5.有序链表去重(重复元素都不留)
方法一:遍历
public static ListNode deleteDuplicates2(ListNode p) {
if (p == null || p.next == null) {
return p;
}
ListNode s = new ListNode(-1, p);
ListNode p1 = s;
ListNode p2;
ListNode p3;
while ((p2 = p1.next)!= null && (p3 = p2.next) != null) {
if (p2.val == p3.val) {
while ((p3 = p3.next) != null && p3.val == p2.val) {
}
p1.next = p3;
} else {
p1 = p1.next;
}
}
return s.next;
}
方法二:递归
public static ListNode deleteDuplicates(ListNode p) {
if (p == null || p.next == null) {
return p;
}
if (p.val == p.next.val) {
ListNode x = p.next.next;
while (x != null && x.val == p.val) {//x定位到第一个不等于p.val的节点
x = x.next;
}
return deleteDuplicates(x);
} else {
p.next = deleteDuplicates(p.next);//去重后拼接
return p;
}
}
6.合并两个有序链表
解法一:遍历
public static ListNode mergeTwoLists(ListNode p1, ListNode p2) {
ListNode s = new ListNode(-1, null);
ListNode q = s;
while (p1 != null || p2 != null) {
if (p1 == null) {
q.next = p2;
return s.next;
}
if (p2 == null) {
q.next = p1;
return s.next;
}
if (p1.val <= p2.val) {
q.next = p1;
p1 = p1.next;
} else {
q.next = p2;
p2 = p2.next;
}
q = q.next;
}
return null;
}
解法二:递归
public static ListNode mergeTwoLists2(ListNode p1, ListNode p2) {
if (p1 == null)
return p2;
if (p2 == null)
return p1;
if (p1.val < p2.val) {
p1.next = mergeTwoLists2(p1.next, p2);
return p1;
} else {
p2.next = mergeTwoLists2(p1, p2.next);
return p2;
}
}
7.合并多个有序链表
利用分治法
public static ListNode mergeTwoLists(ListNode p1, ListNode p2) {
ListNode s = new ListNode(-1, null);
ListNode q = s;
while (p1 != null || p2 != null) {
if (p1 == null) {
q.next = p2;
return s.next;
}
if (p2 == null) {
q.next = p1;
return s.next;
}
if (p1.val <= p2.val) {
q.next = p1;
p1 = p1.next;
} else {
q.next = p2;
p2 = p2.next;
}
q = q.next;
}
return null;
}
public static ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
return split(lists, 0, lists.length - 1);
}
//返回合并后的链表,i和j分别代表左右边界
private static ListNode split(ListNode[] lists, int i, int j) {
if(i==j){ //数组内只有一个链表
return lists[i];
}
int m = (i + j) >>> 1;
ListNode left = split(lists, i, m);
ListNode right = split(lists, m + 1, j);
return mergeTwoLists(left, right);
}
8.查找链表的中间节点
public ListNode middleNode(ListNode head) {
ListNode p1 = head;
ListNode p2 = head;
while (p2 != null && p2.next != null) {
p1 = p1.next;
p2 = p2.next;
p2 = p2.next;
}
return p1;
}
9.判断是否是回文列表
//判断回文链表
/*
* 1.找到中间节点
* 2.中间点后半个链表反转
* 3.反转后链表和元链表比较*/
public static boolean isPalindrome(ListNode head) {
ListNode middle = middleNode(head);
ListNode newHead = reverse(middle);
while (newHead!=null){
if(newHead.val!=head.val){
return false;
}
head = head.next;
newHead = newHead.next;
}
return true;
}
private static ListNode reverse(ListNode o1){
ListNode n1 = null;
while(o1!=null){
ListNode o2 = o1.next;
o1.next = n1;
n1 = o1;
o1 = o2;
}
return n1;
}
private static ListNode middleNode(ListNode head) {
ListNode p1 = head;
ListNode p2 = head;
while (p2 != null && p2.next != null) {
p1 = p1.next;
p2 = p2.next;
p2 = p2.next;
}
return p1;
}
找中间点和反转可以优化为同一步内完成,进一步提升效率
10. 判断链表是否有环
佛洛依德的龟与兔
第一阶段:
- 龟走一步,兔走两步
- 兔子走到终点则不存在环
- 兔子追上龟则判断存在环
第二阶段:
- 从一次相遇开始,龟回到起点,兔位置保持不变
- 兔和龟一次都走一步
- 再次相遇时,地点就是环的入口
第一阶段:判断是否有环
public static boolean hasCycle(ListNode head) {
ListNode h = head; //兔
ListNode t = head; //龟
while (h != null && h.next != null) {
h = h.next.next;
t = t.next;
if (h == t) {
return true;
}
}
return false;
}
第二阶段:判断环入口
public static ListNode detectCycle(ListNode head) {
ListNode h = head; //兔
ListNode t = head; //龟
while (h != null && h.next != null) {
h = h.next.next;
t = t.next;
if (h == t) {
//进入第二阶段
t = head;
while (true) {
if (t == h) {
return t;
}
h = h.next;
t = t.next;
}
}
}
return null;
}