链表是补充数组数据结构的另一种常见数据结构。与数组类似,它也是线性数据结构,以线性方式存储元素。
然而,与数组不同的是,它不会将元素存储在连续的位置;相反,它会将其分散存储在内存中,彼此通过节点相互连接。链表是节点列表,其中每个节点包含存储的值和下一个节点的地址。
由于这种结构,在链表中添加或删除元素变得很简单,因为你只需要改变链接而不是创建数组,但是这样会使搜索变得困难,并且经常需要 O(n) 的时间复杂度才能在单个链表中找到某个元素。
链表还有多种变体,如单链表,即允许在一个方向(正向或反向)上遍历;双链表则允许你在两个方向(向前或向后)遍历;最后是循环链表,它形成一个循环。
要解决关于链表的问题,掌握递归知识很重要,因为链表是递归数据结构。
如果你从链表中取出一个节点,剩下的数据结构仍然是链表,因此,许多链表问题的递归解比迭代解更简单。
1.找到链表的中间元素
思路一:快慢指针
快指针每次走两格,慢指针走一格。
public static void findMid(ListNode pHead) {
ListNode pfast=pHead;
ListNode pslow=pHead;
while(pfast.next!=null) {
if(pfast.next.next!=null) {
pfast=pfast.next.next;
pslow=pslow.next;
}else {
pfast=pfast.next;
}
}
System.out.println(pslow.val);
}
2.找链表中的环
https://www.cnblogs.com/xudong-bupt/p/3667729.html
<1>是否存在环
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null)
return false;
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast)
return true;
}
return false;
}
}
<2>找环的入口
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null) {
slow=slow.next;
fast=fast.next.next;
if(slow==fast)
break;
}
if(fast==null||fast.next==null) {
return null;
}
slow=head;
while(slow!=fast) {
slow=slow.next;
fast=fast.next;
}
return fast;
}
}
3.反转链表
迭代法:每次迭代建next节点存储当前节点的下个节点,防止断链。
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode now = head;
while (now != null) {
ListNode next = now.next;
now.next = pre;
pre = now;
now = next;
}
return pre;
}
递归法
public Node reverse2(Node node, Node prev) {
if (node.next == null) {
node.next = prev;
return node;
} else {
Node re = reverse2(node.next, node);
node.next = prev;
return re;
}
}
4.删除排序链表中的重复节点
<1> 只删除重复的 1-2-2 =>1-2
ListNode curr=head;
while(curr!=null&&curr.next!=null) {
if(curr.val==curr.next.val) {
curr.next=curr.next.next;
}else {
curr=curr.next;
}
}
return head;
}
<2>重复的全部删除 1-2-2 =>1
public static ListNode deleteDuplicates(ListNode head) {
ListNode ne=new ListNode(-1);
ListNode tmp=ne;
while(head!=null&&head.next!=null) {
if(head.val==head.next.val) {
while(head.next!=null&&head.val==head.next.val) {
head=head.next;
}
head=head.next;
}else {
tmp.next=head;
tmp=tmp.next;
head=head.next;
}
}
tmp.next=head;
return ne.next;
}
5.反转部分链表
- 反转链表 II
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
思路:
先找到要反转的节点的前驱节点,然后实现反转其后K个节点的方法。
reverse(ListNode front, int k)反转front(不包括from)其后的k个节点
head 保存第一个反转的节点,from保存最后一个反转的节点。
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode res = new ListNode(0);
res.next = head;
ListNode re = res;
int count = 1;
while (m > count) {
re = re.next;
count++;
}
reverse(re, n - m+1);
return res.next;
}
private static ListNode reverse(ListNode front, int k) {
ListNode from = front.next;
if (from == null)
return front;
ListNode head = from;
ListNode cur = from.next;
ListNode tmp = null;
while (k > 1 && cur != null) {
tmp = cur.next;
cur.next = from;
from = cur;
cur = tmp;
k--;
}
head.next = cur;
front.next = from;
return head;
}
}
6.删除节点
public static void deleteNode(ListNode head, ListNode node) {
ListNode cur = head;
ListNode pre = null;
while (cur != null) {
ListNode next = cur.next;
if (cur.val == node.val) {
pre.next = next;
}
pre = cur;
cur = next;
}
}
7.删除倒数第k个节点
思路:快慢指针。快指针先快走n步,慢指针与指针同时走,快指针到尾时,慢指针指向需要删除的元素。
trike:先设一个哑结点用于解决极端情况。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dumy=new ListNode(0);
dumy.next=head;
ListNode pre = dumy;
while (n > 0) {
pre = pre.next;
n--;
}
ListNode cur=dumy;
ListNode tmp=null;
while(pre!=null) {
tmp=cur;
pre=pre.next;
cur=cur.next;
}
tmp.next=cur.next;
return dumy.next;
}
}
8.删除乱序节点中的重复节点
思路:遍历两遍
注意内循环节点需要先行,才能定位到重复的第一个节点。
static void remove_duplicates(ListNode head) {
ListNode dumy=new ListNode(0);
dumy.next=head;
ListNode first=dumy;
ListNode second=dumy;
while(first!=null&&first.next!=null) {
second=first;
while(second.next!=null) {
if(first.val==second.next.val) {
second.next=second.next.next;
}else
second=second.next;
}
first=first.next;
}
}
利用hashSet
static void remove_duplicates(ListNode head) {
ListNode dumy=new ListNode(0);
dumy.next=head;
ListNode cur=dumy;
ListNode pre=dumy;
HashSet<Integer> hs = new HashSet<>();
while(cur!=null) {
int curval=cur.val;
if(hs.contains(curval)) {
pre.next=cur.next;
}else {
hs.add(curval);
pre=cur;
}
cur=cur.next;
}
}
两数相加
给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
从低位到高位处理
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode res = new ListNode(-1);
ListNode cur = res;
int carry = 0;
while(l1!=null||l2!=null) {
int d1 = l1==null ? 0 : l1.val;
int d2 = l2==null ? 0 : l2.val;
int sum = d1+d2+carry;
carry = sum>10?1:0;
cur.next=new ListNode(sum%10);
cur = cur.next;
if(l1!=null) l1=l1.next;
if(l2!=null) l2=l2.next;
}
if(carry==1) cur.next=new ListNode(1);
return res.next;
}
}
合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
合并排序数组类似
问题:如果一个链表为空,则结果链表的next指向另一个非空链表即可,不用再迭代。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode res = new ListNode(-1);
ListNode cur = res;
while (l1 != null && l2 != null) {
// cur.next = l1.val>=l2.val ? l2:l1;
if (l1.val >= l2.val) {
cur.next = l2;
l2 = l2.next;
} else {
cur.next = l1;
l1 = l1.next;
}
cur = cur.next;
}
cur.next = l1==null?l2:l1;
return res.next;
}
}
链表排序
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
输入: 4->2->1->3
输出: 1->2->3->4
思路:归并排序;
利用快慢指针找到mid节点;
然后断链;
public ListNode sortList(ListNode head) {
if(head==null||head.next==null)
return head;
ListNode fast=head;
ListNode slow=head;
while(fast.next!=null&&fast.next.next!=null) {
fast=fast.next.next;
slow=slow.next;
}
ListNode mid = slow.next;
slow.next=null;
ListNode l1 = sortList(head);
ListNode l2 = sortList(mid);
ListNode sorted = merge(l1, l2);
return sorted;
}
private ListNode merge(ListNode l1,ListNode l2){
if(l1 == null){
return l2;
}
if(l2 == null){
return l1;
}
ListNode head,tmp;
if(l1.val < l2.val){
head = l1;
l1 = l1.next;
}else{
head = l2;
l2 = l2.next;
}
tmp = head;
while(l1!=null && l2!=null){
if(l1.val < l2.val){
tmp.next = l1;
tmp = tmp.next;
l1 = l1.next;
}else{
tmp.next = l2;
tmp = tmp.next;
l2 = l2.next;
}
}
if(l1 == null){
tmp.next = l2;
}
if(l2 == null){
tmp.next = l1;
}
return head;
}