文章结构
- 链表的概念/用处
- 链表的基本代码实现(韩顺平Java数据结构网课)
- 剑指offer上链表题目代码实现(个人手敲,更精巧的答案可以参考官网)
链表
链表包含单链表,双向链表,循环链表等等。相对于线性表,添加,删除操作非常方便,因为不用移动大量的节点,只需要修改对应的前后节点指针即可。下面用一个具体实例来说明下这种结构。现在有一需求,是将具有不同编号,姓名,昵称的人添加到系统中。首先需要创建节点,既然是链表,节点除了基本信息也要加入下一节点指针,方便计算机在内存中查找。
单向链表是通过指针构建的列表,基本结构就是头节点+下一节点地址指针--->节点+下一节点地址指针--->尾节点。
单链表的基本代码实现
在这里强推一波韩顺平的Java数据结构网课,讲的由浅入深,笔记详细。当然自己看视频之后自己再写才是正道。
https://www.bilibili.com/video/av54029771?from=search&seid=15096936792873170656www.bilibili.comclass HeroNode{public int no; //编号public String name; //姓名public String nickname;//昵称public HeroNode next; //下一节点指针//构造器public HeroNode(int no, String name, String nickname) {this.no = no;this.name = name;this.nickname = nickname; }@Overridepublic String toString() {return "HeroNode [no = " + no + ", name " + name + ", nickname = " + nickname;}}
下一步是创建链表类,包括一些基本操作方法
class SingleLinkedList{//初始化头节点,头节点不动private HeroNode head = new HeroNode(0, "", "");//返回头节点,方便后续操作public HeroNode getHead() {return head;}//添加节点内到单向链表//思路,当不考虑编号顺序,找到当前链表的最后节点,将最后节点的next指向新节点public void add(HeroNode heroNode) {HeroNode temp = head;//遍历链表,找到最后while(true) {if(temp.next == null) {break;}//如果没有找到,将temp后移temp = temp.next;}temp.next = heroNode;}//按照顺序添加public void addByOrder(HeroNode heroNode) {//头节点不能动,通过辅助指针//单链表,因此temp在添加位置的前一个结点HeroNode temp = head;boolean flag = false; //编号是否存在while(true) {if(temp.next == null) {//链表最后break;}if(temp.next.no > heroNode.no) {//位置找到break;}else if(temp.next.no == heroNode.no){//编号存在flag = true; }temp = temp.next;}if(flag) {System.out.printf("编号%d存在", heroNode.no);}else {heroNode.next = temp.next;temp.next = heroNode;}}//更新链表,在找到序号的情况下进行更新public void update(HeroNode newHeroNode) {//根据no修改if(head.next == null) {System.out.println("链表为空");return;}HeroNode temp = head.next;boolean flag = false;while(true) {if(temp == null) {break;}if(temp.no == newHeroNode.no) {flag = true;break;}temp = temp.next;}if(flag) {temp.name = newHeroNode.name;temp.nickname = newHeroNode.nickname;}else {System.out.printf("没找编号%d的值", newHeroNode.no);}}//删除节点//head不动,找到被删除节点的前一个public void del(int no) {HeroNode temp = head;boolean flag = false;while(true) {if(temp.next == null) {break;}if (temp.next.no == no) {flag = true;break;}temp = temp.next;}if(flag) {temp.next = temp.next.next;}else {System.out.printf("要删除的节点%d不存在", no);}} }
这里说下头节点存在的意义:
1.防止单链表是空的而设的,否则空链表头指针就会指向null.
2.方便插入表头或者删除第一个结点
剑指offer涉及到的链表题目
第一题:从尾到头打印链表
思路:递归,深入到最底层取出节点.val值后一层一层回退
import java.util.ArrayList;
public class Solution {ArrayList<Integer> list = new ArrayList<Integer>();public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {if(listNode != null){this.printListFromTailToHead(listNode.next);list.add(listNode.val);}return list;}
}
第二题:输入一个链表,输出该链表中倒数第k个结点。
思路1 传统法得到链表长度L,再从头遍历到L-k个
思路2 快慢指针,慢指针距离快指针始终k,当快指针到最后的null时候,输出慢指针
代码是思路2
public class Solution {public ListNode FindKthToTail(ListNode head,int k) {//快慢指针ListNode fast = head;ListNode slow = head;for(int i = 0; i < k; i++){if(fast == null){return null;} fast = fast.next;}while(fast != null){fast = fast.next;slow = slow.next;}return slow;}
}
第三题:输入一个链表,反转链表后,输出新链表的表头。
思路:本能反应是遍历到最后,倒数第二,第三以此类推,但是这样每次都遍历一遍链表花销太大。最清真的思路是采用头插法,先建立一个新的头节点,依次将链表中的节点插入到新头节点后边的第一个位置,以此类推。这里需要注意的是为了防止断链,要建立额外指针来储存当前插入点current在原链表的下一个节点。
题外话:韩老师的视频里面头节点是没有数据的,这个题目Head是有数据的,害得我提交很多次都不合格。下面这个是head无数据版本
public class Solution {public ListNode ReverseList(ListNode head) {if(head.next == null || head.next.next == null) {return head;}//辅助指针,遍历原来的链表ListNode cur = head.next.next;ListNode next = null; //指向cur的下一个节点 防止断链ListNode reverseHead = null;while( cur != null) {next = cur.next;//暂时保存后面有用cur.next = reverseHead.next;//cur的下一个节点指向新链表的最前端reverseHead.next = cur;//cur连接到新的链表最顶端cur = next;}return reverseHead;}
}
下面这个是head有数据版本
public class Solution {public ListNode ReverseList(ListNode head) {if(head==null)return null;ListNode pre = null;ListNode next = null;while(head!=null){next = head.next;head.next = pre;pre = head;head = next;}return pre;}
}
第四题:合并两个有序链表
/*
public class ListNode {int val;ListNode next = null;ListNode(int val) {this.val = val;}
}*/
public class Solution {public ListNode Merge(ListNode list1, ListNode list2) {if (list1 == null) {return list2;}if (list2 == null) {return list1;}ListNode temp = null;ListNode mergeHead = null;while (list1 != null && list2 != null) {if (list1.val < list2.val) {if (mergeHead == null) {mergeHead = list1;temp = list1;} else {temp.next = list1;temp = temp.next;}list1 = list1.next;} else {if (mergeHead == null) {mergeHead = list2;temp = list2;} else {temp.next = list2;temp = temp.next;}list2 = list2.next;}}if (list1 == null) {temp.next = list2;} else {temp.next = list1;}return mergeHead;}
}