链表(LinkedList)


JAVA版本

1 链表操作基本技能

  • null 异常处理
  • dummy node 哑节点
  • 快慢指针
  • 插入一个节点到排序链表
  • 从一个链表中移除一个节点
  • 翻转链表
  • 合并两个链表
  • 找到链表的中间节点

1.1 单链表结构

	/*
	 * 链表节点
	 */
	public static class Node{
		int data;
		Node next;
		Node(int data){
			this.data = data;
		}
	}

1.2 链表插入元素

	private Node head;//头节点指针
	private Node last;//尾节点指针
	private int size;//链表实际长度
	
	/*
	 * 链表插入节点
	 * @param data 插入元素
	 * @param index 插入位置
	 */
	public void insert(int data,int index) {
		if(index < 0 || index > size) {
			throw new IndexOutOfBoundsException("超出链表节点范围!");
		}
		Node insertedNode = new Node(data);
		if(size == 0) { //空链表
			head = insertedNode;
			last = insertedNode;
		}else if(index ==0) {//插入头部
			insertedNode.next = head;
			head = insertedNode;
		}else if(size ==index) {//插入尾部
			last.next = insertedNode;
			last = insertedNode;
		}else {//插入中间
			Node preNode = get(index-1);
			insertedNode.next = preNode.next;
			preNode.next = insertedNode;
		}
		size++;
	}

1.3 链表删除元素

	/*
	 * 链表删除元素 
	 * @param index 删除位置
	 * @return
	 */
	public Node remove(int index) {
		if(index < 0 || index > size) {
			throw new IndexOutOfBoundsException("超出链表节点范围!");
		}
		Node removeNode = null;
		if(index == 0) { //删除头节点
			removeNode = head;
			head = head.next;
		}else if(index==size-1) {//删除尾节点
			Node preNode = get(index-1);
			removeNode = preNode.next;
			preNode.next = null;
			last = preNode;
		}else {//删除中间节点
			Node preNode = get(index-1);
			Node nextNode = preNode.next.next;
			removeNode = preNode.next;
			preNode.next = nextNode;
		}
		size--;
		return removeNode;
	}

1.4 链表查找元素

	/*
	 * 链表查找元素
	 * @param index 查找的位置
	 * @return
	 */
	private Node get(int index) {
		if(index < 0 || index > size) {
			throw new IndexOutOfBoundsException("超出链表节点范围!");
		}
		Node temp = head;
		for(int i =0;i < index;i++) {
			temp = temp.next;
		}
		return temp;
	}

1.5 创建一个链表

    //创建链表
	public ListNode createList(int[] arr) {
		if(arr.length==0) {
			return null;
		}
		ListNode head = new ListNode(arr[0]);
		ListNode cur = head;
		for(int i = 1;i <arr.length;i++) {
			cur.next = new ListNode(arr[i]);
			cur = cur.next;
		}
		return head;
	}

1.6 输出一个链表

	/*
	 * 输出链表
	 */
	public void output() {
		Node temp = head;
		while(temp!= null) {
			System.out.print(temp.data);
			temp = temp.next;
		}
	}

2 链表常见题目

2.1 链表反转

2.1.1 (lee-206) 反转链表
	/**
 	 * (lee-206)反转链表
 	 * 输入:head = [1,2,3,4,5]
 	 * 输出:[5,4,3,2,1]
	 * 翻转一个单链表
	 * 思路:头插法
	 */
	public static class Solution{
		public ListNode reverseList(ListNode head) {
			if(head == null || head.next == null) {
				return head;
			}
			ListNode pre = null;
			ListNode cur = head;
			while(cur != null) {
				ListNode temp = cur.next;
				cur.next = pre;
				pre = cur;
				cur = temp;			
			}
			return pre;			
		}
	}
