链表
1. 反转单向链表
反转单向列表,需要两个辅助指针pre和cur
public class Solution_ReverseList {
public static class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
public static ListNode reverseList(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode pre = head;
ListNode cur = head.next;
head.next = null;
while(cur != null){
head = cur;
cur = cur.next;
head.next = pre;
pre = head;
}
return head;
}
public static void printList(ListNode head){
if(head == null){
return;
}
System.out.print(head.val);
while(head.next != null){
head = head.next;
System.out.print(head.val);
}
System.out.println();
}
public static void main(String[] args) {
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(5);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = null;
ListNode head = node1;
printList(head);
head = reverseList(head);
printList(head);
}
}
2. 反转双向链表
反转双向链表,也需要两个辅助指针,与反转单向链表类似
public class Solution_ReverseList2 {
public static class ListNode {
int val;
ListNode next;
ListNode pre;
ListNode(int x) { val = x; }
}
public static ListNode reverseList2(ListNode head) {
if(head == null || head.next == null){
return null;
}
ListNode cur = head.next;
ListNode lastCur = null;
head.next = null;
while(cur != null){
head.pre = cur;
head.next = lastCur;
lastCur = head;
head = cur;
cur = cur.next;
}
head.next = lastCur;
head.pre = null;
return head;
}
public static void printList(ListNode head){
if(head.next == null){
System.out.print(head.val + " ");
return;
}
System.out.print(head.val + " ");
while(head.next != null){
head = head.next;
System.out.print(head.val + " ");
}
System.out.println("---");
}
public static void main(String[] args) {
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
node1.pre = null;
node1.next = node2;
node2.pre = node1;
node2.next = node3;
node3.pre = node2;
node3.next = null;
ListNode head = node1;
printList(head);
head = reverseList2(head);
printList(head);
}
}
3. 打印两个有序链表的公共部分
题目:给定两个有序链表的头指针head1和head2,打印两个链表的公共部分。
public class Solution_PrintCommon {
public static class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val = val;
next = null;
}
}
public static void printCommon(ListNode head1, ListNode head2){
while(head1 != null && head2 != null){
if(head1.val < head2.val){
head1 = head1.next;
}else if(head1.val > head2.val){
head2 = head2.next;
}else{
System.out.print(head1.val + " ");
head1 = head1.next;
head2 = head2.next;
}
}
}
public static void main(String[] args) {
...
}
}
4. *判断一个链表是否为回文结构
示例1:
输入: 1->2 输出: false
示例2:
输入: 1->2->2->1 输出: true
要求:时间复杂度O(N),空间复杂度O(1)
思路:如果不要求时间复杂度为O(1),可以用一个栈来辅助,先遍历一遍,遍历到的元素压入栈。遍历完之后依次从栈中弹出元素,与链表比对。
为了保证空间复杂度为O(1),先找到链表的中间位置,然后将后半部分逆序。最后从两端开始往中间比对。
最后别忘了将后半部分恢复正常
public class Solution_IsPalindrome {
public static class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val = val;
this.next = null;
}
}
public static boolean isPalindrome(ListNode head){
if(head == null || head.next == null){
return true;
}
//记录原始头结点
ListNode first = head;
//使用两个指针,mid一次向后跳1,quick一次向后跳2。
// 当quick跳到链表终点,mid刚好跳到链表的中间部分。
ListNode mid = head;
ListNode quick = head;
while(quick.next != null && quick.next.next != null){
mid = mid.next;
quick = quick.next.next;
}
//反转后半部分的链表,返回最后一个元素节点。
head = mid;
ListNode head2 = reverse(head);
//从两端开始比较
head = first;
boolean result = compare(head, head2);
//将后半部分反转会初始的样子
reverse(head2);
return result;
}
//反转链表
public static ListNode reverse(ListNode head){
if(head == null || head.next == null){
return head;
}
ListNode pre = head;
ListNode cur = head.next;
head.next = null;
while(cur != null){
head = cur;
cur = cur.next;
head.next = pre;
pre = head;
}
return head;
}
//比较两个链表是否相等
public static boolean compare(ListNode head, ListNode head2){
while(head != null && head2 != null){
if(head.val == head2.val){
head = head.next;
head2 = head2.next;
}else{
return false;
}
}
return true;
}
public static void main(String[] args) {
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(3);
ListNode node6 = new ListNode(2);
ListNode node7 = new ListNode(1);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node6;
node6.next = node7;
node7.next = null;
ListNode head = node1;
boolean result = isPalindrome(head);
System.out.println(result);
}
}
5. *将链表按照某值划分成左边小,中间相等,右边大的形式
5.1 不考虑稳定性
解题思路:仿照荷兰国旗的partition操作, 直接将链表中的值保存到一个数组中,然后按照荷兰国旗的划分方式,划分完之后再用链表穿起来。但是这种方式不保证稳定性,而且额外空间复杂度为O(N)。
5.2 考虑稳定性
解题思路: 将链表划分成三个子链表,然后合并。
步骤:
- 建立三个子链表,分别用于存放小于、等于、大于K的节点。
- 为每个子链表建立两个辅助结点引用,头结点引用和为结点引用。头结点引用永远指向链表中第一个节点,尾结点引用永远指向最后一个。
- 遍历链表,遇到每个结点,都将其加到对应子链表的后面。
- 遍历完毕后,三个子链表头尾相连,即划分完毕。
额外空间复杂度为O(1),时间复杂度为O(N)。
public class Solution_PartitionByNum {
public static class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val = val;
}
}
public static ListNode partition(ListNode head, int k){
if(head == null || head.next == null){
return head;
}
//小于区域子链表的两个首尾结点引用
ListNode less = null;
ListNode lessEnd = null;
//等于区域子链表的两个首尾结点引用
ListNode equal = null;
ListNode equalEnd = null;
//大于区域子链表的两个首尾结点引用
ListNode more = null;
ListNode moreEnd = null;
ListNode headNext = null;
while(head != null){
headNext = head.next;
head.next = null;
if(head.val < k){
//第一个进入子链的情况与后进入的不同
//第一个进入子链,同时给该子链 头结点引用 和 尾结点引用 赋值
//之后进入子链的,只需更新 尾结点引用
if(less == null){
less = head;
lessEnd = head;
}else{
lessEnd.next = head;
lessEnd = lessEnd.next;
}
}else if(head.val == k){
if(equal == null){
equal = head;
equalEnd = head;
}else{
equalEnd.next = head;
equalEnd = equal.next;
}
}else{
if(more == null){
more = head;
moreEnd = more;
}else{
moreEnd.next = head;
moreEnd = moreEnd.next;
}
}
head = headNext;
}
//连接三个子链
lessEnd.next = equal;
equalEnd.next = more;
return less;
}
public static void main(String[] args) {
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(7);
ListNode node3 = new ListNode(4);
ListNode node4 = new ListNode(5);
ListNode node5 = new ListNode(3);
ListNode node6 = new ListNode(6);
ListNode node7 = new ListNode(2);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node6;
node6.next = node7;
ListNode head = partition(node1,5);
while(head != null){
System.out.print(head.val + " ");
head = head.next;
}
}
}
6. *复制含有随机指针节点的链表
题目: 一种特殊的单链表节点类描述如下
class Node { int value; Node next; Node rand; Node(int val) { value = val; } }
rand指针是单链表节点结构中新增的指针, rand可能指向链表中的任意一个节点, 也可能指向null。 给定一个由Node节点类型组成的无环单链表的头节点head, 请实现一个函数完成这个链表的复制, 并返回复制的新链表的头节点。
思路:
遍历链表,对于链表中的每一个节点A,新建一个节点为A’,此时A'
的next
和rand
都为空。
遍历完之后,将这些新建的节点一一连接起来,关键是得利用原链表之间的节点关系将新节点一一连接起来。
即使知道A.next
为B
,为了将A'
于B‘
连接起来,那么怎么把A'
与B'
都准确无误地找到?那就需要使用一个结构将A
于A'
绑在一起。
6.1 解法一:空间复杂度为O(N)
思路:使用哈希表,时间复杂度和空间复杂度都为O(N)
步骤:
- 先遍历一遍链表,链表中的每一个元素对应一个哈希表,其中的key为链表中的元素,value为该元素的复制。
例:如果node1为原链表中的节点,则map.get(node1)
为复制的新链表中的对应的节点。 - 再遍历一遍链表,将哈希表中对应的value节点连接起来。
public static Node function(Node head){
Node copyNode = null;
if(head == null)
return null;
if(head.next == null){//如果只有头结点,头结点的random指针有可能指向他自己
copyNode = new Node(head.val);
if(head.random == null){
copyNode.random = null;
}else {
copyNode.random = copyNode;
}
return copyNode;
}
Node cur = head;
HashMap<Node, Node> map = new HashMap<>();
while(cur != null){
map.put(cur, new Node(cur.value));
cur = cur.next;
}
cur = head;
//将每个新建的节点连接起来。
while(cur != null){
map.get(cur).next = map.get(cur.next);
map.get(cur).rand = map.get(cur.rand);
cur = cur.next;
}
//最后返回的是复制链表的头部
return map.get(head);
}
6.2 解法二:额外空间复杂度为O(1)
思路:利用链表的结构,将每一个节点的复制节点插在原节点的后面,这样就能轻松找到复制后的节点了。
步骤:
- 遍历链表,复制遇到的每一个节点为
copyNode
,并将其插入原节点后。 - 遍历链表,根据原链表中的
rand
指针,将每一个copyNode
的rand
指针赋值。 - 拆分链表,将所有
copyNode
抽离出来,并使用next
指针连接在一起。拆分链表不会破坏rand
指针。
public static Node function(Node head){
Node copyNode = null;
if(head == null)
return null;
if(head.next == null){//如果只有头结点,头结点的random指针有可能指向他自己
copyNode = new Node(head.val);
if(head.random == null){
copyNode.random = null;
}else {
copyNode.random = copyNode;
}
return copyNode;
}
Node cur = head;
Node curNext = null;
//遍历链表,复制遇到的每一个节点,并将其插入原节点后面
while(cur != null){
curNext = cur.next;
cur.next = new Node(cur.value);
cur.next.next = curNext;
cur = curNext;
}
//遍历链表,将上一步新建节点的rand指针连接起来。
cur = head;
while(cur != null){
copyNode = cur.next;
copyNode.rand = cur.rand != null ? cur.rand.next : null;
cur = cur.next.next;
}
//将链表拆成两条链
cur = head;
Node head2 = head.next;
while(cur != null){
curNext = cur.next.next;
copyNode = cur.next;
cur.next = curNext;
if(curNext != null){
copyNode.next = curNext.next;
}else{
copyNode.next = null;
}
cur = curNext;
}
return head2;
}
7. 两链表相交的一系列问题
题目: 在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 head1和head2,这两个链表可能相交,也可能不相交。请实现一个函数, 如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null 即可。
要求: 如果链表1的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外空间复杂度请达到O(1)。
7.1 判断单链表是否有环
leetcode - 环形链表:只需判断是否有环
leetcode - 环形链表II:返回入环的第一个节点
要求:如果有环,返回第一个入环的节点,如果无环返回空
方法一:使用哈希表判断,如哈希表之前,先判断该节点是否已在哈希表中
方法二:使用一个快指针(一次走两步),一个慢指针,都从头节点出发。两个指针相遇后,快指针回到头节点处,变成一次走一步,当和慢指针再次相遇,就是入环的第一个节点。
//判断链表是否有环
public class Solution_HasCycle {
public static class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val = val;
this.next = null;
}
}
//返回入环的第一个节点
public static ListNode hasCycle(ListNode head){
if(head == null || head.next == null){
return null;
}
//定义两个指针,一个快,每次加2,一个慢,每次加1
ListNode quick = head;
ListNode slow = head;
while(quick != null && quick.next != null){
quick = quick.next.next;
slow = slow.next;
if(quick == slow){//快慢指针相遇,说明有环
break;
}
}
//如果快指针为空,说明没有环
if(quick == null || quick.next == null){
return null;
}else{
quick = head;
while(true){
if(slow == quick){
return slow;
}
quick = quick.next;
slow = slow.next;
}
}
}
//不返回节点,返回是否有环
public static boolean hasCycle2(ListNode head){
if(head == null || head.next == null){
return false;
}
//定义两个指针,一个快,每次加2,一个慢,每次加1
ListNode quick = head;
ListNode slow = head;
while(quick != null && quick.next != null){
quick = quick.next.next;
slow = slow.next;
if(quick == slow){//快慢指针相遇,说明有环
return true;
}
}
return false;
}
}
7.2 判断两个无环单链表相交的第一个节点
方法一:使用哈希表,先将第一个链表放入哈希表中去,然后遍历第二条链表,判断第二条链表中的节点是否在哈希表中。
方法二:遍历两条链表,分别得到链表的长度和链表的最后一个节点。如果两条链表的最后一个节点相等,则两条链表相交。假设第二条链表比第一条链表长(n-m),那么第二条链表从头开始遍历n-m个长度,然后第一条链表从头开始遍历,总会遇到相同的节点。
方法二的代码:
public class Solution_Intersection {
public static class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val = val;
this.next = null;
}
}
public static ListNode getIntersection(ListNode headA, ListNode headB){
//两链表的长度
int lenA = 0, lenB = 0;
//定义两链表的遍历指针和最后一个节点
ListNode curA = headA;
ListNode lastA = null;
ListNode curB = headB;
ListNode lastB = null;
ListNode result = null;
while(curA != null){
lenA++;
if(curA.next == null){
lastA = curA;
}
curA = curA.next;
}
while(curB != null){
lenB++;
if(curB.next == null){
lastB = curB;
}
curB = curB.next;
}
//如果两个链表的最后一个节点相同,则肯定相交
if(lastA == lastB){
curA = headA;
curB = headB;
//两链表的长度差
int len = 0;
if(lenA > lenB){
result = search(lenA, lenB, curA, curB);
}else{
result = search(lenB, lenA, curB, curA);
}
}else{
return null;
}
return result;
}
public static ListNode search(int lenA, int lenB, ListNode curA, ListNode curB){
int len = lenA - lenB;
while(len > 0){
curA = curA.next;
len--;
}
while(curA != null && curB != null){
if(curA == curB){
return curA;
}else{
curA = curA.next;
curB = curB.next;
}
}
return curA;
}
public static void main(String[] args) {
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(5);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = null;
ListNode node6 = new ListNode(6);
node6.next = node5;
ListNode result = getIntersection(node1, node6);
System.out.println(result.val);
}
}
7.3 ***判断两个有环单链表相交的第一个节点
有环链表和无环链表不可能相交
两个有环链表的相交情况有以下三种:
-
两个有环链表不相交
分别找出两个有环链表的入环节点,然后从入环节点遍历第二个环,如果再次遍历到入环节点,没有遇到与第一个链表的入环节点相等的环,则两个链表不相交。
-
两个有环链表在环外相交
在环外相交,说明入环节点相同。将环截去,入环节点当做两条链表的最后一个节点,则问题转换成两个无环单链表的相交问题。
-
两个有环链表在环内相交
入环节点不同。让第一条有环链表遍历,直到遍历到有节点与第二条有环链表的入环节点相同,则证明两个链表相交。返回任意一条链表的入环节点都行。
求两条链表(链表可能有环,也可能无环)相交的第一个节点的代码如下
如果一直两个链表都有环,可将判断链表是否有环的5行if语句删除,不删也行。
//两个有环链表如果相交的话,返回第一个相交的节点,不相交返回null
public class Solution_IntersectionCycle {
public static class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val = val;
this.next = null;
}
}
public static ListNode function(ListNode head1, ListNode head2){
//先判断每一个链表的入环节点first
ListNode first1 = hasCycle(head1);
ListNode first2 = hasCycle(head2);
if(first1 == null && first2 == null){//两个无环链表
return getIntersection(head1, head2);
}else if(first1 == null || first2 == null){//一个无环,一个有环,不相交
return null;
}
//创建两个迭代指针
ListNode cur1 = head1;
ListNode cur2 = head2;
if(first1 == first2){//入环节点相同
//入环节点相同,则肯定在环外相交。可以将环截去,
// 转换成无环链表寻找第一个相交节点
//先将环截去,用以下两个指针分别记录入环节点的下一个位置,
// 以备将环添加上
ListNode temp1 = first1.next;
ListNode temp2 = first2.next;
first1.next = null;
first2.next = null;
ListNode result = getIntersection(cur1, cur2);
first1.next = temp1;
first2.next = temp2;
return result;
}else{//入环节点不同,可能相交,也可能不相交
//从first1下一个节点开始遍历,
// 如果再遍历到first1,没有发现与first2相等的节点,则不相交
// 如果还没转到first1,发现了与first2相等的节点,返回first1或first2都行
cur1 = first1.next;
while(true){
if(cur1 == first2){
return cur1;
}else if(cur1 == first1){
return null;
}else{
cur1 = cur1.next;
}
}
}
}
//返回入环的第一个节点
public static ListNode hasCycle(ListNode head){
if(head == null || head.next == null){
return null;
}
//定义两个指针,一个快,每次加2,一个慢,每次加1
ListNode quick = head;
ListNode slow = head;
while(quick != null && quick.next != null){
quick = quick.next.next;
slow = slow.next;
if(quick == slow){//快慢指针相遇,说明有环
break;
}
}
//如果快指针为空,说明没有环
if(quick == null || quick.next == null){
return null;
}else{
quick = head;
while(true){
if(slow == quick){
return slow;
}
quick = quick.next;
slow = slow.next;
}
}
}
//判断两个无环单链表相交的第一个节点
public static ListNode getIntersection(ListNode headA, ListNode headB){
//两链表的长度
int lenA = 0, lenB = 0;
//定义两链表的遍历指针和最后一个节点
ListNode curA = headA;
ListNode lastA = null;
ListNode curB = headB;
ListNode lastB = null;
ListNode result = null;
while(curA != null){
lenA++;
if(curA.next == null){
lastA = curA;
}
curA = curA.next;
}
while(curB != null){
lenB++;
if(curB.next == null){
lastB = curB;
}
curB = curB.next;
}
//如果两个链表的最后一个节点相同,则肯定相交
if(lastA == lastB){
curA = headA;
curB = headB;
//两链表的长度差
int len = 0;
if(lenA > lenB){
result = search(lenA, lenB, curA, curB);
}else{
result = search(lenB, lenA, curB, curA);
}
}else{
return null;
}
return result;
}
public static ListNode search(int lenA, int lenB, ListNode curA, ListNode curB){
int len = lenA - lenB;
while(len > 0){
curA = curA.next;
len--;
}
while(curA != null && curB != null){
if(curA == curB){
return curA;
}else{
curA = curA.next;
curB = curB.next;
}
}
return curA;
}
public static ListNode test1(){//两个有环链表不相交的情况
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(5);
ListNode node6 = new ListNode(6);
node1.next = node2;
node2.next = node3;
node3.next = node2;
node4.next = node5;
node5.next = node6;
node6.next = node5;
return function(node1, node4);
}
public static ListNode test2(){//两个有环链表相较于环外
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(5);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node2;
node5.next = node2;
return function(node1, node5);
}
public static ListNode test3(){//两个有环链表相较于环内
ListNode node1 = new ListNode(1);
ListNode node2 = new ListNode(2);
ListNode node3 = new ListNode(3);
ListNode node4 = new ListNode(4);
ListNode node5 = new ListNode(5);
ListNode node6 = new ListNode(6);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
node5.next = node2;
node6.next = node4;
return function(node1, node6);
}
public static void main(String[] args) {
ListNode result = test3();
if(result == null){
System.out.println("不相交");
}else{
System.out.println("相交于" + result.val);
}
}
}