算法基础入门——单链表、双链表、判断回文、链表的荷兰国旗、随机指针节点链表的复制、比较器、单链表相交、获取环节点
package com.zuoshen.jichurumen.class04; import java.util.*; /** * @author ShiZhe * @create 2022-02-25 21:35 */ public class code01 { /** * Node结构体 * 单链表的节点结构 */ public static class Node { public int value; public Node next; public Node rand; // 构造函数 public Node(int data) { this.value = data; } } /** * 双链表的节点结构 */ public static class DoubleNode { public int value; public DoubleNode last; public DoubleNode next; // 构造函数 public DoubleNode(int data) { this.value = data; } } /** * 单链表反转 * @param head * @return */ public static Node reverseList(Node head) { Node pre = null; Node next = null; while (head != null) { next = head.next; head.next = pre; pre =head; head = next; } return pre; } /** * 打印单链表 * @param head */ public static void printLinkedList(Node head) { System.out.println("Linked List: "); while (head != null) { System.out.print(head.value + " "); head = head.next; } System.out.println(); } /** * 双链表反转 * @param head * @return */ public static DoubleNode reverseList(DoubleNode head) { DoubleNode pre = null; DoubleNode next =null; while (head != null) { next = head.next; head.last = next; head.next = pre; pre = head; head = next; } return pre; } /** * 打印双链表 * @param head */ public static void printDoubleLinkedList(DoubleNode head) { System.out.print("Double Linked List: "); DoubleNode end = null; while (head != null) { System.out.print(head.value + " "); end = head; head = head.next; } System.out.print("| "); while (end != null) { System.out.print(end.value + " "); end = end.last; } System.out.println(); } /** * 打印两个有序链表的公共部分 * @param head1 * @param head2 */ public static void printCommonPart(Node head1, Node head2) { System.out.print("Common Part: "); while (head1 != null && head2 != null) { if (head1.value < head2.value) { head1 = head1.next; } else if (head1.value > head2.value) { head2 = head2.next; } else { System.out.print(head1.value + " "); head1 = head1.next; head2 = head2.next; } } System.out.println(); } /** * 判断一个链表是否为回文结构 * need n extra space:新建一个栈,链表全部放入栈中,然后一一弹出与head相比,head++。 * need n/2 extra space:新建一个栈,利用2个指针,一个next,一个next.next, * 使某一指针指向空时,一指针指向回文的右边,将右边放入栈中,一一弹出与head相比较,head++。 * need O(1) extra space:反转右边的单链表。利用2个指针一一对比。 * @param head * @return */ public static boolean isPalindrome(Node head) { if (head == null || head.next == null) { return false; } // 定义指向右半部分的辅助节点 Node right = head; // 定义2倍速跳转的辅助节点 Node next = head; // next指针更快,只用判断next指针不越界 while (next.next != null && next.next.next != null) { // 一倍速,right到中间 right = right.next; // 二倍速,next到末尾 next = next.next.next; } // 右半部分的开头 next = right.next; // 反转右半部分链表的辅助节点 Node pre = null; // 从中间断开 right.next = null; // 反转右边单链表 while (next != null) { pre = next; next = next.next; pre.next = right; right = pre; } // 将左边的头赋值给next next = head; // 返回结果 boolean res = true; // 检查回文 while (right != next) { if (next.value != right.value) { res = false; break; } next = next.next; right = right.next; } // 恢复反转的右部分单链表 right = pre; Node tmp = null; while (right != next) { right = right.next; pre.next = tmp; tmp = pre; pre = right; } pre.next = tmp; return res; } /** * 将单向链表按某值划分成左边小、中间相等、右边大的形式 * 时间复杂度请达到O(N),额外空间复杂度请达到O(N)。 * 方法一:类似于荷兰国旗问题,新建一个Node类型的数组,进行排序,然后再建立指针。 * 时间复杂度请达到O(N),额外空间复杂度请达到O(1)。 * 方法二:申请6个辅助节点,标识各个3个区间(小于、等于、大于)的头和尾。 * @param head * @param pivot * @return */ public static Node listPartition(Node head, int pivot) { // 小于头 Node sH = null; // 小于尾 Node sT = null; // 等于头 Node eH = null; // 等于尾 Node eT = null; // 大于头 Node bH = null; // 大于尾 Node bT = null; // 辅助节点 Node next = null; // 将所有节点划分给3条链 while (head != null) { next = head.next; head.next = null; if (head.value < pivot) { if (sH == null) { sH = head; sT = head; } else { sT.next = head; sT = head; } } else if (head.value == pivot) { if (eH == null) { eH = head; eT = head; } else { eT.next = head; eT = head; } } else { if (bH == null) { bH = head; bT = head; } else { bT.next = head; bT = head; } } head = next; } // 整合 if (sT != null) { sT.next = eH; // eT为空,将st赋值给eT eT = eT == null ? sT : eT; } if (eT != null) { eT.next = bH; } return sH != null ? sH : eH != null ? eH : bH; } /** * 复制含有随机指针节点的链表 * 时间复杂度O(N),额外空间复杂度O(1) * 方法一:hashMap,遍历新建节点,再遍历新建连接。额外空间是O(N) * 方法二:将新节点放在老节点的next中,节省了hashMap,使额外空间为O(1),遍历放入新节点,再遍历构建新节点的rand,再遍历分离 * @param head * @return */ public static Node copyListWithRand(Node head) { if (head == null) { return null; } Node cur = head; Node next =null; // 将新建节点插入在老节点的next中 while (cur != null) { next = cur.next; cur.next = new Node(cur.value); cur.next.next = next; cur =next; } // 构建新节点的rand cur = head; Node curCopy = null; while (cur != null) { next = cur.next.next; curCopy = cur.next; curCopy.rand = cur.rand != null ? cur.rand.next : null; cur =next; } // 分离 cur = head; Node res = head.next; while (cur != null) { next = cur.next.next; curCopy = cur.next; cur.next = next; curCopy.next = next != null ? next.next : null; cur = next; } return res; } /** * 比较器 */ public static class NodeComparator implements Comparator<Node> { // 根据Node的value大小来排序,官方默认下面的返回为升序。 // < return -1 // = return 0 // > return 1 // 需要为降序则return的值与官方相反 @Override public int compare(Node o1, Node o2) { return o1.value - o2.value; } } /** * 带有rand节点的链表打印 * @param head */ public static void printRandLinkedList(Node head) { Node cur = head; System.out.print("order: "); while (cur != null) { System.out.print(cur.value + " "); cur = cur.next; } System.out.println(); cur = head; System.out.print("rand: "); while (cur != null) { System.out.print(cur.rand == null ? "- " : cur.rand.value + " "); cur = cur.next; } System.out.println(); } /** * 两个单链表相交的一系列问题 * @param head1 * @param head2 * @return */ 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; } /** * 获取环节点 * @param head * @return */ public static Node getLoopNode(Node head) { if (head == null || head.next == null || head.next.next == null) { return null; } // n1 -> slow Node n1 = head.next; // n2 -> fast Node n2 = head.next.next; while (n1 != n2) { if (n2.next == null || n2.next.next == null) { return null; } n2 = n2.next.next; n1 = n1.next; } // n2 -> walk again from head,这步很重要。 n2 = head; while (n1 != n2) { n1 = n1.next; n2 = n2.next; } return n1; } /** * 第一种情况:均无环 * * @param head1 * @param head2 * @return */ public 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.next != null) { n++; cur1 = cur1.next; } while (cur2.next != null) { n--; cur2 = cur2.next; } // 尾节点是否相同 if (cur1 != cur2) { return null; } // 将节点数多的赋值给cur1 cur1 = n > 0 ? head1 : head2; // 将节点数少的赋值给cur1 cur2 = cur1 == head1 ? head2 : head1; // 绝对值取正 n = Math.abs(n); while (n != 0) { n--; cur1 = cur1.next; } while (cur1 != cur2) { cur1 = cur1.next; cur2 = cur2.next; } return cur1; } /** * 第二种情况:均有环 * @param head1 * @param loop1 * @param head2 * @param loop2 * @return */ public 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) { n++; cur1 = cur1.next; } while (cur2 != loop2) { n--; cur2 = cur2.next; } cur1 = n > 0 ? head1 : head2; cur2 = cur1 == head1 ? head2 : head1; n = Math.abs(n); while (n != 0) { n--; cur1 = cur1.next; } while (cur1 != cur2) { cur1 = cur1.next; cur2 = cur2.next; } return cur1; } else { // 环节点不相等,共用一个环,环节点在环上的不同位置 cur1 = loop1.next; while (cur1 != loop1) { if (cur1 == loop2) { return loop1; } cur1 = cur1.next; } return null; } } public static void main(String[] args) { Node nodeA = null; Node nodeB = null; Node nodeC = null; // hashSet1的key是基础类型->int类型 HashSet<Integer> hashSet1 = new HashSet<>(); hashSet1.add(3); System.out.println(hashSet1.contains(3)); hashSet1.remove(3); System.out.println(hashSet1.contains(3)); System.out.println("========1========="); // hashSet2的key是非基础类型->Node类型 nodeA = new Node(1); nodeB = new Node(1); HashSet<Node> hashSet2 = new HashSet<>(); hashSet2.add(nodeA); System.out.println(hashSet2.contains(nodeA)); System.out.println(hashSet2.contains(nodeB)); hashSet2.remove(nodeA); System.out.println(hashSet2.contains(nodeA)); System.out.println("========2========="); // hashMap1的key是基础类型->String类型 HashMap<String, Integer> hashMap1 = new HashMap<>(); String str1 = "key"; String str2 = "key"; hashMap1.put(str1, 1); System.out.println(hashMap1.containsKey(str1)); System.out.println(hashMap1.containsKey(str2)); System.out.println(hashMap1.get(str1)); System.out.println(hashMap1.get(str2)); hashMap1.put(str2, 2); System.out.println(hashMap1.containsKey(str1)); System.out.println(hashMap1.containsKey(str2)); System.out.println(hashMap1.get(str1)); System.out.println(hashMap1.get(str2)); hashMap1.remove(str1); System.out.println(hashMap1.containsKey(str1)); System.out.println(hashMap1.containsKey(str2)); System.out.println("========3========="); // hashMap2的key是非基础类型->Node类型 // 如果不是基础类型,内部按引用传递 nodeA = new Node(1); nodeB = new Node(1); HashMap<Node, String> hashMap2 = new HashMap<>(); hashMap2.put(nodeA, "A节点"); System.out.println(hashMap2.containsKey(nodeA)); System.out.println(hashMap2.containsKey(nodeB)); System.out.println(hashMap2.get(nodeA)); System.out.println(hashMap2.get(nodeB)); hashMap2.put(nodeB, "B节点"); System.out.println(hashMap2.containsKey(nodeA)); System.out.println(hashMap2.containsKey(nodeB)); System.out.println(hashMap2.get(nodeA)); System.out.println(hashMap2.get(nodeB)); System.out.println("========4========="); // treeSet的key是非基础类型->Node类型 // 如果不是基础类型,必须提供比较器 nodeA = new Node(5); nodeB = new Node(3); nodeC = new Node(7); TreeSet<Node> treeSet = new TreeSet<>(); // 以下的代码会报错,因为没有提供Node类型的比较器 try { treeSet.add(nodeA); treeSet.add(nodeB); treeSet.add(nodeC); } catch (Exception e) { System.out.println("错误信息:" + e.getMessage()); } treeSet = new TreeSet<>(new NodeComparator()); // 以下的代码没问题,因为提供了Node类型的比较器 try { treeSet.add(nodeA); treeSet.add(nodeB); treeSet.add(nodeC); System.out.println("这次节点都加入了"); } catch (Exception e) { System.out.println(e.getMessage()); } System.out.println("========5========="); // 展示有序表常用操作 TreeMap<Integer, String> treeMap1 = new TreeMap<>(); treeMap1.put(7, "我是7"); treeMap1.put(5, "我是5"); treeMap1.put(4, "我是4"); treeMap1.put(3, "我是3"); treeMap1.put(9, "我是9"); treeMap1.put(2, "我是2"); System.out.println(treeMap1.containsKey(5)); System.out.println(treeMap1.get(5)); System.out.println(treeMap1.firstKey() + ", 我最小"); System.out.println(treeMap1.lastKey() + ", 我最大"); System.out.println(treeMap1.floorKey(8) + ", 在表中所有<=8的数中,我离8最近"); System.out.println(treeMap1.ceilingKey(8) + ", 在表中所有>=8的数中,我离8最近"); System.out.println(treeMap1.floorKey(7) + ", 在表中所有<=7的数中,我离7最近"); System.out.println(treeMap1.ceilingKey(7) + ", 在表中所有>=7的数中,我离7最近"); treeMap1.remove(5); System.out.println(treeMap1.get(5) + ", 删了就没有了哦"); System.out.println("========6========="); // 单链表反转与输出 Node head1 = new Node(1); head1.next = new Node(2); head1.next.next = new Node(3); printLinkedList(head1); head1 = reverseList(head1); printLinkedList(head1); // 双链表反转与输出 DoubleNode head2 = new DoubleNode(1); head2.next = new DoubleNode(2); head2.next.last = head2; head2.next.next = new DoubleNode(3); head2.next.next.last = head2.next; head2.next.next.next = new DoubleNode(4); head2.next.next.next.last = head2.next.next; printDoubleLinkedList(head2); printDoubleLinkedList(reverseList(head2)); // 打印两个有序链表的公共部分 Node node1 = new Node(2); node1.next = new Node(3); node1.next.next = new Node(5); node1.next.next.next = new Node(6); Node node2 = new Node(1); node2.next = new Node(2); node2.next.next = new Node(5); node2.next.next.next = new Node(7); node2.next.next.next.next = new Node(8); printLinkedList(node1); printLinkedList(node2); printCommonPart(node1, node2); // 判断回文 Node head3 = null; head3 = new Node(1); head3.next = new Node(2); head3.next.next = new Node(3); head3.next.next.next = new Node(2); head3.next.next.next.next = new Node(1); printLinkedList(head3); System.out.println(isPalindrome(head3)); printLinkedList(head3); System.out.println("========================="); // 将单向链表按某值划分成左边小、中间相等、右边大的形式 Node head4 = new Node(7); head4.next = new Node(9); head4.next.next = new Node(1); head4.next.next.next = new Node(8); head4.next.next.next.next = new Node(5); head4.next.next.next.next.next = new Node(2); head4.next.next.next.next.next.next = new Node(5); printLinkedList(head4); head4 = listPartition(head4, 5); printLinkedList(head4); // 复制含有随机指针节点的链表 Node head5 = new Node(1); head5.next = new Node(2); head5.next.next = new Node(3); head5.next.next.next = new Node(4); head5.next.next.next.next = new Node(5); head5.next.next.next.next.next = new Node(6); head5.rand = head5.next.next.next.next.next; // 1 -> 6 head5.next.rand = head5.next.next.next.next.next; // 2 -> 6 head5.next.next.rand = head5.next.next.next.next; // 3 -> 5 head5.next.next.next.rand = head5.next.next; // 4 -> 3 head5.next.next.next.next.rand = null; // 5 -> null head5.next.next.next.next.next.rand = head5.next.next.next; // 6 -> 4 printRandLinkedList(head5); Node res = copyListWithRand(head5); printRandLinkedList(res); // 两个单链表相交的一系列问题 // 1->2->3->4->5->6->7->null Node head6 = new Node(1); head6.next = new Node(2); head6.next.next = new Node(3); head6.next.next.next = new Node(4); head6.next.next.next.next = new Node(5); head6.next.next.next.next.next = new Node(6); head6.next.next.next.next.next.next = new Node(7); // 0->9->8->6->7->null Node head7 = new Node(0); head7.next = new Node(9); head7.next.next = new Node(8); head7.next.next.next = head6.next.next.next.next.next; // 8->6 System.out.println(getIntersectNode(head6, head7).value); // 1->2->3->4->5->6->7->4... head6 = new Node(1); head6.next = new Node(2); head6.next.next = new Node(3); head6.next.next.next = new Node(4); head6.next.next.next.next = new Node(5); head6.next.next.next.next.next = new Node(6); head6.next.next.next.next.next.next = new Node(7); head6.next.next.next.next.next.next = head6.next.next.next; // 7->4 // 0->9->8->2... head7 = new Node(0); head7.next = new Node(9); head7.next.next = new Node(8); head7.next.next.next = head6.next; // 8->2 System.out.println(getIntersectNode(head6, head7).value); // 0->9->8->6->4->5->6.. head7 = new Node(0); head7.next = new Node(9); head7.next.next = new Node(8); head7.next.next.next = head6.next.next.next.next.next; // 8->6 System.out.println(getIntersectNode(head6, head7).value); } }