2.1.2 (lee-92) 反转链表2
	/*
	 * (lee-92)反转链表2
 	 * 输入:head = [1,2,3,4,5], left = 2, right = 4
	 * 输出:[1,4,3,2,5]
	 * 反转从位置m到n的链表。请使用一趟扫描完成反转。
	 * 思路:先遍历到m处,翻转,再拼接后续,注意指针处理
	 */
	public static class Solution{
		public ListNode reverseBetween(ListNode head,int m,int n) {
			List_Reverse lr = new List_Reverse();
			ListNode dummy = lr.new ListNode(-1); //定义哑结点指向链表的头部
			dummy.next = head;
			ListNode pre = dummy; //pre指向要翻转子链表的第一个节点
			for(int i = 0;i < m-1;i++) {
				pre=pre.next;
			}
			ListNode cur = pre.next; //head指向翻转子链表的首部
			ListNode temp;
			for(int i = m;i < n;i++) {//翻转子链表
				temp = 	cur.next;		//定义temp指向子链表头结点的下一节点
				cur.next = temp.next; 	//head节点连接temp节点之后链表部分,也就是向后移动一位
				temp.next = pre.next;  	//temp节点移动到需要反转链表部分的首部
				pre.next = temp;       	//pre继续为需要反转头节点的前驱节点
			}
			return dummy.next;
		}
	}

2.2 链表删除

2.2.1 (lee-83) 删除排序链表中的重复元素

非递归解法

	/*
	 * 题型1:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 
	 * 非递归解法
	 * @param head
	 * @return
	 */
	public static class Solution{
		public ListNode deleteDuplicates(ListNode head) {
			ListNode cur = head;
			while(cur != null && cur.next != null) {
				if(cur.val==cur.next.val) {
					cur.next = cur.next.next;
				}else {
					cur = cur.next;
				}
			}
			output(head);
			return head;
		}
	}

递归解法

	public static class Solution{
		public ListNode deleteDuplicates(ListNode head) {
			if(head==null || head.next==null) {
				return head;
			}
			head.next = deleteDuplicates(head.next);
			if(head.val==head.next.val) {
				return head.next;
			}else {
				return head;
			}
			//return hea.val==head.next.val : head.next ? head;
		}
	}
	
2.2.2 (lee-82) 删除排序链表中的重复元素2

非递归解法

	/*
	 * 题型2:给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中没有重复出现的数字。
	 * 思路:链表头结点可能被删除,所以用 dummy node 辅助删除
	 * 非递归法
	 * 注意空节点的判断!!
	 */
	public static class Solution{
		public ListNode deleteDulicates(ListNode head) {
			if(head==null || head.next==null) {
				return head;
			}
			List_Remove lr = new List_Remove();
			ListNode dummy = lr.new ListNode(-1); //由于链表的头节点可能会被删除,因此额外使用一个哑节点(dummy node)
			dummy.next = head;      	//哑节点的next指向链表的头节点
			ListNode cur = dummy;       //定义当前指针指向链表的哑节点
			while(cur.next != null && cur.next.next  != null) {
				if(cur.next.val == cur.next.next.val) {
					int x  = cur.next.val;
					while(cur.next != null && cur.next.val == x) {
						cur.next = cur.next.next;
					}
				}else {                 //如果当前cur.next与cur.next.next对应的元素不相同,说明链表中只有一个元素值为cur.next的节点
					cur = cur.next;     //将cur指向cur.next
				}
			}
			return dummy.next;		
		}
		
	}

递归解法

	public static class Solution{
		public ListNode deleteDuplicates(ListNode head) {
			if(head==null || head.next==null) {
				return head;
			}
			if(head.val != head.next.val) {
				head.next = deleteDuplicates(head.next);
				return head;
			}else {
				int x = head.val;
				while(head != null && head.val==x) {
					head = head.next;
				}
				if(head==null) {
					return null;
				}else {
					head = deleteDuplicates(head);
					return head;
				}
			}	
		}
	}
2.2.3 (lee-19) 删除链表的倒数第N个节点
/*
 * (lee-19)删除链表的倒数第N个节点
 * 输入:head = [1,2,3,4,5], n = 2
 * 输出:[1,2,3,5]
 */
public class List_RemoveNthFromEnd {
	class ListNode{
		int val;
		ListNode next ;
		ListNode (int x){
			val = x;
		}
	}
	
	/**
	 * 删除链表的倒数第N个节点
	 * 思路1:快慢指针法:先让快指针走n步,然后慢指针开始从头结点走
	 * 思路2:先求出链表的长度,然后找到要删除节点的前一个节点,然后让该节点的next指向要删除节点的next.
	 * 注意:删除的节点是头结点的情况
	 * @param head
	 * @param n
	 * @return
	 */
	public ListNode removeNthFromEnd (ListNode head, int n) {
        ListNode slow = head;
        ListNode fast = head;
        for(int i = 0;i <n;i++) {
        	fast = fast.next;
        }
        if(fast==null) {
        	return head.next;
        }
        while(fast.next!= null) {
        	fast = fast.next; 
        	slow = slow.next;
        }
        slow.next = slow.next.next;
		return head;
        
    }
	
