难度:❤️
给定一个单链表的头部节点head,链表长度为N,如果N为偶数,那么前N/2个节点算作左半区,后N/2个节点算作右半区;如果N为奇数,那么前N/2个节点算作左半区,后N/2 + 1个节点算作右半区。左半区从左到右依次记为L1 -> l2 -> …,右半区从左到右依次记为R1 -> R2 ->…,请将单链表调整成L1 -> R1 -> L2 -> R2 -> …的形式。
链表的分区域后,合并思想
首先关注题目要求,找出其特点:
将mid = head, right= head.next,将mid进行预处理:如果right每次向右移动两个节点且不为null,则mid右移一位。最终mid将指向左区域的尾结点,故mid+1为右区域首节点。
将left(左区域首节点)和right(右区域首节点)进行merge时,须要使用一个临时变量(next),记录被断开的节点。
第一次merge完,left和right需要重新赋值,直到left.next = null
left = right.next right = next
每次找到左区域的头节点(left)和右区域的投节点(right),然后进行merge
merge的过程就是将left和right拼接
代码:
public class Solution{
public static void relocate(Node head){
if (head == null || head.next == null) {
return;
}
//划分左右两区域
Node mid = head;
Node right = head.next;
while (right.next != null && right.next.next != null) {
mid = mid.next;
right = right.next.next;
}
//将左右两区域断开 用作合并完成 退出的条件
mid.next = null;
//合并左右两区域
mergeLR(head, mid.next);
}
private static void mergeLR(Node left, Node right){
//left和right分别代表左右区域首节点
Node next = null;
while (left.next != null) {
next = right.next;
right.next = left.next;
left.next = right;
left = right.next;
right = next;
}
left.next = right;
}
}
class Node{
public int value;
public Node next;
public Node(int value){
this.value = value;
}
}
难度:❤️
给定两个有序单链表的头节点head1和head2,请合并两个有序链表,合并后的链表依然有序,并返回合并后链表的头节点
采用范围(指针)合并的思想
代码:
public class Solution{
public static Node merge(Node head1, Node head2){
if (head1 == null || head2 == null) {
return head1 != null ? head1 : head2;
}
//cur1总是指向头节点值小的那个 cur2则相反
//这是为了保证第一次能进if (cur1.value <= cur2.value)分支
Node head = head1.value <= head1 ? head1 : head2;
Node cur1 = head == head1 ? head1 : head2;
Node cur2 = head == head1 ? head2 : head1;
Node pre = null;
Node next = null;
while (cru1 != null && cur2 != null) {
if (cur1.value <= cur2.value) {
pre = cur1;
cur1 = cur1.next
}
else{
next = cur2.next;
cur2.next = cur1.next;
cur1.next = cur2;
pre = cur2;
cur2 = next;
}
}
pre.next = cur1 == null ? cur2 : cur1;
return head;
}
}
难度:❤️
一个环形单链表从头节点head开始不降序,同时由最后的结点指回头节点。给定这样一个环形单链表的头节点head和一个整数num,请生成节点值为num的新节点,并插入到这个环形链表中,保证调整后的链表依然有序。
双指针确定插入的范围,然后合并
插入过程分为2种情况
如果是2.1直接返回头节点,如果是2.2则须将插入的值作为新的头节点返回
代码:
public class Solution{
public static Node insertNum(Node head, int num){
Node node = new Node(num);
//如果head为空 则将自己指向本省
if (head == null) {
node.next = node;
return node;
}
Node pre = head;
Node cur = head.next;
while (cur != head) {
if (num >= pre.value && num <= cur.value) {
break;
}
pre = cur;
cur = cur.next;
}
//insert
pre.next = node;
node.next = cur;
//is new head?
return num > head.value ? head : node;
}
}
class Node{
public int value;
public Node next;
public Node(int value){
this.value = value;
}
}
总结:
到此为止,链表的遍历须涉及到两个重要变量,pre和next。pre代表cur前一个结点,next代表cur后一个结点
难度:❤️
由于链表是有序的,所以只需同时遍历两个链表即可
class Node {
public int value;
public Node next;
public Node(int value) {
this.value = value;
}
}
public class Solution {
public void printCommonPart(Node head1, Node head2) {
System.out.println("Common part:");
while (head1 != null && head2 != null) {
if (head1.value > head2.value) {
head2 = head2.next;
}
else if (head1.value < head2.value) {
head1 = head1.next;
}
else {
System.out.println(head1.value);
head1 = head1.next;
head2 = head2.next;
}
}
System.out.println();
}
}
学会了链表的遍历方法
难度:❤️
要想删除节点,必须要知道前一个结点(除了链表头节点)
观看视频
class Node {
public int value;
public Node next;
public Node(int value) {
this.value = value;
}
}
public class Solution {
public Node removeLastKthNode(Node head, int k) {
Node cur = head;
//第一次遍历 k--
while (cur != null) {
k--;
cur = cur.next;
}
//判断k
if (k == 0) {
return head.next;
}
if (k < 0) {
//第二次遍历 k++ 找到要删除的节点的前一个节点
cur = head;
while (++k != 0) {
cur = cur.next;
}
//k = 0
//这里的cur表示的是倒数第k - 1个节点
cur.next = cur.next.next;
}
return head;
}
//单链表换成双链表
public Node removeLastKthNode(DoubleNode head, int k) {
Node cur = head;
//第一次遍历 k--
while (cur != null) {
k--;
cur = cur.next;
}
//判断k
if (k == 0) {
head.last = null;
return head.next;
}
if (k < 0) {
//第二次遍历 k++ 找到要删除的节点的前一个节点
cur = head;
while (++k != 0) {
cur = cur.next;
}
//k = 0
//这里的cur表示的是倒数第k - 1个节点
DoubleNode newNext = cur.next.next;
cur.next = cur.next.next;
//注意删除最后一个节点的情况
if (newNext != null) {
newNext.last = cur;
}
}
return head;
}
}
学会了链表结点的遍历和删除操作
难度:❤️
视频讲解
其中视频出现的错误:
public class Solution {
public Node removeMidNode(Node head) {
if (head == null || head.next == null) {
return head;
}
Node pre = head;
if (pre.next.next == null) {
return head.next;
}
Node cur = pre.next.next;
while (cur.next != null && cur.next.next != null) {
pre = pre.next;
cur = cur.next.next;
}
//delete
pre.next = pre.next.next;
return head;
}
public Node removeByRatio(Node head, int a, int b) {
if (head == null || a > b) {
return head;
}
//求链表的长度N
int n = 0;
Node cur = head;
while (cur != null) {
n++;
cur = cur.next;
}
//计算要删除第几个节点 向上取整
int k = (int) Math.ceil(((double)a / (double)b) * n );
if (k == 1){
return head.next;
}
//找到该节点的前一个节点
if (k > 1) {
cur = head;
while (--k != 1) {
cur = cur.next;
}
//delete
cur.next = cur.next.next;
}
return head;
}
}
学会了求链表的长度
删除节点一定要找到前一个节点
难度:❤️
找到节点的前继节点和后继节点
public class Solution {
public Node reverseLinkedList(Node head){
Node pre = null;
Node next = null;
//head兼顾遍历变量的作用
while (head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
public Node reverseLinkedList(Double head){
Double pre = null;
Double next = null;
//head兼顾遍历变量的作用
while (head != null) {
next = head.next;
head.next = pre;
head.last = next;
pre = head;
head = next;
}
return pre;
}
}
学会了如何反转链表及注意到的事项
难度:❤️
视频讲解
public class Solution {
public Node reversePart(Node head, int from, int to) {
Node cur = head;
Node fPre = null;
Node tPos = null;
int n = 0;
while (cur != null) {
n++;
fPre = from - 1 == n ? cur : fPre;
tPos = to + 1 == n ? cur : tPos;
cur = cur.next;
}
if (to < 1 || to > from ||from > n || head) {
return head;
}
//对内部链表进行反转
Node pre = null;
Node next = null;
cur = fPre == null ? head : fPre.next;
Node temp = cur;
while (cur != tPos) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
//将反转后的链表连接到原始链表
//判断是不是新的头节点
if (fPre == null) {
temp.next = tPos;
return pre;
}
fPre.next = pre;
temp.next = tPos;
return head;
}
}
巩固了链表的反转操作
原问题:❤️
进阶问题:❤️❤️❤️
public class Solution {
//方法一:不停的删除报到m数的节点 最后返回存活的数
publc Node josefphusKill1(Node head, int m) {
if (head == null || head.next = head || m < 1) {
return head;
}
Node last = head;
while (last.next != head) {
last = last.next;
}
int count = 0;
while (last != head) {
if (++count == m) {
last.next = head.next;
count = 0;
}
else {
last = head;
}
head = last.next;
}
return head;
}
//方法二:直接找到最后生存的节点
publc Node josefphusKill2(Node head, int m) {
if (head == null || head.next = head || m < 1) {
return head;
}
//求链表i的长度
int i = 0;
cur = head;
while (cur != null) {
i++;
cur = cur.next;
}
//在i环中 节点的编号是old
//在i - 1环中 节点的编号是new
//可以得到递归函数(其中s为要删除的这样一个报数):old = (new + s - 1) % i + 1
int live = getLive(i, m);
//找到存活的节点后 返回即可
cur = head;
while (--live != 0) {
cur = cur.next;
}
cur.next = cur;
return cur;
}
private int getLive(int i, int m){
//base case: 只剩下i = 1个节点的环 该节点就是最终存活的
if (i == 1) {
return 1;
}
return ( getLive(i - 1, m) + m - 1) % i + 1
}
}
原问题:❤️
进阶问题:❤️❤️
根据回文的特点
此时有两个突破方向
public class Solution {
//method-1:
public boolean isPalindrome1 (Node head){
//利用stack 比较压栈后出栈后的节点
Stack<Node> stack = new Stack<>();
//压栈
Node cur = head;
while(cur != cur) {
stack.push(cur);
cur = cur.next
}
//出栈
cur = head;
while (cur != null) {
if (cur.value != stack.pop().value) {
return false;
}
}
return true;
}
//method-2:
public boolean isPalindrome2 (Node head){
if (head == null || head.next == null) {
return true;
}
//利用stack 比较压栈后出栈后的节点
Stack<Node> stack = new Stack<>();
//寻找右半部分链表第一个节点 奇数则中间数不比较 然后压栈
//不过只压入一半的链表
Node right = head.next;
Node cur = head;
while (cur.next != null && cur.next.next != null) {
right = right.next;
cur = cur.next.next;
}
while (right != null) {
stack.push(right);
right = right.next;
}
//出栈
cur = head;
while (!stack.isEmpty()) {
if (cur.value != stack.pop().value) {
return false;
}
cur = cur.next;
}
return true;
}
//method-3:
public boolean isPalindrome3 (Node head){
if (head == null || head.next == null) {
return true;
}
//寻找中间节点 time:O(n) space:O(1)
Node cur = head;
Node mid = head;
while (cur.next != null && cur.next.next != null) {
mid = mid.next;
cur = cur.next.next;
}
//反转右部分链表节点 time:O(n) space:O(1)
Node pre = null;
Node next = null;
cur = mid.next;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
mid.next = pre;
//比较左 右部分链表节点
Node left = head;
Node right = mid.next;
boolean res = true;
while (right != null){
if (left.value != right.value) {
res = false;
break;
}
right = right.next;
}
//恢复被反转的链表部分
pre = null;
next = null;
cur = mid.next;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
mid.next = pre;
}
}
学会寻找链表的2/N
处的位置
难度:❤️❤️
public class Solution {
public Node listPartition1 (Node head, int pivot){
if (head == null) {
return head;
}
//扁平化数组---快排问题
//求链表长度
int n = 0;
Node cur = head;
while (cur != null) {
n++;
cur = cur.next;
}
Node[] nArr = new Node[n];
cur = head;
for (int i = 0; i < n; i++) {
nArr[i] = cur;
cur = cur.next;
}
partition(nArr, pivot);
for (int i = 1; i < n; i++) {
nArr[i - 1].next = nArr[i];
}
nArr[i - 1].next = null;
return nArr[0];
}
private void partition (Node[] nArr, int pivot){
int small = -1;
int big = nArr.length;
int index = 0;
while (index != big) {
if (nArr[index] < pivot) {
swap(nArr, ++small, index++);
}
else if (nArr[index] = pivot) {
index++;
}
else {
swap(nArr, index++, --big);
}
}
}
private void swap (Node[] nArr, int i, int j){
Node temp = nArr[i];
nArr[i] = nArr[j];
nArr[j] = temp;
}
public Node listPartition2 (Node head, int pivot) {
Node cur = head;
Node next = null;
Node smallHead = null;
Node smallTail = null;
Node equalHead = null;
Node equalTail = null;
Node bigHead = null;
Node smallTail = null;
//分为small equal big三个区域
while (cur != null) {
next = cur.next;
cur.next = null;
if (cur.value < pivot) {
if (smallHead == null) {
smallHead = cur;
smallTail = cur;
}
else {
smallTail.next = cur;
smallTail = smallTail.next;
}
}
else if (cur.value == pivot) {
if (equalHead == null) {
equalHead = cur;
equalTail = cur;
}
else {
equalTail.next = cur;
equalTail = equalTail.next;
}
}
else {
if (bigHead == null) {
bigHead = cur;
bigTail = cur;
}
else {
bigTail.next = cur;
bigTail = bigTail.next;
}
}
cur = next;
}
//将三个区域连接起来
if (smallTail != null) {
smallTail.next = equalHead;
//equalTail可能为空
equalTail = equalTail == null ? smallTail : equalTail;
}
if (equalTail != null) {
equalTail.next = bighead;
}
bigTail.next = null;
return smallHead != null ? smallHead : equalHead != null ? equalHead : bigHead ;
}
}
学会了处理链表的分区问题
难度:❤️❤️
class Node {
public int value;
public Node next;
public Node rand;
public Node (int data) {
this.value = data;
}
}
public class Solution {
//方法一 space: O(n)
public Node copyListWithRand1 (Node head){
if (head == null) {
return head;
}
HashMap<Node, Node> map = new HashMap<>();
Node cur = head;
while (cur != null) {
map.put(cur, new Node(cur.value));
cur = cur.next;
}
//设置next和rand
cur = head;
while (cur != null) {
map.get(cur).next = map.get(cur.next);
map.get(cur).rand = map.get(cur.rand);
}
return map.get(head);
}
//方法二 space: O(1)
public Node copyListWithRand2 (Node head){
if (head == null) {
return head;
}
//将每个节点的副本添加为节点的next
Node cur = head;
Node next = null;
while (cur != null) {
next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = next;
}
//接下来关键找到新节点的rand(也为新节点)
cur = head;
next = null;
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;
next = null;
curCopy = null;
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;
}
}
对陌生数据结构的处理,主要关注新的属性,本题就是rand,关键在于如何找到新节点的next和rand
方法一
方法二
方法三
public class Solution {
public Node addList1(Node head1, Node head2) {
if (head1 == null || head2 == null) {
}
//压栈 进行尾数相加
Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
Node cur = head1;
while (cur != null) {
stack1.push(cur.value);
cur = cur.next;
}
cur = head2;
while (cur != null) {
stack2.push(cur.value);
cur = cur.next;
}
Node node = null; //新建节点
Node pre = null; //前一个节点 用于连接新节点
int n = 0; //当前新节点值
int n1 = 0; //第一个链表节点值
int n2 = 0; //第二个链表的节点值
int ca = 0; //进位数
while (!stack1.isEmpty() || !stack2.isEmpty()) {
pre = node;
n1 = !stack1().isEmpty() ? 0 : stack1().pop;
n2 = !stack2().isEmpty() ? 0 : stack2().pop;
n = n1 + n2 + ca;
node = new Node(n % 10);
ca = n / 10;
node.next = pre;
}
//进位不可能超过2
if (ca == 1) {
//最后还要生成进位节点
pre = node;
node = new Node(1);
node.next = pre;
}
return node;
}
public Node addList2(Node head1, Node head2) {
head1 = reverseList(node1);
head2 = reverseList(node2);
Node c1 = head1;
Node c2 = head2;
Node node = null;
Node pre = null;
int n = 0;
int n1 = 0;
int n2 = 0;
int ca = 0;
while (c1 != null || c1 != null) {
n1 = c1 != null ? c1.value : 0;
n2 = c2 != null ? c2.value : 0;
n = n1 + n2 + ca;
pre = node;
node = new Node(n % 10);
node.next = pre;
c1 = c1 != null ? c1.next : null;
c2 = c2 != null ? c2.next : null;
ca = n / 10;
}
if (ca == 1) {
pre = node;
node = new Node(ca);
node.next = pre;
}
reverseList(head1);
reverseList(head2);
return node;
}
private Node reverseList(Node head) {
Node pre = null;
Node next = null;
Node cur = head;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
逆序(反转)链表的两种方式:
1.利用栈
2.对链表本身进行逆序 不过最后要将原链表恢复
什么是单链表相交?
单链表相交就是链表共享某一连续部分,并且持续到尾结点end(但是尾结点可能进入循环)
题意 ”相交部分“指的时哪里
n可能为负值
getLoopNode()函数的调用时机
public class Solution {
//如果有结尾节点 则只需判断结尾节点
//如果没有结尾节点 即结尾部分是个环形链表 则将进入环形链表的第一个节点当作结尾节点 然后比较结尾节点是否相等
//总之还是比较结尾节点
public Node getLoopNode (Node head){
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node slow = head; //慢指针每次移动一个节点
Node fast = head; //快指针每次移动两个节点
while (fast != slow) {
if (fast.next == null || fast.next.next == null) {
//如果无环 fast首先到终点
return null;
}
slow = slow.next;
fast = fast.next.next;
}
//此时fast与slow相遇:
//将fast设为head 每次移动一个节点 fast与slow再一次相遇的节点则为进入环的第一个节点
fast = head;
while (fast != slow) {
slow = slow.next;
fast = fast.next;
}
return fast;
}
public Node noLoop(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
int n = 0;
Node c1 = head1;
Node c2 = head2;
while (c1 != null) {
n++;
c1 = c1.next;
}
while (c2 != null) {
n--;
c2 = c2.next;
}
//end1 != end2 说明无共享部分
if (c1 != c2) {
return null;
}
c1 = n > 0 ? head1 : head2;
c2 = c1 != head1 ? head1 : head2;
Math.abs(n);
while (n != 0) {
n--;
c1 = c1.next;
}
//第一次相等的节点
while (c1 != c2) {
c1 = c1.next;
c2 = c2.next;
}
return c1;
}
public Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
Node c1 = null;
Node c2 = null;
//结尾节点相等的话 则要往前找共享的第一个节点
if (loop1 == loop2) {
c1 = head1;
c2 = head2;
int n = 0;
while (c1 != loop1) {
n++;
c1 = c1.next;
}
while (c2 != loop2) {
n--;
c2 = c2.next;
}
if (c1 != c2) {
return loop1;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 != head1 ? head1 : head2;
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
//结尾节点不相等 则往环里寻找第一个相等的节点
else {
c1 = loop1.next;
while (c1 != loop1) {
if (c1 == loop2) {
return loop1;
}
c1 = c1.next;
}
return null;
}
}
public Node getIntegersectNode(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 class Solution {
public Node reverseKNodes1(Node head, int K) {
if (head == null || k < 2) {
return head;
}
Stack<Node> stack = new Stack<>();
Node newHead = head;
Node cur = head;
Node pre = null;
Node next = null;
while (cur != null) {
next = cur.next;
stack.push(cur);
if (stack.size() == K) {
pre = resign1(stack, pre, next);
//change head
newHead = newHead == head ? cur : newHead;
}
cur = next;
}
return newHead;
}
//left代表前一组的最后一个节点
//right代表后一组的第一个节点
//返回值表示返回逆序后该组链表的最后一个节点
private Node resign1(Stack<Node> stack, Node left, Node right) {
Node cur = stack.pop();
Node next = null;
//连接前一组 null
if (left != null) {
left.next = cur;
}
while (!stack.isEmpty()) {
next = stack.pop();
cur.next = next;
cur = next;
}
//连接后一组
cur.next = right;
return cur;
}
public Node reverseKNodes2(Node head, int K) {
if (head == null || k < 2) {
return head;
}
int count = 1; //
Node cur = head;
Node pre = null;
Node next = null;
Node start = null;
while (cur != null) {
next = cur.next;
if (count == K) {
start = pre == null ? head : pre.next;
head = pre == null ? cur : head; //
resign2(pre, start, cur, next);
pre = cur;
// start = next; //
count = 0; //
}
count ++;
cur = next;
}
return head;
}
//left:前一组的最后一个节点
//right:后一组的第一个节点
//start:待逆序组的第一个节点
//end:待逆序组的最后一个节点
private void resign2(Node left, Node start, Node end, Node right) {
Node pre = start;
Node cur = start.next;
Node next = null;
while (cur != right) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
if (left != null) {
left.next = end;
}
start.next = right;
}
}
加强了链表局部反转(逆序)的一些细节操作,如局部反转需从第2个节点开始连接第1个节点,最后将第一个节点的next指向下一组的第一个节点。需要2个变量完成拼接,left和right
要求:
方法一:时间复杂度为O(n)
方法二:空间复杂度为O(1)
public class Solution {
public void removeRep1(Node head) {
if (head == null) {
return;
}
HashSet<Integer> set = new HashSet<>();
Node cur = head;
Node pre = null;
while (cur != null) {
if (set.contains(cur.value)) {
pre.next = cur.next;
}
else {
set.add(cur.value);
pre = cur;
}
cur = cur.next;
}
}
public void removeRep2(Node head) {
if (head == null) {
return;
}
Node target = head;
Node cur = null;
while (target != null) {
cur = target.next;
pre = target;
while (cur != null) {
if (cur.value == target.value) {
pre.next = cur.next;
}
else {
pre = cur;
}
cur = cur.next;
}
target = target.next;
}
}
}
双遍历链表并删除节点的细节代码操作,pre从head开始
HashSet的用法
public class Solution {
//方法一
public Node removeValue1(Node head, int num) {
if (head == null) {
return head;
}
Stack<Node> stack = new Stack<>();
while (head != null) {
if (head.value != num) {
stack.push(head);
}
head = head.next;
}
while (!stack.isEmpty()) {
stack.peek().next = head;
head = stack.pop();
}
return head;
}
//方法二
public Node removeValue2(Node head, int num) {
if (head == null) {
return head;
}
//头节点没有判断到
Node cur = head.next;
Node pre = head;
while (cur != null) {
if (cur.value == num) {
pre.next = cur.next;
}
else {
pre = cur;
}
cur = cur.next;
}
return head.value == num ? head.next : head;
}
}
灵活利用栈的特性;
删除链表中的指定值,pre从null开始
观看视频
class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public class Solution {
public Node convert1(Node head) {
if (head == null) {
return head;
}
Queue<Node> queue = new Queue<>();
inOrderToQueue(queue, head);
head = queue.poll();
Node cur = head;
Node pre = head;
//头节点的left为null
pre.left = null;
while (!queue.isEmpty()) {
cur = queue.poll();
pre.right = cur;
cur.left = pre;
pre = cur;
}
//尾结点的right为null
pre.right = null;
return head;
}
private void inOrderToQueue(Queue<Node> queue, Node head) {
if (head == null) {
return;
}
inOrderToQueue(queue, head.left);
queue.offer(head);
inOrderToQueue(queue, head.right);
}
public Node convert2(Node head) {
if (head == null) {
return head;
}
Node temp = process();
head = temp.right;
temp.right = null;
return head;
}
private Node process(Node head) {
if (head == null) {
return null;
}
Node leftE = process(head.left);
Node rightE = process(head.right);
//因为尾结点进行了特殊处理: 将尾结点的right指向头节点 于是可快速的找到头节点
Node leftS = leftE == null ? null : leftE.right;
Node rightS = rightE == null ? null : rightE.right;
if (leftE != null && rightE != null) {
//形成新的链表 需进行拼接
leftE.right = head;
head.left = leftE;
head.right = rightS;
rightS.left = head;
//将新链表的尾结点的right指向头节点 并返回新链表的尾结点
rightE.right = leftS;
return rightE;
}
else if (leftE != null) {
//右孩子为空
leftE.right = head;
head.left = leftE;
//将新链表的尾结点(head)的right指向头节点 并返回head
head.right = leftS;
return head;
}
else if (rightE != null) {
head.right = rightS;
rightS.left = head;
rightE.right = head;
return rightE;
}
else {
head.right = head;
//返回end节点
return head;
}
}
}
链表与二叉树结合的问题;
二叉树递归函数的设计;
对链表的尾结点进行特殊的技巧操作
观看视频
public class Solution {
public Node listSelection(Node head) {
if (head == null) {
return head;
}
Node cur = head;
Node small = null; //未排序链表中值最小的节点
Node smallPre = null; //small前一个结点
Node tail = null; //排序链表中的尾结点
while (cur != null) {
small = cur;
smallPre = getSmallestPre(cur);
if (smallPre != null) {
small = smallPre.next;
smallPre.next = small.next;
}
cur = cur == small ? cur.next : cur;
if (tail == null) {
head = small;
}
else {
tail.next = small;
}
tail = small;
}
return head;
}
//删除最小结点必须找到该节点的前一个节点
//在以head为头节点的链表中寻找
private void getSmallestPre(Node head) {
Node small = head;
Node smallPre = null;
Node cur = head.next;
Node pre = head;
while (cur != null) {
if (cur.value < small.value) {
small = cur;
smallPre = pre;
}
pre = cur;
cur = cur.next;
}
return smallPre;
}
}
选择排序的思想;
链表中找最小值并删除
通过这20几道的算法题,可以将链表类算法题目进行如下总结:
链表的结构
链表的遍历
链表的局部操作
链表的类型
链表的操作方法
双指针
pre
cur
或者
针对环形链表
last
head
或快慢指针
fast
low
快慢指针用于判断有无环或者进入环的第一个节点
总之,链表的操作细节麻烦之处在于链表的连接,写这部分代码时必须仔细