单向链表:
是由一个个节点组成的,每个节点是一种信息集合,包含元素本身以及下一个节点的地址。
链表可以有头结点也可以没有,区别在于链表有头结点虽浪费空间,但易理解,边界好处理,不易出错
定义单链表及其操作
public class SingleList {
//定义一个节点内部类
class Node{
int data;
Node next;
public Node(int data){
this.data = data;
}
}
//头节点(头节点我们保持不动)
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;
}
}
//可以从node开始遍历,不需要从head开始遍历
public void print(Node node){
if(node == null){
return;
}
current = node;
while(current != null){
System.out.println(current.data);
current=current.next;
}
}
public static void main(String[] args) {
SingleList list = new SingleList();
list.add(1);
list.add(2);
list.add(3);
list.print(list.head);
}
}
如何将数组转换为链表:
将数组的第一个元素作为链表的头节点,current开始等于头结点,进入循环,将数组中的值赋值给节点,当前节点指向新节点,之后将新节点当作当前节点
public Node arrayToList(int[] arr){
if(arr.length==0){
return null;
}
Node head = new Node(arr[0]);
Node current=head;
for(int i=1;i<arr.length;i++){
Node node =new Node(arr[i]);
current.next=node;
current=node;
}
return head;
}
下面来看一些算法题:
例题1:查找单链表中的倒数第k个节点
思路1:先遍历出链表的长度n,倒数第k个节点在n-k+1
位置
public Node FindKthToTail (Node head, int k) {
int n=0;
current = head;
//第一次遍历得到长度n
while(current.next!=null){
n++;
current=current.next;
}
//第二次遍历,输出倒数第k个节点
current=head;
for(int i=0;i<n-k+1;i++){
current=current.next;
}
return current;
}
思路2:定义两个指针,慢指针和快指针初始化都为head,将快指针移动到第K个节点时,快慢指针同时后移,当快指针到达尾节点时,慢指针此时指向倒数第K个节点
public Node FindKthToTail (Node head, int k) {
Node slow=head;
Node fast=head;
for(int i=1;i<k;i++){
fast=fast.next;
}
while(fast.next!=null){
fast=fast.next;
slow=slow.next;
}
return slow;
}
例题2:链表反转
思路1:使用递归
public Node ReverseList(Node head) {
if(head==null || head.next==null){
return head;
}
Node nextNode = head.next;
Node newHead = ReverseList(nextNode);
nextNode.next=head;
head.next=null;
return newHead;
}
思路2:定义前后指针,将前后指针的指向反转
public Node ReverseList(Node head) {
if(head==null || head.next==null){
return head;
}
Node pre=head;
Node current=head.next;
Node temp;
while(current!=null){
temp=current.next;
current.next=pre;
pre=current;
current=temp;
}
head.next=null;
return pre;
}
例题3:环形链表
判断链表是否有环:设置两个快慢指针,慢指针以速度1移动,快指针以速度2移动。快慢指针如果相遇,说明链表有环
public boolean hasCycle(Node head){
if(head==null || head.next==null){
return false;
}
Node slow=head;
Node fast=head;
while(true){
if(fast==null || fast.next==null){
return false;
}
slow=slow.next;
fast=fast.next.next;
if(slow==fast){
return true;
}
}
}
找到环形链表的入口节点:
第一次相遇后,让fast重新指向头节点,然后fast和slow以相同速度移动,第二次相遇时,此节点即为入口节点
这个题目有点绕,题解就不写了
public Node EntryNodeOfLoop(Node head){
if(head==null || head.next==null){
return null;
}
Node slow=head;
Node fast=head;
while(true){
if(fast==null || fast.next==null){
return null;
}
slow=slow.next;
fast=fast.next.next;
if(slow==fast){
break;
}
}
fast=head;
while(true){
slow=slow.next;
fast=fast.next;
if(slow==fast) {
return slow;
}
}
}
例题4:合并两个排序的链表
递归:先找到两个链表更小的那个头节点,这个头结点指向后面更小的数
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null){
return list2;
}
if(list2==null){
return list1;
}
if(list1.val<list2.val){
list1.next=Merge(list1.next,list2);
return list1;
}else{
list2.next=Merge(list1,list2.next);
return list2;
}
}
用一个新链表来保存排序后的链表
public ListNode Merge(ListNode list1, ListNode list2) {
if (list1 == null) {
return list2;
}
if (list2 == null) {
return list1;
}
ListNode head = new ListNode(-1);
ListNode list3 = head;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
list3.next = list1;
list1 = list1.next;
} else {
list3.next = list2;
list2 = list2.next;
}
list3 = list3.next;
}
while (list1 != null) {
list3.next = list1;
list1 = list1.next;
list3 = list3.next;
}
while (list2 != null) {
list3.next = list2;
list2 = list2.next;
list3 = list3.next;
}
return head.next;
}