	public ListNode removeNthFromEnd2 (ListNode head, int n) {
		ListNode pre = head;
		int last = length(head)-n;
		if(last==0) {   //删除头结点的情况
			return head.next;
		}
		for(int i = 0;i <last-1;i++) {//找到要删除链表的第一个节点
			pre = pre.next;
		}
		pre.next = pre.next.next; //让前一个节点的next指向要删除节点的next
		return head;
	}
	
	//求链表的长度
	public int length(ListNode head) {
		int len = 0;
		while(head!= null) {
			len++;
			head = head.next;
		}
		return len;
	}
	
	public static void main(String[] args) {
		List_RemoveNthFromEnd lr = new List_RemoveNthFromEnd();
		ListNode n1 = lr.new ListNode(1);
		ListNode n2 = lr.new ListNode(2);
		ListNode n3 = lr.new ListNode(3);
		n1.next = n2;
		n2.next = n3;
		ListNode head = lr.removeNthFromEnd2(n1, 1);
		System.out.print(head.val);
	}
}

2.3 链表合并

2.3.1 (lee-21) 合并两个有序链表

递归解法
时间复杂度:O(n+m)其中n和m分别为两个链表的长度;
空间复杂度:O(n+m)

	public static class Solution{
		/**
		 * 合并链表:将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
		 * 1.递归解法(注意边界)
		 * 输入:l1 = [1,2,4], l2 = [1,3,4]
		 * 输出:[1,1,2,3,4,4]
		 * @param l1
		 * @param l2
		 * @return
		 */
		public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
			if(l1==null) {
				return l2;
			}else if(l2==null) {
				return l1;
			}else if(l1.val <= l2.val) {
				l1.next  = mergeTwoLists(l1.next, l2);
				return l1;
			}else {
				l2.next = mergeTwoLists(l1, l2.next);
				return l2;
			}
			
		}
	}

迭代解法
时间复杂度:O(n+m)其中n和m分别为两个链表的长度;
空间复杂度:O(1)只需要常数的空间存放若干变量。

	public static class Solution{
		/**
		 * 迭代法
		 * @param l1
		 * @param l2
		 * @return
		 */
		public ListNode mergeTwoLists(ListNode l1,ListNode l2) {
			List_Merge lm = new List_Merge();
			ListNode dummy = lm.new ListNode(-1);
			ListNode pre = dummy;
			while(l1 != null && l2 != null) {
				if(l1.val <= l2.val) {
					pre.next = l1;
					l1 = l1.next;
				}else {
					pre.next = l2;
					l2 = l2.next;
				}
				pre = pre.next;
			}
			pre.next = l1 == null ? l2 : l1; // 合并后l1和l2最多只有一个还未被合并完,直接将链表末尾指向未合并完的链表即可
			return dummy.next;
		}
	}
2.3.2 (lee-23) 合并K个升序链表

时间复杂度:O(kn * logk) 第一轮合并k/2组链表,每组时间代价为O(2n);第二轮合并k/4组链表,每一组为O(4n)…
空间复杂度:O(logk)递归用到

	/**
	 * 扩展:合并K个链表
	 * 思路:先分治法(归并),在递归调用合并两个链表的方法
	 * 输入:lists = [[1,4,5],[1,3,4],[2,6]]
     * 输出:[1,1,2,3,4,4,5,6]
	 */
	public static class Solution{
		
		public ListNode mergeKLists(ArrayList<ListNode> lists) {
			return merge(lists,0,lists.size()-1);
	        
	    }
		//归并(分治法)
		private ListNode merge(ArrayList<ListNode> lists, int left, int right) {
			if(left==right) {
				return lists.get(left);
			}
			if(left > right) {
				return null;
			}
			int mid = (left+right)/2;
			return mergeTwoList(merge(lists,left,mid), merge(lists, mid+1, right));
		}

		//合并两个链表(递归法),也可以使用迭代法
		public ListNode mergeTwoList(ListNode l1,ListNode l2) {
			if(l1==null) {
				return l2;
			}else if(l2==null) {
				return l1;
			}else if(l1.val <= l2.val) {
				l1.next = mergeTwoList(l1.next, l2);
				return l1;
			}else {
				l2.next = mergeTwoList(l1, l2.next);
				return l2;		
			}	
		}
	}

