链表(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));
}
}