常见错误:空指针、死循环、边界条件
基本结构
public class ListNode {
* int val;=
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }基本操作
遍历链表
案例:给一个链表头,求这个链表的长度
迭代法:
int listLength(ListNode head){
int len=0;
while(head!null){
head=head.next;
len++;
}
return len;
}
int listLength(ListNode head){
int len=0;
for(ListNode p=head;p!=null;p=p.next){
len++;
}
return len;
}插入元素
- 表头前(链表开始处):头插法
- 表尾后(链表结尾处)
- 表中间(随机位置)
删除元素
- 表头前(链表开始处)
- 表尾后(链表结尾处)
- 表中间(随机位置)
左神笔记
1.链表的分类
- 按照连接方向分类:单链表、双链表
- 按照有环无环分类:普通链表、循环链表
2.链表问题代码实现的关键点
- 链表调整函数的返回值类型,根据要求往往是节点类型,因为链表在调整过程中往往会有改变头部的情况发生,如果头结点换了,自然要返回新头部
- 处理链表过程中,要注意思考哪些指针变化了,先采用画图的方式理清逻辑,看一下修改的哪些指针,同时要注意调整时前后节点的变化,环境需要预先保持下来,不然可能发生后面断线找不到后续节点的情况
- 链表问题边界问题讨论要去严格,比如头结点、尾结点、空节点这些都是特殊值,不能总假设指针有意义,处理过程要时刻判断节点是否为空
3.关于链表插入和删除的注意事项
- 特殊处理链表为空,或者链表长度为1的情况
- 注意插入操作的调整过程
注意点:头尾节点及空节点需要特殊考虑
双链表的插入与删除和单链表类似,但是需要额外考虑previous指针的指向
4.单链表的翻转操作
- 当链表为空或者长度为1时,特殊处理
- 大量链表问题可以使用额外数据结构来简化调整过程,比如队列和栈、数组结构;
- 但链表问题最优解往往是不适用额外数据结构的方法,无疑需要更好的代码实现能力
算法题
剑指6:从尾到头打印链表
思路一(最简单):在动态数组从头开始加入链表的值
import java.util.ArrayList;
public class Solution {
public ArrayList printListFromTailToHead(ListNode listNode) {
ArrayList array = new ArrayList<>();
//遍历单链表
while(listNode!=null){
array.add(0,listNode.val);
listNode=listNode.next;
}
return array;
}
}思路二:使用头插法构建逆序链表(这里藏着一个反转链表!!!)
import java.util.ArrayList;
public class Solution {
public ArrayList printListFromTailToHead(ListNode listNode) {
ListNode head = new ListNode(-1);
while(listNode!=null){
ListNode t=listNode.next;
listNode.next=head.next;
head.next=listNode;
listNode=t;
}
head=head.next;
ArrayList array = new ArrayList<>();
while(head!=null){
array.add(head.val);
head=head.next;
}
return array;
}
}思路三:使用栈,利用栈的先进后出的顺序弹出链表数值(这题没写对)
注意:栈的定义很不熟悉,需要总结和复习
Stack<> stack
add()
pop()
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList printListFromTailToHead(ListNode listNode) {
Stack stack=new Stack<>();
while(listNode!=null){
stack.add(listNode.val);
listNode=listNode.next;
}
ArrayList array = new ArrayList<>();
while(!stack.isEmpty()){
array.add(stack.pop());
}
return array;
}
}思路四:使用递归(这题没写对)
注意:1、对递归结构的理解不够,不容易写对 2、addAll()方法没用过,现在学到了
1和2处要好好理解
import java.util.ArrayList;
public class Solution {
public ArrayList printListFromTailToHead(ListNode listNode) {
ArrayList array = new ArrayList<>();
if(listNode!=null){//这里是if
array.addAll(printListFromTailToHead(listNode.next));//1
array.add(listNode.val);//2
}
return array;
}
}剑指18:删除链表中重复的节点
疑问:假如1->2->3->4->2->5,能不能把两个2都删除?想写函数验证一下。思路一:双指针法
public class Solution {
public ListNode deleteDuplication(ListNode pHead){
//因为头节点可能删除,所以需要一个新节点保存链表入口
ListNode head=new ListNode(-1);
head.next=pHead;
//每次都落在不重复的节点上的判断指针,功能是让链表不断
ListNode pre=head;
//搜索用的工作指针
ListNode last=pre.next;
while(last!=null){
//假设当前的和后面相同
if(last.next!=null&&last.val==last.next.val){
while(last.next!=null&&last.val==last.next.val){
last=last.next;
}
last=last.next;
pre.next=last;
//当前的和后面已经不同
}else{
last=last.next;
pre=pre.next;
}
}
return head.next;
}
}思路二:递归法
待完成LC86:删除链表中的重复元素
(能做出来,改了很多遍才改对,边界条件注意的不好)
思路:跟上题很像,注意返回值
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null||head.next==null)
return head;
ListNode pHead=new ListNode(-1);
pHead.next=head;
ListNode pre=pHead;
ListNode last=pre.next;
while(last!=null){
if(last.next!=null&&last.val==last.next.val){
while(last.next!=null&&last.val==last.next.val){
last=last.next;
}
pre.next=last;
}else{
pre=pre.next;
last=last.next;
}
}
return pHead.next;
}
}LC707 设计链表
class MyLinkedList {
Node head;
int length;
/** 初始化链表 */
public MyLinkedList() {
length=0;
head = new Node(-1);
}
/** 获取链表中第 index 个节点的值。如果索引无效,则返回-1 */
public int get(int index) {
if(index>=length||index<0)
return -1;
Node current = head;
for(int i = 0; i <= index; i++)
current = current.next;
return current.val;
}
/** 在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点 */
public void addAtHead(int val) {
Node temp = new Node(val);
temp.next = head.next;
head.next = temp;
length ++;
}
/** 将值为 val 的节点追加到链表的最后一个元素 */
public void addAtTail(int val) {
Node current = head;
while(current.next != null)
current = current.next;
Node temp = new Node(val);
current.next = temp;
length ++;
}
/** 在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点 */
public void addAtIndex(int index, int val) {
if(index > length)
return;
if(index == length){
addAtTail(val);
return;
}
if(index < 0)
index = index+length+1;
Node current = head;
for(int i = 0; i < index; i++)
current = current.next;
Node ptr = current.next;
Node temp = new Node(val);
current.next = temp;
temp.next = ptr;
length ++;
}
/** 如果索引 index 有效,则删除链表中的第 index 个节点 */
public void deleteAtIndex(int index) {
if(index >= length || index < 0)
return;
Node current = head;
for(int i = 0; i < index; i++)
current = current.next;
current.next = current.next.next;
length --;
}
public void print(){
Node current = head;
StringBuilder builder = new StringBuilder();
while(current != null){
builder.append(String.valueOf(current.val) + " ");
current = current.next;
}
System.out.
(builder);
}
}
class Node{
int val;
Node next;
Node(int x){
this.val=x;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
剑指24 反转链表(多练多练,写的不好)
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null||head.next==null)
return head;
ListNode pre=null;
ListNode cur=head;
ListNode next=head.next;
while(cur!=null){
cur.next=pre;
pre=cur;
cur=next;
if(cur==null)
continue;
next=next.next;
}
return pre;
}
}头插法
1、画好变化流程图
2、起点什么样
3、重点什么样
4、循环条件判断是什么
class Solution {
public ListNode reverseList(ListNode head) {
ListNode newList=new ListNode(-1);//创建出来的单节点它的下一个节点是null
while(head!=null){
ListNode next=head.next;
head.next=newList.next;
newList.next=head;
head=next;
}
return newList.next;
}
}
我写错的头插法:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode newList=new ListNode(-1);
while(head!=null){
ListNode t=head.next;
head.next=newList;
newList=head;
head=t.next;
}
return head;
}
}
LC92 反转链表2(反转部分链表)
思路一:递归
思路二:迭代反转
剑指22:链表中倒数第k个节点
进度不同的双指针(不是快慢指针),注意边界点!
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
ListNode slow=head;
ListNode fast=head;
int flag=0;
while(fast!=null){
fast=fast.next;
if(flag>=k)
slow=slow.next;
flag++;
}
if(flag剑指23:链表中环的入口节点
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead){
if(pHead==null||pHead.next==null)
return null;
ListNode fast=pHead;
ListNode slow=pHead;
//只要能走就不停下来
while(fast.next!=null&&fast.next.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
//如果此时已经相遇,slow还有z到达入口
fast=pHead;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return fast;
}
}
return null;
}
}剑指25:合并两个排序链表
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode pre=new ListNode(-1);
ListNode cur=pre;
ListNode p1=list1;
ListNode p2=list2;
while(p1!=null&&p2!=null){//写错了N次,链表题必须细致画图,做太久或者做错就完蛋了!!
if(p1.val<=p2.val){
cur.next=p1;
p1=p1.next;
cur=cur.next;
}else{
cur.next=p2;
p2=p2.next;
cur=cur.next;
}
}
if(p1==null)
cur.next=p2;
if(p2==null)
cur.next=p1;
return pre.next;
}
}剑指52:两个链表的第一个公共节点
(值得反复做,解法多,边界条件容易出错)
public class Solution {
/**
* 思路:如果有公共节点,1)若两个链表长度相等,那么遍历一遍后,在某个时刻,p1 == p2
* 2)若两个链表长度不相等,那么短的那个链表的指针pn(也就是p1或p2)
* 必先为null,那么这时再另pn = 链表头节点。经过一段时间后,
* 则一定会出现p1 == p2。
* 如果没有公共节点:这种情况可以看成是公共节点为null,顾不用再考虑。
*/
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while(p1 != p2) {
if(p1 != null) p1 = p1.next; //防止空指针异常
if(p2 != null) p2 = p2.next;
if(p1 != p2) { //当两个链表长度不想等
if(p1 == null) p1 = pHead1;
if(p2 == null) p2 = pHead2;
}
}
return p1;
}
}剑指35:复杂链表的复制
public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null)
return null;
// 插入新节点
RandomListNode cur = pHead;
while (cur != null) {
RandomListNode clone = new RandomListNode(cur.label);
clone.next = cur.next;
cur.next = clone;
cur = clone.next;
}
// 建立 random 链接
cur = pHead;
while (cur != null) {
RandomListNode clone = cur.next;
if (cur.random != null)
clone.random = cur.random.next;
cur = clone.next;
}
// 拆分:没搞懂为何这样拆
cur = pHead;
RandomListNode pCloneHead = pHead.next;
while (cur.next != null) {
RandomListNode next = cur.next;
cur.next = next.next;
cur = next;
}
return pCloneHead;
}
我的错解
public class Solution {
public RandomListNode Clone(RandomListNode pHead){
if(pHead==null)
return null;
//复制节点
RandomListNode cur=pHead;
while(cur!=null){
RandomListNode clone=new RandomListNode(cur.label);
clone.next=cur.next;
cur.next=clone;
cur=clone.next;
}
//复制随机指针
cur=pHead;
while(cur!=null){
RandomListNode clone=cur.next;
if(cur.random!=null)
clone.random=cur.random.next;
cur=clone.next;
}
//拆分链表
RandomListNode newHead=pHead.next;//定义出来以后就不用动了
cur=newHead;
while(cur.next!=null){
RandomListNode next=cur.next.next;
cur.next=next;
cur=next;
}
return newHead;
}
}
LC141 环形链表
思路一:哈希表(思路非常清晰)
import java.util.*;
public class Solution {
public boolean hasCycle(ListNode head) {
Set set = new HashSet<>();
while(head!=null){
if(set.contains(head)){
return true;
}else{
set.add(head);
}
head=head.next;
}
return false;
}
}思路二:快慢指针
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null||head.next==null)
return false;
ListNode fast=head.next;//快指针得抢跑,不然刚进入循环就追上了
ListNode slow=head;
while(fast!=slow){
if(fast==null||fast.next==null)
return false;
fast=fast.next.next;
slow=slow.next;
}
return true;
}
}LC142 环形链表2(剑指offer23 链表中环的入口节点)
思路一:哈希表
public class Solution {
public ListNode detectCycle(ListNode head) {
Set set=new HashSet<>();
while(head!=null){
if(set.contains(head)){
return head;
}else{
set.add(head);
}
head=head.next;
}
return null;
}
}
public class Solution {
public ListNode detectCycle(ListNode head) {
Set visited = new HashSet();
ListNode node = head;
while (node != null) {
if (visited.contains(node)) {
return node;
}
visited.add(node);
node = node.next;
}
return null;
}
}思路二:快慢指针
注意:跟LC141不一样的是,两个指针是同起点出发的
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head==null||head.next==null)
return null;
ListNode fast=head;
ListNode slow=head;
//先判断是否有环
while(fast.next!=null&&fast.next.next!=null){
fast=fast.next.next;
slow=slow.next;
//如果相遇,可以进行第二步操作
if(fast==slow){
fast=head;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
//出循环的时候第二次相遇
return slow;
}
}
//出循环就说明没环
return null;
}
}LC160 相交链表(剑指52 两个链表的第一个公共节点)
哈希表法
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Set set=new HashSet<>();
while(headA!=null){
set.add(headA);
headA=headA.next;
}
while(headB!=null){
if(set.contains(headB))
return headB;
headB=headB.next;
}
return null;
}
}双指针法
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1=headA;
ListNode p2=headB;
while(p1!=p2){
if(p1==null)p1=headB;
else
p1=p1.next;
if(p2==null)p2=headA;
else
p2=p2.next;
}
return p1;
}
}LC19 删除链表的倒数第N个节点
不认真画图相当容易写错
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre=new ListNode(-1);
pre.next=head;
ListNode p1=pre;
ListNode p2=pre;
int flag=0;
while(p2!=null){
p2=p2.next;
if(flag>n)
p1=p1.next;
flag++;
}
if(flag<=n)
return head;
p1.next=p1.next.next;
return pre.next;
}
}待完成
移除链表元素
奇偶链表
回文链表
合并两个有序链表
两数相加
扁平化多级双向链表
复制带随机指针的链表
旋转链表