2.4 链表分隔

2.4.1 (lee-86) 分隔链表

以下为本地编辑器所有代码,包含输入输出,核心方法为partition方法

/*
 * (lee-86)分隔链表
 * 输入:head = [1,4,3,2,5,2], x = 3
 * 输出:[1,2,2,4,3,5]
 */
public class List_Partition {

	class ListNode{
		ListNode next;
		int val;
		ListNode (int x){
			val = x;
		}
	}
	
	/**
	 * 题目:给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于x的节点都在大于或等于x的节点之前。
	 * 思路:维护两个链表small和large,small按顺序存储小于x的节点,large链表按顺序存储大于x的节点;
	 * 遍历完原链表后,将small链表的尾节点指向large链表的头节点,完成分隔
	 * 注意:定义哑结点,目的是为了更方便地处理头节点为空的边界条件
	 * @param head
	 * @param x
	 * @return
	 */
	public ListNode partition(ListNode head,int x) {
		ListNode smallHead = new ListNode(-1);//第一个链表的哑结点
		ListNode small = smallHead;
		ListNode largeHead = new ListNode(-1); //第二个链表的哑结点
		ListNode large = largeHead;
		while(head!=null) {
			if(head.val < x) { //当前节点值 < x,则small的next指针指向该节点
				small.next = head;
				small = small.next;
			}else {            //否则,将large的next指针指向该节点
				large.next = head;
				large = large.next;
			}
			head = head.next;
		}
		large.next = null; //遍历结束后,将large的next指针置空,因为当前节点复用的是原链表的节点,而其next指针可能指向一个小于x的节点,需要切断这个引用.
		small.next = largeHead.next; //同时将small的next指针指向largeHead的next指针指向的节点,即真正意义上的large链表的头节点.
		return smallHead.next;
	}
	
	
	public static void main(String[] args) {
		List_Partition lp = new List_Partition();
		int[] arr = new int[] {1,4,3,2,5,2};
		ListNode head = lp.createList(arr, 6);
		lp.output(head);
		ListNode res = lp.partition(head, 3);
		lp.output(res);
	}
	
	/*
	 * 输出链表
	 */
	public void output(ListNode head) {
		ListNode temp = head;
		while(temp!= null) {
			System.out.print(temp.val+" -> ");
			temp = temp.next;
		}
		System.out.print("NULL");
		System.out.println();
	}
	/*
	 * 创建一个链表
	 */
	public ListNode createList(int[] arr,int n) {
		if(n==0) {
			return null;
		}
		ListNode head = new ListNode(arr[0]);
		ListNode cur = head;
		for(int i = 1;i < n;i++) {
			cur.next = new ListNode(arr[i]);
			cur = cur.next;
		}
		return head;
	}
}

2.5 环形链表

2.5.1 (lee-141) 环形链表
	/**
	 * 给定一个链表,判断链表中是否有环。如果链表中存在环,则返回 true。 否则,返回 false 。
	 * 思路:快慢指针——快慢指针相同则有环,如果有环每走一步快慢指针距离会减 1,最终重合。
	 * 输入:head = [3,2,0,-4], pos = 1
     * 输出:true
     * 解释:链表中有一个环,其尾部连接到第二个节点。
	 * @param head
	 * @return
	 */
	public boolean hasCycle(ListNode head) {
		if(head==null) {
			return false;
		}
		ListNode slow = head;
		ListNode fast = head.next;
		boolean flag = false;
		while(fast!= null && fast.next != null) { 
			slow = slow.next;
			fast = fast.next.next;
			if(slow == fast) { //指针比较时直接比较对象,不要用值比较,链表中有可能存在重复值情况
				flag = true;
				break;
			}else {
				flag = false;
			}
		}
		return flag;
	}
