1. 链表
2. 单链表和双链表的反转
2.1. 单链表
// 单链表public static class Node {int value;Node next;public Node(int value) {this.value = value;}}// 反转单链表public static Node reversalNode(Node head) {Node next = null;Node pre = null;while (head != null) {next = head.next; // 缓存下一个节点head.next = pre; // 更改当前节点的下一节点为反向节点pre = head; // 缓存当前节点为下一节点head = next; // 更新新的当前节点}return pre;}public static void main(String[] args) {// 单链表反转Node n1 = new Node(1);Node n2 = new Node(2);Node n3 = new Node(3);n1.next = n2;n2.next = n3;System.out.println(n1.value);System.out.println(n1.next.value);System.out.println(n1.next.next.value);System.out.println("=============");n1 = reversalNode(n1);System.out.println(n1.value);System.out.println(n1.next.value);System.out.println(n1.next.next.value);System.out.println("=============");}
2.2. 双链表
// 双链表public static class Node1 {int value;Node1 last;Node1 next;public Node1(int value) {this.value = value;}}// 双链表反转public static Node1 reversalNode1(Node1 head) {Node1 next = null;Node1 pre = null;while (head != null) {next = head.next; //记录下一个节点head.next = pre; //修改当前节点的右节点 为上次记录的head节点head.last = next; //修改当前节点的左节点pre = head; //记录当前节点head = next; //修改当前节点位置}return pre; //返回当前节点 即头节点}public static void main(String[] args) {// 双链表反转Node1 nn1 = new Node1(1);Node1 nn2 = new Node1(2);Node1 nn3 = new Node1(3);nn1.next = nn2;nn2.last = nn1;nn2.next = nn3;nn3.last = nn2;System.out.println(nn1.value);System.out.println(nn1.next.value);System.out.println(nn1.next.next.value);nn1 = reversalNode1(nn1);System.out.println("=============");System.out.println(nn1.value);System.out.println(nn1.next.value);System.out.println(nn1.next.next.value);}
3. 单链表实现栈和队列
3.1. 队列
public static class Node<V> {V value;Node<V> next;public Node(V value) {this.value = value;}}// 队列public static class MyQueue<V> {Node<V> head;Node<V> tail;int size;public MyQueue() {head = null;tail = null;size = 0;}public boolean isEmpty() {return size == 0;}public int size() {return size;}// 添加元素到队列中public void offer(V value) {Node<V> v = new Node<V>(value);if (tail == null) { // tail为null 则当前队列中没有元素 直接添加head = v; // 头尾同时指向 新添加元素tail = v;} else { // 当前队列有元素tail.next = v; // 更新尾元素的下一跳为添加的新元素地址tail = v; // 然后再更新尾元素 为添加元素地址}size++; // 长度+1}// 从队列中删除元素public V poll() {V ans = null;if (head != null) {ans = head.value; // 获取头部元素值head = head.next; // 将头部指向下一个元素size--;}if (head == null) { // 边界情况 当头部当前为null时 尾部也无元素tail = null;}return ans;}// 查看队头元素public V peek() {V ans = null;if (head != null) {ans = head.value;}return ans;}}
在上面删除的操作 java中有自己的回收器 因此我们无需手动回收无用对象 只需要把此对象改成不可达(即无法通过任何途径访问到此对象,则jvm会自动释放该对象)
3.2. 栈
public static class Node<V> {V value;Node<V> next;public Node(V value) {this.value = value;}}//栈public static class MyStack<V>{int size;Node<V> head;public MyStack() {head = null;size =0;}public boolean isEmpty() {return size ==0;}public int size() {return size;}//入栈public void pusu(V value) {Node<V> v = new Node<V>(value);if(head == null) { //栈中无元素head = v; //添加元素} else { //栈中有元素head.next = v; //压栈底head = v;//更新栈顶}size++;}//出栈public V pop() {V ans = null;if (head !=null) {ans = head.value; //获取栈顶值head = head.next; //更新栈顶元素为下个元素size--;}return ans;}//查询栈顶元素public V peek() {V ans = null;if(head !=null) {ans = head.value;}return ans;}}
4. 双链表实现双端队列
为什么无法使用单链表实现双端队列?
- 单链表支持从队头中增删查元素
- 单链表只支持从队尾增查元素,无法删除元素
因为无法从当前队尾中快速查找到上一跳的地址,只能遍历一次单链表。无法做到O(1),因为删除操作要遍历一次链表时间复杂程度为O(n) - 所以我们可以使用双链表可以完成O(1)的双端队列的增删查
public static class Node<V> {V value;Node<V> fist;Node<V> last;public Node(V value) {this.value = value;fist = null; // 上一跳last = null; // 下一跳}}public static class DoubleQueue<V> {Node<V> head;Node<V> tail;int size;public DoubleQueue() {head = null;tail = null;size = 0;}public boolean isEmpty() {return size == 0;}public int size() {return size;}// 从队列头添加指定元素public void addFist(V value) {Node<V> v = new Node<V>(value);if (head == null) { // 当前队列为空 直接添加head = v;tail = v;} else { // 从队头添加head.fist = v; // 原队头上一跳 标记为当前元素v.last = head; // 当前元素 下一跳为旧队头head = v; // 队头为当前元素}size++;}// 从队列尾添加指定元素public void addLast(V value) {Node<V> v = new Node<V>(value);if (head == null) {head = v;tail = v;} else {tail.last = v; // 旧队尾下一跳为 添加元素v.fist = tail; // 添加元素的上一跳 为旧队尾tail = v; // 更新队尾}size++;}// 删除队头元素public V pollFirst() {V ans = null;if (head != null) {ans = head.value;size--;}if (head == tail) { // 边界情况 只有一个元素head = null;tail = null;} else {head = head.fist;head.fist = null; // 将head上一跳标记为null}return ans;}// 删除队尾元素public V pollLast() {V ans = null;if (head != null) { // 有元素ans = tail.value; // 取当前队尾出值size--;}if (tail == tail) { // 边界情况 只有一个元素head = null;tail = null;} else {tail = tail.fist; // 更新队尾tail.last = null; // 当tail不为null 才将tall下一跳标记为null}return ans;}// 查看队头public V peekFist() {V ans = null;if (head != null) {ans = head.value;}return ans;}// 查看队尾public V peekLast() {V ans = null;if (tail != null) {ans = tail.value;}return ans;}}
5. leecode 链表题
5.1. K 个一组翻转链表
public class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) {this.val = val;}ListNode(int val, ListNode next) {this.val = val;this.next = next;}}public class Solution {public ListNode reverseKGroup(ListNode head, int k) {ListNode start = head; // 缓存当前链表头ListNode end = getKEnd(start, k); // 获取第一组链表尾if (end == null) { // 如果k个元素内没有链尾 说明第一组链表<=k个元素 无法进行反转 直接返回当前链表即可return head;}// 第一组满足k个元素进行反转head = end; // 将当前链表头指向链表尾的元素reverse(start, end); // 进行反转ListNode lastEnd = start; // 上一组节点尾while (lastEnd.next != null) {start = lastEnd.next; // 缓存当前组的链表头end = getKEnd(start, k); // 获得当前组的链表尾if (end == null) {return head;}reverse(start, end); // 满足k个 反转当前组lastEnd.next = end; // 将上组的链尾 指向 当前组反转后的链表头lastEnd = start; // 更新lastEnd为当前组的链尾}return head;}public ListNode getKEnd(ListNode pre, int k) {while (--k != 0 && pre != null) { // 返回当前组的最后一个元素(即新的链头)pre = pre.next;}return pre;}public void reverse(ListNode start, ListNode end) {end = end.next; // 将当前end向后移一位(即下一组的链头,下一组未反转)ListNode cur = start; // 当前元素ListNode pre = null; // 缓存上次的修改过元素ListNode next = null; // 下一跳元素while (cur != end) {next = cur.next; // 缓存下一跳cur.next = pre; // 指向上次修改后的元素pre = cur; // 更新修改后的元素cur = next; // 更新当前元素}start.next = end; // 将当前组链尾 指向下一组的链头}}
5.2. 两数相加
public class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) {this.val = val;}ListNode(int val, ListNode next) {this.val = val;this.next = next;}}class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {ListNode l = getSize(l1) > getSize(l2) ? l1 : l2; // 最长的链表ListNode s = l == l1 ? l2 : l1; // 第二个链表 <= l的长度int temp = 0; // 进位标记 如果为1则需要进1 0则不进1int cou = 0; // 存储每次计算结果ListNode tl = l; // 缓存当前长节点 元素ListNode ts = s; // 缓存当前短节点 元素ListNode pre = null;// 处理短的链表while (ts != null) {cou = ts.val + tl.val + temp; // 相加结果temp = cou / 10; // 是否需要进1tl.val = cou % 10; // 存储到节点中pre = tl; // 缓存当前长的节点元素 因为最后进1 需要使用该缓存节点下一跳 指向新的节点// 为何要在长的和短的链表 中记录当前节点呢 因为有可能短和长的链表长度一致 短的跑完长也跑完 又需要进1 会发生空指针异常tl = tl.next; // 下一个节点ts = ts.next;}// 处理长的链表while (tl != null) {cou = tl.val + temp;temp = cou / 10;tl.val = cou % 10;pre = tl;tl = tl.next;}// 如果进位是1 则需要新创建节点if (temp != 0) {pre.next = new ListNode(1);}return l;}public int getSize(ListNode head) {int size = 0;while (head.next != null) {size++;head = head.next;}return size;}}
5.3. 合并两个有序链表
public class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) {this.val = val;}ListNode(int val, ListNode next) {this.val = val;this.next = next;}}class Solution {public ListNode mergeTwoLists(ListNode list1, ListNode list2) {if (list1 == null || list2 == null) { // 另外一个链表为空直接返回非空链表return list1 == null ? list2 : list1;}ListNode head = list1.val <= list2.val ? list1 : list2; // 头节点ListNode l = head.next; // 下一条ListNode s = head == list1 ? list2 : list1; // 另外一个链表ListNode pre = head;while (l != null && s != null) {if (l.val <= s.val) {pre.next = l;l = l.next;} else {pre.next = s;s = s.next;}pre = pre.next;}pre.next = l == null ? s : l;return head;}}
6. 单链表的相交节点
给定两个可能有环也可能无环的单链表,头节点head1和head2
请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交返回null
要求如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)
6.1. 如何判断单个链表是否有环
- 定义一个快指针步长为2,定义一个慢指针步长为1,两指针不断往下走除非遇到null(说明此链表无环)
- 两指针各走各的,直到两节点相遇,慢指针停下,快指针更变步长为1,并返回到head节点
- 两指针重新往下走,当两指针再次相遇则此为入环节点
//找到链表入环头节点 如果不成环返回nullpublic static Node getLoopNode(Node head) {if(head == null) {return null;}//定义一个快指针 步长为2Node fast = head.next.next;//定义一个慢指针 步长为1Node slow = head.next;//两指针相遇while(fast != slow) {if(fast.next.next == null || slow.next == null) {//其中一个指针到尾了 此链表不成环return null;}//往下走fast = head.next.next;slow = head.next;}//两指针相遇后 快指针返回原点 并且步长恢复为1fast = head;//此时两指针步长相等 两指针相遇时当前节点为 入环节点while(fast != slow) {fast = fast.next;slow = slow.next;}return fast; //返回任意一个指针都可以 此时两指针必定在同一节点上}
6.2. 两链表无环 获取两链表有相交头节点
- 判断两链表的最后一个节点是否相等,不相等则两链表无相交(各走各的),如相交必定尾节点相等
- 获取两个链表的各自长度,长的链表先让 head1.length - head2.length 步,然后两指针并行走,直到两节点相等,则此节点为相交节点
// 两链表无环 返回两链表相交头节点 如无返回nullpublic static Node noLoop(Node head1, Node head2) {if (head1 == null || head2 == null) {return null;}// 两节点各走各的末尾 并记录长度Node cur1 = head1;Node cur2 = head2;int n = 0; // 此为记录长度的while (cur1 != null) {cur1 = cur1.next;n++;}while (cur2 != null) {cur2 = cur2.next;n--;}if (cur1 != cur2) { // 两链表走到尾 查看两节点是否相等 不相等则无相交return null;}//cur1 = n > 0 ? head1 : head2; // 大于0则说明head1长 小于0则说明head2长cur2 = cur1 == head1 ? head2 : head1;n = Math.abs(n); // 变成绝对值 防止负数情况// 先让短链表 n步while (n != 0) {n--;cur1 = cur1.next;}// 两链表并行走 直到相遇while (cur1 != cur2) {cur1 = cur1.next;cur2 = cur2.next;}return cur1;}
6.3. 其中一个链表有环 另外一个链表无环 两链表不可能相交
此情况直接返回null
6.4. 两链表都有环 获取两链表相交头节点
此情况有三种分支
两链表没有相交节点,两个环各走各的
- 此分支下无相交节点 返回null
两链表相交节点在环之前,环为同一个(即loop1 == loop2)
- 此分支下,把入环节点视为尾结点去除掉,与两链表无环 获取两链表有相交头节点处理一致
两链表相交节点在环内部,但相交头节点有两个,此时返回head1或head2都对
- loop1 != loop2 ,loop1不断往下走,如果遇到自身则为分支1返回null,如果遇到loop2则为分支3,返回loop1即可
// 两链表为环链表 返回两链表相交头节点 如无返回nullpublic static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {Node cur1 = null;Node cur2 = null;//两相交节点 在环之前 先判断两环头是否相等if(loop1 == loop2) {// 两节点各走各的末尾 并记录长度cur1 = head1;cur2 = head2;int n = 0; // 此为记录长度的while (cur1 != loop1) {cur1 = cur1.next;n++;}while (cur2 != loop2) {cur2 = cur2.next;n--;}if (cur1 != cur2) { // 两链表走到尾 查看两节点是否相等 不相等则无相交return null;}//cur1 = n > 0 ? head1 : head2; // 大于0则说明head1长 小于0则说明head2长cur2 = cur1 == head1 ? head2 : head1;n = Math.abs(n); // 变成绝对值 防止负数情况// 先让短链表 n步while (n != 0) {n--;cur1 = cur1.next;}// 两链表并行走 直到相遇while (cur1 != cur2) {cur1 = cur1.next;cur2 = cur2.next;}return cur1;} else {//两链表 相交节点可能在环内 判断环内部是否有loop2(即链表2的入环头节点)cur1 = loop1.next;while(cur1 != loop1) {if(cur1 == loop2) {return loop1;}cur1 = cur1.next; //往下走}//环内走完 说明两环并不相交 返回nullreturn null;}}
6.5. 完整代码
public static class Node {public int value;public Node next;public Node(int data) {this.value = data;}}// 找到链表入环头节点 如果不成环返回nullpublic static Node getLoopNode(Node head) {if (head == null) {return null;}// 定义一个快指针 步长为2Node fast = head.next.next;// 定义一个慢指针 步长为1Node slow = head.next;// 两指针相遇while (fast != slow) {if (fast.next == null || fast.next.next == null) {// 其中一个指针到尾了 此链表不成环return null;}// 往下走fast = fast.next.next;slow = slow.next;}// 两指针相遇后 快指针返回原点 并且步长恢复为1fast = head;// 此时两指针步长相等 两指针相遇时当前节点为 入环节点while (fast != slow) {fast = fast.next;slow = slow.next;}return fast; // 返回任意一个指针都可以 此时两指针必定在同一节点上}// 两链表无环 返回两链表相交头节点 如无返回nullpublic static Node noLoop(Node head1, Node head2) {if (head1 == null || head2 == null) {return null;}// 两节点各走各的末尾 并记录长度Node cur1 = head1;Node cur2 = head2;int n = 0; // 此为记录长度的while (cur1 != null) {cur1 = cur1.next;n++;}while (cur2 != null) {cur2 = cur2.next;n--;}if (cur1 != cur2) { // 两链表走到尾 查看两节点是否相等 不相等则无相交return null;}//cur1 = n > 0 ? head1 : head2; // 大于0则说明head1长 小于0则说明head2长cur2 = cur1 == head1 ? head2 : head1;n = Math.abs(n); // 变成绝对值 防止负数情况// 先让短链表 n步while (n != 0) {n--;cur1 = cur1.next;}// 两链表并行走 直到相遇while (cur1 != cur2) {cur1 = cur1.next;cur2 = cur2.next;}return cur1;}// 两链表为环链表 返回两链表相交头节点 如无返回nullpublic static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {Node cur1 = null;Node cur2 = null;//两相交节点 在环之前 先判断两环头是否相等if(loop1 == loop2) {// 两节点各走各的末尾 并记录长度cur1 = head1;cur2 = head2;int n = 0; // 此为记录长度的while (cur1 != loop1) {cur1 = cur1.next;n++;}while (cur2 != loop2) {cur2 = cur2.next;n--;}if (cur1 != cur2) { // 两链表走到尾 查看两节点是否相等 不相等则无相交return null;}//cur1 = n > 0 ? head1 : head2; // 大于0则说明head1长 小于0则说明head2长cur2 = cur1 == head1 ? head2 : head1;n = Math.abs(n); // 变成绝对值 防止负数情况// 先让短链表 n步while (n != 0) {n--;cur1 = cur1.next;}// 两链表并行走 直到相遇while (cur1 != cur2) {cur1 = cur1.next;cur2 = cur2.next;}return cur1;} else {//两链表 相交节点可能在环内 判断环内部是否有loop2(即链表2的入环头节点)cur1 = loop1.next;while(cur1 != loop1) {if(cur1 == loop2) {return loop1;}cur1 = cur1.next; //往下走}//环内走完 说明两环并不相交 返回nullreturn null;}}public static Node getIntersectNode(Node head1, Node head2) {if(head1 == null || head2 == null ) {return null;}//找出两链表是否有环Node loop1 = getLoopNode(head1);Node loop2 = getLoopNode(head2);//两节点无环情况if(loop1 == null && loop2 == null) {return noLoop(head1,head2);}//两节点有环情况if(loop1 != null && loop2 !=null) {return bothLoop(head1, loop1, head2, loop2);}//两节点 其中一个有环情况 必定没有相交点return null;}public static void main(String[] args) {// 1->2->3->4->5->6->7->nullNode head1 = new Node(1);head1.next = new Node(2);head1.next.next = new Node(3);head1.next.next.next = new Node(4);head1.next.next.next.next = new Node(5);head1.next.next.next.next.next = new Node(6);head1.next.next.next.next.next.next = new Node(7);// 0->9->8->6->7->nullNode head2 = new Node(0);head2.next = new Node(9);head2.next.next = new Node(8);head2.next.next.next = head1.next.next.next.next.next; // 8->6System.out.println(getIntersectNode(head1, head2).value);// 1->2->3->4->5->6->7->4...head1 = new Node(1);head1.next = new Node(2);head1.next.next = new Node(3);head1.next.next.next = new Node(4);head1.next.next.next.next = new Node(5);head1.next.next.next.next.next = new Node(6);head1.next.next.next.next.next.next = new Node(7);head1.next.next.next.next.next.next = head1.next.next.next; // 7->4// 0->9->8->2...head2 = new Node(0);head2.next = new Node(9);head2.next.next = new Node(8);head2.next.next.next = head1.next; // 8->2System.out.println(getIntersectNode(head1, head2).value);// 0->9->8->6->4->5->6..head2 = new Node(0);head2.next = new Node(9);head2.next.next = new Node(8);head2.next.next.next = head1.next.next.next.next.next; // 8->6System.out.println(getIntersectNode(head1, head2).value);}
