单链表:只有一个指向下一节点的指针 --> 单向读取
双链表:既有指向下一节点的指针,也有指向上一节点的指针,可以通过此向前查找
整个链表逆序、部分链表逆序
链表的反转需要换头(整个链表逆序)的操作,则需要返回值(需要一个Node类型的返回值,存储换头之后的head值)
单链表的逆序及部分链表的逆序
注:当head为整个链表的头结点时,pre初始值为null,next指针反转之后head头节点指向pre,也就是null
- package linkedlist;
-
- public class SingleLinkedList {
-
- class Node {
- public int value;
- public Node next;
-
- public Node(int data) {
- this.value = data;
- }
-
- }
-
- //单链表的反转
- //单链表的反转只需要将指针的方向反转,在逆序整个链表的时候,用head遍历链表的所有结点,使得每个指针由后一结点指向前一结点
- public static Node reverseList(Node head) {//head传入时是链表的头结点,在循环中为当前结点
- Node pre = null;//处理的结点的前一个结点
- Node next = null;//处理的结点的后一个结点
- while (head != null) {
- next = head.next;//head的后一个结点
- head.next = pre;//反转指针
- pre = head;
- head = next;
- }
- return pre;//出循环时head==null,pre为整个链表的最后一个结点,也就是链表反转之后的头
- }
-
- //部分单链表的反转
- //这里说部分反转应当是不需要返回值的,但是取的不同的start和end数值,可能会导致链表的头改变,需要返回值,因此返回
- public static Node reverselistPart(Node head, int start, int end) {//head为当前结点,starthe end为起始位置
- Node startNode = null;
- Node endNode = null;
- int length = 0;//因为链表与数组不同,不能够直到当前结点在整个链表之间的位置,需要计数
- Node pre = head;
-
- //遍历整个链表,此时pre指的是当前结点
- while (pre != null) {//获得整个链表的长度
- length++;
- //找到startNode节点的前一个结点
- if (length == start - 1) {
- startNode = pre;
- }
- //找到endNode节点的后一个节点
- if (length == end + 1) {
- endNode = pre;
- }
- pre = pre.next;//遍历,避免死循环
- }
-
-
- //判断所给的start和end是否合理
- if (start < 1 || end > length || start >= end) {
- return head;//不合理的数视为不对链表进行逆序
- } else if (startNode == null) {//start==1,反转的部分包含头结点
- pre = head;
- } else {
- pre = startNode.next;
- }
-
-
- //反转startNode和后一个结点
- //pre为开始结点的下一个结点,headNode为pre结点的下一个结点
- Node headNode = pre.next;
- pre.next = startNode;//next指针由pre指向startNode,实现指针的反转
-
- Node next = null;
-
-
- //反转从startNode后一个结点到endNode的前一个结点中所有的结点
- while (headNode != endNode) {//链表的反转
- next = headNode.next;
- headNode.next = pre;
- pre = headNode;
- headNode = next;
- }
-
- // if (startNode != null) {
- // startNode.next = pre;
- // return headNode;
- // }
-
- return pre;
- }
- }
-
-
双链表的逆序
双链表的逆序和单链表相同,但是需要反转pre和next两个指针
- package linkedlist;
-
- class DoubleNode {
- public DoubleNode(int data) {
- this.value = data;
- }
-
- public int value;
- public DoubleNode next;
- public DoubleNode pre;
-
- public static DoubleNode reverseList(DoubleNode head) {
- DoubleNode pre = null;
- DoubleNode next = null;
- while (head != null) {
- next = head.next;
- head.next = pre;
- head.pre = next;
- pre = head;
- head = next;
- }
- return pre;
- }
- }
-
1、将链表结构放到栈中,比较栈弹出的顺序是否与原序相同(栈先进后出),全部相同则是回文
O(n)的额外空间
- package linkedlist;
-
- import java.util.Stack;
-
- public class IsPalindromic {
- class Node {
- public int value;
- public Node next;
-
- public Node(int data) {
- this.value = data;
- }
- }
-
- public static boolean isplindromicbyStack(Node head) {
- Stack<Node> stack = new Stack<Node>();//创建栈结构
-
- Node temp = head;
- while (temp != null) {
- stack.push(temp);//入栈
- temp = temp.next;
- }
-
- while (head != null){
- if(head.value != stack.pop().value){//出栈,判断前后顺序是否相同
- return false;
- }
- head = head.next;
- }
- return true;
- }
- }
2、优化:降低空间复杂度
O(n/2)的额外空间
将右半部分的链表放到栈中,从链表左侧开始遍历,每遍历一个栈弹出一个,一一进行比较,全部相同则回文
单链表无法获知整个链表有多少数,单指针无法判断此时经历的结点是否是链表结构的一半
快慢指针:快指针一次走2步,慢指针一次走1步;当快指针遍历完整个链表,慢指针走到中点
在实际情况中快慢指针需要根据题目调整代码
- public static boolean isplindromicbyfastandslowPointer(Node head){
- if(head == null || head.next == null){
- return true;//整个链表为空或是只有一个Node也被视为回文结构
- }
-
- //快慢指针
- Node slow = head.next;
- Node fast = head;
-
- //整个链表为偶数时,慢指针位于n/2位置上;奇数时,整个链表位于n/2位置向上取整+1(5 - 4)
- while(fast.next != null && fast.next.next != null){
- slow = slow.next;
- fast = fast.next.next;
- }
-
- //取出后半段入栈
- Stack<Node> stack = new Stack<Node>();
- while(slow != null){
- stack.push(slow);
- slow = slow.next;
- }
-
- //将后半段与前半段比较,是否相同
- while(!stack.isEmpty()){
- if(head.value != stack.pop().value){
- return false;
- }
- head = head.next;
- }
- return true;
- }
3、优化:不使用额外的数据结构
O(1)的额外空间
快指针一次走2步,慢指针一次走1步;慢指针走到中间的位置,遍历后面部分的链表逆序;
两个指针,一个从head向后遍历,一个从end向前遍历,直到一个指针指向空
- public static boolean isplindromicbytwoPointer(Node head) {
- if (head == null || head.next == null) {
- return true;//整个链表为空或是只有一个Node也被视为回文结构
- }
-
- Node slow = head;
- Node fast = head;
- while (fast.next != null && fast.next.next != null) {
- slow = slow.next;//最后到达中点位置,奇数时中点位置向上取整
- fast = fast.next.next;//最后到达链表最后一位或两位
- }
-
- fast = slow.next;//从右半部分的起点开始
- slow.next = null;//空出空间便于后半部分逆序
-
- //后半部分逆序
- Node temp = null;
- while (fast != null) {
- temp = fast.next;
- fast.next = slow;//反转指针
- slow = fast;
- fast = temp;
- }
- temp = slow;//slow为反转部分的最后一个结点,也就是后半部分反转部分的头
- fast = head;//左半部分的头
-
- boolean res = true;
- //slow表示右半部分,fast表示左半部分
- while (slow != null && fast != null) {
- if (slow.value != fast.value) {
- res = false;
- break;
- }
- slow = slow.next;
- fast = fast.next;
- }
-
- //将链表反转回去 slow -- fast , temp -- slow , fast -- temp
- slow = temp.next;
- temp.next = null;
- // fast = slow.next;
- // slow.next = null;
-
- while (slow != null) {
- // while (fast != null)
-
- fast = slow.next;
- slow.next = temp;
- temp = slow;
- slow = fast;
- // temp = fast.next;
- // fast.next = slow;//反转指针
- // slow = fast;
- // fast = temp;
- }
-
- return res;
- }
使用额外空间:将单链表的每个结点放到数组中,对数组进行partition操作(左边小、中间相等、右边大)
不使用额外空间:六个变量,分别为小于、等于、大于部分的头和尾
注:三区域串起来的时候需要注意区域是否存在,否则会指向null导致出错
- package linkedlist;
-
- public class ListPartition {
- class Node {
- public int value;
- public Node next;
-
- public Node(int data) {
- this.value = data;
- }
- }
-
-
- public static Node listPattition(Node head, int pivot) {
- Node SH = null;
- Node ST = null;
- Node EH = null;
- Node ET = null;
- Node BH = null;
- Node BT = null;
- Node temp = null;
-
- while (head != null) {
- temp = head.next;//存储下一个head
- head.next = null;//消去指针
-
-
- if (head.value < pivot) {
- if (SH == null) {
- SH = head;
- ST = head;
- } else {
- ST.next = head;
- ST = head;
- }
- }
- if (head.value == pivot) {
- if (EH == null) {
- EH = head;
- ET = head;
- } else {
- ET.next = head;
- ET = head;
- }
- }
- if (head.value > pivot) {
- if (BH == null) {
- BH = head;
- BT = null;
- } else {
- BT.next = head;
- BT = head;
- }
- }
-
- head = temp;//再次进行赋值,使得head遍历整个链表
- }
-
- if (ST != null) {//有小于区域
- if (ET != null) {//有等于区域
- ST.next = EH;
- } else if (BT != null) {//没有等于区域,有大于区域
- ST.next = BH;
- } else {//啥也没有
- ST.next = null;
- }
- }
-
- if (ET != null) {//有等于区域
- if (BT != null) {//有大于区域
- ET.next = BH;
- } else {//无大于区域
- ET.next = null;
- }
- }
-
- //最终返回结果
- if(ST != null){
- return SH;
- } else if (ET != null) {
- return EH;
- }else{
- return BH;
- }
- }
- }
使用额外空间:哈希表拷贝节点,通过原来的节点1的next指针找到节点2,拷贝后的节点1'对应节点2',由此可知节点1'的next指针指向节点2'。random指针同理。
不使用额外空间:在链表的每个节点后插入其对应拷贝节点,通过节点1的next指针找到拷贝的节点1',节点1的random指针指向节点3,节点3的next指针指向节点3',由此可以拷贝节点1'指向节点3'的random指针
由next指针向下遍历,将每一对的指针都拷贝出,拷贝所有的random指针后抽离出拷贝的链表(画的好丑)
- package linkedlist;
-
- public class CopyLinkedListWithRand {
- class Node {
- public int value;
- public Node next;
- public Node rand;
-
- public Node(int data) {
- this.value = data;
- }
-
- }
-
- public Node copylistwithRand(Node head) {
- if (head == null) {
- return null;
- }
-
- Node node1 = head;
- Node node2 = null;
-
- //1 -> 2
- //1 -> 1' -> 2
- while (node1 != null) {
- node2 = node1.next;
- node1.next = new Node(node1.value);//克隆的新节点
- node1.next.next = node2;
- node1 = node2;
- }
-
- node1 = head;//重新来过
-
- //拷贝random指针
- while (node1 != null) {
-
- if (node1.rand != null) {//节点1的random指针
- node1.next.rand = node1.rand.next;//节点1'的random指针=节点3的next=节点3'
- } else {
- node1.next.rand = null;
- }
-
- node1 = node1.next.next;
- }
-
- Node res = head.next;//拷贝出来的链表的头节点
- node1 = head;//再次重新来过
- Node temp = res;
-
- while(node1 != null){
- node1 = node1.next.next;//跳到第三个,第五个...
- if(node1.next != null) {
- temp.next = node1.next;//第二个指向第四个,第四个指向第六个...
- }else{
- temp.next = null;
- }
- temp = temp.next;//第二个跳第四个,第四个跳第六个...
- }
-
- return res;//返回拷贝链表的头
- }
- }