2.5.2 (lee-142) 环形链表 2
	/**
	 * 给定一个链表,返回链表开始入环的第一个节点。  如果链表无环,则返回 null。
	 * 思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点
	 * 输入:head = [3,2,0,-4], pos = 1
     * 输出:返回索引为 1 的链表节点
     * 解释:链表中有一个环,其尾部连接到第二个节点。
	 * @param head
	 * @return
	 */
	public ListNode detectCycle(ListNode head) {
		if(head==null || head.next==null) {
			return null;
		}
		ListNode slow = head;
		ListNode fast = head.next; //fast 如果初始化为 head.Next 则中点在 slow.Next
		while(fast!= null && fast.next != null) {
			if(slow==fast) { //第一次相遇,slow回到head, fast从相遇点下一个节点开始走
				slow = head; 
				fast = fast.next;
				while(slow != fast) { //第二次相遇的地方就是环的入口
					fast = fast.next;
					slow = slow.next;
				}
				return slow;
			}
			slow = slow.next;
			fast = fast.next.next;
		}
		return null;
	}

另外一种方式是 fast=head,slow=head
这两种方式不同点在于,一般用 fast=head.Next 较多,因为这样可以知道中点的上一个节点,可以用来删除等操作。

	public ListNode detectCycle2(ListNode head) {
		if(head==null || head.next==null) {
			return null;
		}
		ListNode slow = head;
		ListNode fast = head; //fast 如果初始化为 head 则中点在 slow
		while(fast!= null && fast.next!= null) {
			slow = slow.next;
			fast = fast.next.next;
			if(fast==slow) {   //第一次相遇,slow回到head, fast从相遇点下一个节点开始走
				slow = head;
				while(fast!= slow) {  //第二次相遇的地方就是环的入口
					fast = fast.next;
					slow = slow.next;
				}
				return slow;
			}
		}
		return null;	
	}

使用哈希表的方式

	/**
	 * 使用哈希表的方式
	 * @param head
	 * @return
	 */
	public ListNode detectCycle3(ListNode head) {
		ListNode pos = head;
		Set<ListNode> set = new HashSet<>();
		while(pos!= null) {
			if(set.contains(pos)) {
				return pos;
			}else {
				set.add(pos);
			}
			pos = pos.next;
		}
		return null;
	}

2.6 重排链表

以下为本地编辑器所有代码,包含输入输出,核心方法为reorderList方法

2.6.1 (lee-143) 重排链表
/*
 * (lee-143)重排链表
 * 给定一个单链表 L:L0→L1→…→Ln-1→Ln ,将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
 * 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
 * 给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3
 */
public class List_ReOrder {

	class ListNode{
		int val;
		ListNode next;
		ListNode(int x){
			val = x;
		}
	}
	/**
	 * 思路:找到链表中点断开,翻转后面部分,然后合并前后两个链表
	 * 知识点1:找到链表中点(快慢指针法)
	 * 知识点2:反转链表(使用迭代法)
	 * 知识点3:合并链表
	 * @param head
	 */
	public void reorderList(ListNode head) {
		if(head==null || head.next==null) {
			return;
		}
		ListNode slow = head;
		ListNode fast = head.next;
		//找到链表中点
		while(fast!=null && fast.next!= null) {
			slow = slow.next;
			fast = fast.next.next;
		}
		// 将后一半链表翻转, 用fast记录后一段的头结点
		fast = reverse(slow.next); 
		// 将链表切断
		slow.next = null;
		// 合并链表
		//mergeList(head,fast);
		mergeLists(head,fast);
    }
	
	/*
	 * 反转链表
	 * @param head
	 * @return
	 */
	private ListNode reverse(ListNode head) {
		ListNode pre = null;
		ListNode cur = head;
		while(cur!= null) {
			ListNode temp = cur.next;
			cur.next = pre;
			pre = cur;
			cur = temp;
		}
		return pre;
	}
	
	/*
	 * 合并两个链表
	 * @param l1
	 * @param l2
	 */
	private void mergeList(ListNode l1,ListNode l2) {
		ListNode head = l1;
		ListNode temp ;
		while(l1!=null && l2!=null) {
			temp = l2.next;
			l2.next  = l1.next;
			l1.next = l2;
			l2 = temp;
			l1 = l1.next.next;
		}
		if(l2!=null) {
			l1.next = l2;
		}
		l1 = head;
	}
	
