1、单链表的创建和遍历:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | public class LinkList { public Node head; public Node current; //方法:向链表中添加数据 public void add( int data) { //判断链表为空的时候 if (head == null ) { //如果头结点为空,说明这个链表还没有创建,那就把新的结点赋给头结点 head = new Node(data); current = head; } else { //创建新的结点,放在当前节点的后面(把新的结点合链表进行关联) current.next = new Node(data); //把链表的当前索引向后移动一位 current = current.next; //此步操作完成之后,current结点指向新添加的那个结点 } } //方法:遍历链表(打印输出链表。方法的参数表示从节点node开始进行遍历 public void print(Node node) { if (node == null ) { return ; } current = node; while (current != null ) { System.out.println(current.data); current = current.next; } } class Node { //注:此处的两个成员变量权限不能为private,因为private的权限是仅对本类访问。 int data; //数据域 Node next; //指针域 public Node( int data) { this .data = data; } } public static void main(String[] args) { LinkList list = new LinkList(); //向LinkList中添加数据 for ( int i = 0 ; i < 10 ; i++) { list.add(i); } list.print(list.head); // 从head节点开始遍历输出 } } |
上方代码中,这里面的Node节点采用的是内部类来表示(33行)。使用内部类的最大好处是可以和外部类进行私有操作的互相访问。
注:内部类访问的特点是:内部类可以直接访问外部类的成员,包括私有;外部类要访问内部类的成员,必须先创建对象。
为了方便添加和遍历的操作,在LinkList类中添加一个成员变量current,用来表示当前节点的索引(03行)。
这里面的遍历链表的方法(20行)中,参数node表示从node节点开始遍历,不一定要从head节点遍历。
2、求单链表中节点的个数:
注意检查链表是否为空。时间复杂度为O(n)。这个比较简单。
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //方法:获取单链表的长度 public int getLength(Node head) { if (head == null ) { return 0 ; } int length = 0 ; Node current = head; while (current != null ) { length++; current = current.next; } return length; } |
4、查找单链表中的中间结点:
同样,面试官不允许你算出链表的长度,该怎么做呢?
思路:
和上面的第2节一样,也是设置两个指针first和second,只不过这里是,两个指针同时向前走,second指针每次走两步,first指针每次走 一步,直到second指针走到最后一个结点时,此时first指针所指的结点就是中间结点。注意链表为空,链表结点个数为1和2的情况。时间复杂度为 O(n)。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //方法:查找链表的中间结点 public Node findMidNode(Node head) { if (head == null ) { return null ; } Node first = head; Node second = head; //每次移动时,让second结点移动两位,first结点移动一位 while (second != null && second.next != null ) { first = first.next; second = second.next.next; } //直到second结点移动到null时,此时first指针指向的位置就是中间结点的位置 return first; } |
上方代码中,当n为偶数时,得到的中间结点是第n/2 + 1个结点。比如链表有6个节点时,得到的是第4个节点。
判断单链表是否有环:
这里也是用到两个指针,如果一个链表有环,那么用一个指针去遍历,是永远走不到头的。
因此,我们用两个指针去遍历:first指针每次走一步,second指针每次走两步,如果first指针和second指针相遇,说明有环。时间复杂度为O (n)。
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //方法:判断单链表是否有环 public boolean hasCycle(Node head) { if (head == null ) { return false ; } Node first = head; Node second = head; while (second != null ) { first = first.next; //first指针走一步 second = second.next.next; second指针走两步 if (first == second) { //一旦两个指针相遇,说明链表是有环的 return true ; } } return false ; } |
完整版代码:(包含测试部分)
这里,我们还需要加一个重载的add(Node node)方法,在创建单向循环链表时要用到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | LinkList.java: public class LinkList { public Node head; public Node current; //方法:向链表中添加数据 public void add( int data) { //判断链表为空的时候 if (head == null ) { //如果头结点为空,说明这个链表还没有创建,那就把新的结点赋给头结点 head = new Node(data); current = head; } else { //创建新的结点,放在当前节点的后面(把新的结点合链表进行关联) current.next = new Node(data); //把链表的当前索引向后移动一位 current = current.next; } } //方法重载:向链表中添加结点 public void add(Node node) { if (node == null ) { return ; } if (head == null ) { head = node; current = head; } else { current.next = node; current = current.next; } } //方法:遍历链表(打印输出链表。方法的参数表示从节点node开始进行遍历 public void print(Node node) { if (node == null ) { return ; } current = node; while (current != null ) { System.out.println(current.data); current = current.next; } } //方法:检测单链表是否有环 public boolean hasCycle(Node head) { if (head == null ) { return false ; } Node first = head; Node second = head; while (second != null ) { first = first.next; //first指针走一步 second = second.next.next; //second指针走两步 if (first == second) { //一旦两个指针相遇,说明链表是有环的 return true ; } } return false ; } class Node { //注:此处的两个成员变量权限不能为private,因为private的权限是仅对本类访问。 int data; //数据域 Node next; //指针域 public Node( int data) { this .data = data; } } public static void main(String[] args) { LinkList list = new LinkList(); //向LinkList中添加数据 for ( int i = 0 ; i < 4 ; i++) { list.add(i); } list.add(list.head); //将头结点添加到链表当中,于是,单链表就有环了。备注:此时得到的这个环的结构,是下面的第8小节中图1的那种结构。 System.out.println(list.hasCycle(list.head)); } } |
检测单链表是否有环的代码是第50行。
88行:我们将头结点继续往链表中添加,此时单链表就环了。最终运行效果为true。
如果删掉了88行代码,此时单链表没有环,运行效果为false。
取出有环链表中,环的长度:
我们平时碰到的有环链表是下面的这种:(图1)
上图中环的长度是4。
但有可能也是下面的这种:(图2)
此时,上图中环的长度就是3了。
那怎么求出环的长度呢?
//方法:判断单链表是否有环。返回的结点是相遇的那个结点 public Node hasCycle(Node head) { if (head == null ) { return null ; } Node first = head; Node second = head; while (second != null ) { first = first.next; second = second.next.next; if (first == second) { //一旦两个指针相遇,说明链表是有环的 return first; //将相遇的那个结点进行返回 } } return null ; } //方法:有环链表中,获取环的长度。参数node代表的是相遇的那个结点 public int getCycleLength(Node node) { if (head == null ) { return 0 ; } Node current = node; int length = 0 ; while (current != null ) { current = current.next; length++; if (current == node) { //当current结点走到原点的时候 return length; } } return length; } |
完整版代码:(包含测试部分)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | public class LinkList { public Node head; public Node current; public int size; //方法:向链表中添加数据 public void add( int data) { //判断链表为空的时候 if (head == null ) { //如果头结点为空,说明这个链表还没有创建,那就把新的结点赋给头结点 head = new Node(data); current = head; } else { //创建新的结点,放在当前节点的后面(把新的结点合链表进行关联) current.next = new Node(data); //把链表的当前索引向后移动一位 current = current.next; //此步操作完成之后,current结点指向新添加的那个结点 } } //方法重载:向链表中添加结点 public void add(Node node) { if (node == null ) { return ; } if (head == null ) { head = node; current = head; } else { current.next = node; current = current.next; } } //方法:遍历链表(打印输出链表。方法的参数表示从节点node开始进行遍历 public void print(Node node) { if (node == null ) { return ; } current = node; while (current != null ) { System.out.println(current.data); current = current.next; } } //方法:判断单链表是否有环。返回的结点是相遇的那个结点 public Node hasCycle(Node head) { if (head == null ) { return null ; } Node first = head; Node second = head; while (second != null ) { first = first.next; second = second.next.next; if (first == second) { //一旦两个指针相遇,说明链表是有环的 return first; //将相遇的那个结点进行返回 } } return null ; } //方法:有环链表中,获取环的长度。参数node代表的是相遇的那个结点 public int getCycleLength(Node node) { if (head == null ) { return 0 ; } Node current = node; int length = 0 ; while (current != null ) { current = current.next; length++; if (current == node) { //当current结点走到原点的时候 return length; } } return length; } class Node { //注:此处的两个成员变量权限不能为private,因为private的权限是仅对本类访问。 int data; //数据域 Node next; //指针域 public Node( int data) { this .data = data; } } public static void main(String[] args) { LinkList list1 = new LinkList(); Node second = null ; //把第二个结点记下来 //向LinkList中添加数据 for ( int i = 0 ; i < 4 ; i++) { list1.add(i); if (i == 1 ) { second = list1.current; //把第二个结点记下来 } } list1.add(second); //将尾结点指向链表的第二个结点,于是单链表就有环了,备注:此时得到的环的结构,是本节中图2的那种结构 Node current = list1.hasCycle(list1.head); //获取相遇的那个结点 System.out.println( "环的长度为" + list1.getCycleLength(current)); } } |
运行效果:
如果将上面的104至122行的测试代码改成下面这样的:(即:将图2中的结构改成图1中的结构)
1 2 3 4 5 6 7 8 9 10 11 12 13 | public static void main(String[] args) { LinkList list1 = new LinkList(); //向LinkList中添加数据 for ( int i = 0 ; i < 4 ; i++) { list1.add(i); } list1.add(list1.head); //将头结点添加到链表当中(将尾结点指向头结点),于是,单链表就有环了。备注:此时得到的这个环的结构,是本节中图1的那种结构。 Node current = list1.hasCycle(list1.head); System.out.println( "环的长度为" + list1.getCycleLength(current)); } |
运行结果:
如果把上面的代码中的第8行删掉,那么这个链表就没有环了,于是运行的结果为0。
一,问题描述
请自己构造一个简单的有序单链表,然后实现删除链表中的重复结点。比如:
二,问题分析
首先要实现一个单链表,因此需要定义一个节点类Node。其次,实现向链表中添加结点的方法(使用尾插法)addNode
删除重复结点的实现思路:
定义两个指针:pre 和 next。初始时,pre指向链表中的第一个元素,next指向链表中的第二个元素。如果 pre 的值与 next 的值不相等,则两个指针分别都向后移一个结点;若相等,则删除 next 指针指向的结点即可。
三,整个代码实现
// delete duplicated nodes in increased listpublic class MyLinkedList { private class Node{ int ele; Node next; public Node(int ele) { this.ele = ele; next = null; } } private Node head; private Node tail; //采用尾插法添加结点 public void addNode(int ele){ Node newNode = new Node(ele); if(tail != null) tail.next = newNode; else{ // first node head = newNode; } tail = newNode; } //删除有序单链表中的重复结点 public void delDuplicatedNode(){ if(head == null) return; Node pre,next; pre = head; next = head.next; while(next != null) { if(pre.ele != next.ele) { pre = next; next = next.next; }else{ //delete next point node Node delNode = next; pre.next = next.next; next = next.next; delNode.next = null;//avoid memory leak// delNode = null; } } } @Override public String toString() { if(head == null) return "null"; Node current = head; StringBuilder sb = new StringBuilder(); while(current != null){ sb.append(current.ele + " "); current = current.next; } return sb.toString(); } //hapjin test public static void main(String[] args) { MyLinkedList mylist = new MyLinkedList(); int[] eles = {1,2,3,3,4,4,5}; for (int ele : eles) { mylist.addNode(ele); } System.out.println("before del: " + mylist); mylist.delDuplicatedNode(); System.out.println("after del: " + mylist); }}