定义一个node节点
class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
206 反转链表
题目:给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
解题:
1、如果head为空或者只有一个节点,那么直接返回结果即可
2、否则,定义一个新链表,遍历旧链表,将每个节点插入到新链表的头部即可。
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode res = new ListNode(head.val);
while (head.next != null) {
ListNode next = head.next;
head = next;
res = new ListNode(next.val, res);
}
return res;
}
203 根据值来删除节点
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
定义一个头节点,和两个指针, p和q,如图,循环该链表:
1、如果q指针的值不等于目标值,则分别向后移动一位
2、如果q指针的值等于目标值,则p不懂,q后移,但是p仍指向q
public List1Node removeElements(List1Node head, int val) {
// 定义哨兵和双指针
List1Node myHead = new List1Node(-1, head);
List1Node p = myHead;
List1Node q = p.next;
while (q!=null) {
if (q.val == val) {
// 右指针等于目标值,q右移,p不变,但是p还指向q
q = q.next;
p.next = q;
} else {
// 两个指针均向后移动一位
p = p.next;
q = q.next;
}
}
return myHead.next;
}
19 删除链表的倒数第N个节点
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
解题1:
1、先获取链表的长度
2、找到倒数第N个节点的前一个节点,指向下一个节点即可
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
int length = length(head);
ListNode dummy = new ListNode(0,head);
ListNode cur = dummy;
for (int i = 1; i < length -n +1; i++) {
cur = cur.next;
}
cur.next = cur.next ==null ? null : cur.next.next;
return dummy.next;
}
public int length(ListNode head) {
if (head == null) {
return 0;
}
ListNode p = head;
int count = 1;
while (p.next != null) {
p = p.next;
count++;
}
return count;
}
}
解题2
定义两个指针节点p和q,分别指向头节点,先让q节点向后移动n步,然后,在让p和q同时后移,当q指向空的时候,p恰好是倒数第n个节点的前一个节点。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode p1 = dummy;
ListNode p2 = dummy;
for (int i = 0; i < n + 1; i++) {
p2 = p2.next;
}
while (p2 != null) {
p1 = p1.next;
p2 = p2.next;
}
p1.next = p1.next.next;
return dummy.next;
}
}
83、 有序链表去重 (留下一个)
给定一个已排序的链表的头 head
, 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表
解题1:
去重,可以使用Map或者set ,遍历链表,如果节点的值已经在集合中存在,那么这个节点就是重复节点,可以删除,如果不存在,则将值存入集合,继续遍历。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head==null){
return head;
}
Set<Integer> set = new HashSet<>();
ListNode node = head;
set.add(node.val);
while (node.next!=null) {
ListNode next = node.next;
if (set.contains(next.val)){
node.next = next.next;
}else {
node = next;
set.add(next.val);
}
}
return head;
}
}
解题2:
定义哨兵节点,和两个指针节点p/q,比较p/q的值是否相等,如果相等,q后移,p还是指向q,如果不相等,同时后移。
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode dummy = new ListNode(-101, head);
ListNode p = dummy;
ListNode q = dummy.next;
while (q != null) {
if (q.val == p.val) {
q = q.next;
p.next = q;
} else {
q = q.next;
p = p.next;
}
}
return dummy.next;
}
82. 删除排序链表中的重复元素 (重复的一个不留)
给定一个已排序的链表的头 head
, 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
解题:
参考上一题,定义哨兵节点,以及三个指针节点,仍然按照p和q比较,遍历,多定义的r节点,是p节点的上一个节点,pq相同的时候,q不断后移,一直移到为null或者值不同,然后将r的下一个节点指向q
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode dummy = new ListNode(-200, head);
ListNode r = dummy;
ListNode p;
ListNode q;
while ((p = r.next) != null && (q = p.next) != null) {
if (p.val == q.val) {
while (q != null && q.val == p.val) {
q = q.next;
}
r.next=q;
} else {
r = r.next;
}
}
return dummy.next;
}
21 合并有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
解题:
定义一个新的头节点,遍历两个链表,哪个值小,就放在新建的头节点后面。如果有一个链表为null,那就把另外一个链表的值全放在新建链表的后面。
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// 头节点,及指针节点
ListNode head = new ListNode();
ListNode node = head;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
node.next = list1;
list1 = list1.next;
} else {
node.next = list2;
list2 = list2.next;
}
node = node.next;
}
if (list1 != null) {
node.next = list1;
}
if (list2 != null) {
node.next = list2;
}
return head.next;
}
23、和并多个升序链表
题解:参考21题,遍历链表,两两合并
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
ListNode node = null;
for (int i = 0; i < lists.length; i++) {
node = mergeTwoLists(lists[i], node);
}
return node;
}
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// 头节点,及指针节点
ListNode head = new ListNode();
ListNode node = head;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
node.next = list1;
list1 = list1.next;
} else {
node.next = list2;
list2 = list2.next;
}
node = node.next;
}
if (list1 != null) {
node.next = list1;
}
if (list2 != null) {
node.next = list2;
}
return head.next;
}
}
876. 链表的中间结点
给你单链表的头结点 head
,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
题解:
快慢指针法,定义两个指针,初始分别指向头节点,p每次前进一步,q每次前进两步,当q前进时为null了,此时p指向的就是题目要求的中间节点。
public ListNode middleNode(ListNode head) {
ListNode p1 = head;
ListNode p2 = head;
while (p2 != null && p2.next != null) {
p1 = p1.next;
p2 = p2.next.next;
}
return p1;
}
243 回文链表
判断一个链表是不是回文链表,就是正读反读是一样的。
题解:
将给定的链表反转,然后将新链表和老链表逐个比较,一致则为true,否则为false。
public boolean isPalindrome(ListNode head) {
ListNode reverse = reverse(head);
while (head != null) {
if (head.val != reverse.val) {
return false;
}
head = head.next;
reverse = reverse.next;
}
return true;
}
public ListNode reverse(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode res = new ListNode(head.val);
while (head.next != null) {
ListNode next = head.next;
head = next;
res = new ListNode(next.val, res);
}
return res;
}
链表判环算法:
用快慢指针的方法(龟兔赛跑算法):
- 阶段1
- 龟一次走一步,兔子一次走两步
- 兔子能走到终点,那么就不存在环
- 兔子能追上龟(再次相遇),说明存在环。
- 阶段2
- 再次相遇时,兔子留在原地不动,龟退回到起点
- 兔子和龟都每次走一步
- 再次相遇的时候,相遇点就是环的入口。
解释一下阶段2:
- 假设起点到环入口是a步,环一圈是b步
- 从起点开始,走a+b*n都可以到环的入口
- 第一次相遇:
- 兔走了a + m*b +k 步,k是距离环入口的距离
- 龟走了a + n*b +k 步,k是距离环入口的距离, m>n
- 兔子走的路程是龟的两倍,所以 龟 = 兔 - 龟 = x*n 圈,龟走的是圈的整数倍
- 因为走a+b*n都可以到环的入口,因此,从相遇点开始,在走a步,就可以找到环的入口。
- 这个还有一个小bug,就是如果a=0,那么起点就是环入口。
141、判断是否是环形链表
根据上述判环算法,可以直接写出代码
public boolean hasCycle(ListNode head) {
ListNode p = head;
ListNode q = head;
while (q != null && q.next != null) {
p = p.next;
q = q.next.next;
if (q == p) {
return true;
}
}
return false;
}
142 找到环形链表的入口
根据上述算法,可以直接在141的基础上改写代码
public ListNode detectCycle(ListNode head) {
ListNode p = head;
ListNode q = head;
while (q != null && q.next != null) {
p = p.next;
q = q.next.next;
if (q == p) {
// 判断里面改写代码,将p重新指向头节点,然后两个节点每次分别走一步
p = head;
while (true) {
// 进到循环之后,先判断两个节点是否相等,
// 解决了头结点就是入口节点的问题。
if (q == p) {
return q;
}
p = p.next;
q = q.next;
}
}
}
return null;
}