	private void mergeLists(ListNode l1, ListNode l2) {
		//因为两链表长度相差不超过1,因此直接合并即可。
		ListNode l1_temp;
		ListNode l2_temp;
		while(l1 != null && l2!= null) {
			l1_temp = l1.next;
			l2_temp = l2.next;
			
			l1.next = l2;
			l1 = l1_temp;
			
			l2.next = l1;
			l2 = l2_temp;
		}
	}

	
	public static void main(String[] args) {
		List_ReOrder lr = new  List_ReOrder();
		int[] arr = new int[] {1,2,3,4};
		ListNode head = lr.createList(arr);
		lr.output(head);
		lr.reorderList(head);
		lr.output(head);
		
	}
	
	//输出链表
	public void output(ListNode head) {
		ListNode temp = head;
		while(temp != null) {
			System.out.print(temp.val+" -> ");
			temp = temp.next;
		}
		System.out.print("NULL");
		System.out.println();
	}
	//创建一个链表
	public ListNode createList(int[] arr) {
		if(arr.length==0) {
			return null;
		}
		ListNode head = new ListNode(arr[0]);
		ListNode cur = head;
		for(int i = 1;i <arr.length;i++) {
			cur.next = new ListNode(arr[i]);
			cur = cur.next;
		}
		return head;
	}
}

2.7 排序链表

2.7.1 (lee-148) 排序链表

以下为本地编辑器所有代码,包含输入输出,核心方法为sortList方法

/*
 * (lee-148)排序链表
 * 输入:head = [-1,5,3,4,0]
 * 输出:[-1,0,3,4,5]
 */
public class List_Sort {

	class ListNode{
		int val;
		ListNode next;
		ListNode(int x){
			val = x;
		}
	}
	/**
	 * 题目:排序链表【给你链表的头结点head ,请将其按升序排列并返回排序后的链表。在O(nlogn)时间复杂度和常数级空间复杂度下,对链表进行排序。】
	 * 思路:利用归并的思想,递归地将当前链表分为两段,然后merge。
	 * 分两段的方法使用 fast-slow 法【用两个指针,一个每次走两步,一个走一步,直到快指针走到了末尾,慢指针的所在位置就是中间位置,这样就分成了两段。】
	 * merge时可以采用递归或迭代的方法,注意边界值。
	 * 知识点1:归并排序的整体思想
     * 知识点2:找到一个链表的中间节点的方法
     * 知识点3:合并两个已排好序的链表为一个新的有序链表
	 * @param head
	 * @return
	 */
	public ListNode sortList(ListNode head) {
		//递归结束条件
		if(head==null || head.next==null) {
			return head;
		}
		ListNode slow = head;
		ListNode fast = head.next;
		//找到链表中点
		while(fast!=null && fast.next != null) {
			slow = slow.next;
			fast = fast.next.next;
		}
		ListNode rightHead = slow.next; //右部分的头节点指向链表中点的next节点,有前面知链表中点为慢指针所在位置
		slow.next = null;               //链表判断结束的标志:末尾节点.next==null,递归 mergeSort 需要断开中间节点
		ListNode left = sortList(head);        //对左部分进行排序
		ListNode right = sortList(rightHead);  //对右部分进行排序
		return mergeLists(left,right);         //迭代合并左右两部分
		//return mergeLists2(left, right);     //递归合并
		
    }
	
	//使用迭代法合并两个list
	private ListNode mergeLists(ListNode left, ListNode right) {
		ListNode dummy = new ListNode(-1);
		ListNode cur = dummy;
		while(left != null && right != null) {
			if(left.val < right.val) {
				cur.next = left;
				left = left.next;
			}else {
				cur.next = right;
				right = right.next;
			}
			cur = cur.next;
		}
		if(right != null) { // 合并后left和right最多只有一个还未被合并完,直接将链表末尾指向未合并完的链表即可
			cur.next = right;
		}else {
			cur.next = left;
		}		
		return dummy.next;
	}

	//使用递归法合并
	private ListNode mergeLists2(ListNode left,ListNode right) {
		if(left==null) {
			return right;
		}else if(right==null) {
			return left;
		}else if(left.val < right.val) {
			left.next = mergeLists2(left.next, right);
			return left;
		}else {
			right.next = mergeLists2(left, right.next);
			return right;
		}
	}

	public static void main(String[] args) {
		List_Sort ls = new List_Sort();
		int[] arr = new int[] {-1,5,3,4,0};
		ListNode head = ls.createList(arr, 5);
		//ls.output(head);
		ListNode res = ls.sortList(head);
		ls.output(res);
		
	}
	
	//输出
	public void output(ListNode head) {
		ListNode temp = head;
		while(temp!= null) {
			System.out.print(temp.val+" -> ");
			temp = temp.next;
		}
		System.out.print("NULL");
		System.out.println();
	}
	//创建链表
	public ListNode createList(int[] arr,int n) {
		if(n==0) {
			return null;
		}
		ListNode head = new ListNode(arr[0]);
		ListNode cur = head;
		for(int i =1;i < n;i++) {
			cur.next = new ListNode(arr[i]);
			cur = cur.next;
		}
		return head;
	}
}

2.8 回文链表

2.8.1 (lee-234) 回文链表

以下为本地编辑器所有代码,包含输入输出,核心方法为isPalindrome方法

/*
 * (lee-234)回文链表
 * 输入: 1->2->2->1
 * 输出: true
 */
public class List_Palindrome {

	class ListNode{
		int val;
		ListNode next;
		ListNode(int x){
			val = x;
		}
	}
	
	/**
	 * 请判断一个链表是否为回文链表。
	 * 思路:将链表分为两部分,反转后半部分链表,比较两部分链表是否相等
	 * 知识点1:找到链表中点(快慢指针法)
	 * 知识点2:反转链表
	 * 知识点3:比较链表相等
	 * @param head
	 * @return
	 */
	public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }
        ListNode slow = head, fast = head.next;
        // 找到链表中点
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode rightHead = slow.next;
        slow.next=null;
        ListNode right = reverse(rightHead); // 将后一半链表翻转        
        return isEqual(head, right); // 判断两半链表是否相等
    }
	
	
	//判断链表是否相等
    private boolean isEqual(ListNode l1, ListNode l2) {
        while (l1 != null && l2 != null) {
            if (l1.val != l2.val) {
                return false;
            }
            l1 = l1.next;
            l2 = l2.next;
        }
        return true;
    }
    
    //反转链表后半部分
    private ListNode reverse(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    } 

    //将链表分为两部分
    /*private void partition(ListNode head, ListNode cutNode) {
        while (head.next != cutNode) {
            head = head.next;
        }
        head.next = null;
     }*/
	
	public static void main(String[] args) {
		List_Palindrome lp  = new List_Palindrome();
		int[] arr = new int[] {1,2,3,1};
		ListNode head = lp.createList(arr);
		/*ListNode n1 = lp.new ListNode(1);
		ListNode n2 = lp.new ListNode(2);
		ListNode n3 = lp.new ListNode(2);
		ListNode n4 = lp.new ListNode(1);
		n1.next = n2;
		n2.next = n3;
		n3.next = n4;	*/	
		boolean b = lp.isPalindrome(head);
		System.out.print(b);
	}
	
	//创建一个链表
	public ListNode createList(int[] arr) {
		if(arr.length==0) {
			return null;
		}
		ListNode head = new ListNode(arr[0]);
		ListNode cur = head;
		for(int i = 1;i <arr.length;i++) { //注意:从1开始
			cur.next = new ListNode(arr[i]);
			cur = cur.next;
		}
		return head;
	}
}

2.9 两个链表相加

2.9.1 (lee-02) 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储一位数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807

	public class ListNode {
		 int val;
		 ListNode next;
		 ListNode(int x) { val = x; }
	}
	public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
		ListNode dummy = new ListNode(0);
		ListNode cur = dummy;
		int cnt = 0;
		while(l1 != null || l2 != null) {
			int x1 = l1 == null ? 0 : l1.val;
			int x2 = l2 == null ? 0 : l2.val;
			int sum = x1 + x2 + cnt;
			cnt = sum / 10;
			cur.next = new ListNode(sum % 10);
			cur = cur.next;
			l1 = l1 == null ? null : l1.next;
			l2 = l2 == null ? null : l2.next;
		}
		if(cnt > 0) {
			cur.next = new ListNode(cnt);
		}
		return dummy.next;
    }
2.9.2 (NC-40) 两个链表生成相加链表

假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。给定两个这种链表,请生成代表两个整数相加值的结果链表。

例如:链表 1 为 9->3->7,链表 2 为 6->3,最后生成新的结果链表为 1->0->0->0。

	public class ListNode {
		 int val;
		 ListNode next;
		 ListNode(int x) { val = x; }
	}
	public ListNode addInList (ListNode head1, ListNode head2) {       
        ListNode h1 = reverse(head1);
        ListNode h2 = reverse(head2);
        ListNode dummy = new ListNode(0);
		ListNode cur = dummy;
        int cnt = 0;
        while(h1!= null || h2!= null){
            int x1 = h1 == null ? 0 : h1.val;
            int x2 = h2 == null ? 0 : h2.val;
            int sum = x1+x2+cnt;
            cnt = sum/10;
            cur.next = new ListNode(sum%10);
            cur = cur.next;
            h1 = h1 == null ? null : h1.next;
            h2 = h2 == null ? null : h2.next;
        }
        if(cnt > 0){
            cur.next = new ListNode(cnt);
        }
        return reverse(dummy.next);
    }
     
    public ListNode reverse(ListNode head){
        ListNode pre = null;
        ListNode cur = head;
        while(cur!= null){
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
                
        }
        return pre;
    }	

2.10 双向链表 + hash表

2.10.1 (lee-146) LRU缓存机制

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:

LRUCache(int capacity) :以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) :如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) :如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

输入
[“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4

进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?

import java.util.HashMap;

/*
 * (lee-146) LRU缓存机制
 * 思路:双向链表 + hash表
 * 		双向链表按照被使用的顺序存储了这些键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的。
 * 		哈希表即为普通的哈希映射(HashMap),通过缓存数据的键映射到其在双向链表中的位置。
 * 时间复杂度:对于 put 和 get 都是 O(1)
 * 空间复杂度:O(capacity)
 */
public class LRUCache2 {

	class DLinkedNode{
		int key;
		int value;
		DLinkedNode prev;
		DLinkedNode next;
		public DLinkedNode() {}
		public DLinkedNode(int key,int value) {
			this.key = key;
			this.value = value;
		}
	}
	
	private int capacity;
	private int size;
	private HashMap<Integer,DLinkedNode> map;
	private DLinkedNode head;
	private DLinkedNode tail;
	
	public LRUCache2(int capacity) {
		this.capacity = capacity;
		this.size = 0;
		map = new HashMap<Integer,DLinkedNode>();
		head = new DLinkedNode();                              // 使用伪头部和伪尾部节点
		tail = new DLinkedNode();
		head.next = tail;
		tail.prev = head;
    }
    
    public int get(int key) {
    	DLinkedNode node = map.get(key);
    	if(node == null) {
    		return -1;
    	}
    	moveToHead(node);                                      // 如果 key 存在,先通过哈希表定位,再移到头部
    	return node.value;
    }   
    
	public void put(int key, int value) {
		DLinkedNode node = map.get(key);
		if(node == null) {                                       
			DLinkedNode newNode = new DLinkedNode(key, value);  // 如果 key 不存在,创建一个新的节点
			map.put(key, newNode);                              // 添加进哈希表
			addToHead(newNode);                                 // 添加至双向链表的头部
			++size;
			if(size > capacity) {                               // 如果超出容量,删除双向链表的尾部节点
				DLinkedNode tail = removeTail();                  
				map.remove(tail.key);                           // 删除哈希表中对应的项
				--size;
			}		
		}else {                                                 // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
			node.value = value;
			moveToHead(node);
		}
		
    }

	private void removeNode(DLinkedNode node) {
		node.prev.next = node.next;
		node.next.prev = node.prev;
	}
	
	private void moveToHead(DLinkedNode node) {
		removeNode(node);
		addToHead(node);
	}	

	private void addToHead(DLinkedNode node) {
		node.prev = head;
		node.next = head.next;
		head.next.prev = node;
		head.next = node;
	}
	
	private DLinkedNode removeTail() {
		DLinkedNode res = tail.prev;
		removeNode(res);
		return res;
	}

	
    public static void main(String[] args) {
    	LRUCache2 lru = new LRUCache2(2);
    	lru.put(1, 1);
    	lru.put(2, 2);
    	System.out.println(lru.get(1));
    	lru.put(3, 3);
    	System.out.println(lru.get(2));
    	lru.put(4, 4);
    	System.out.println(lru.get(1));
    	System.out.println(lru.get(3));
    	System.out.println(lru.get(4));
    }
}

3 练习链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值