1.题目 从尾到头打印链表
解法一:先统计链表有多少节点,然后创建数组,再次遍历链表将值存储至数组,然后反转数组。这种解法思路简单,但是时间复杂度较高。
class Solution {
int a = 0,b=0;
public int[] reversePrint(ListNode head) {
ListNode first = head;
while (head != null) {
head = head.next;
a++;
}
int[] arr = new int[a];
while (first!= null) {
arr[b] = first.val;
first = first.next;
b++;
}
for (int j = 0; j < arr.length/2; j++) {
int temp = arr[j];
arr[j] = arr[arr.length - j - 1];
arr[arr.length - j - 1] = temp;
}
return arr;
}
}
解法二:利用递归,先走至链表末端,回溯时依次将节点值加入列表 ,这样就可以实现链表值的倒序输出。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
int a = 0, b = 0;
int[] arr = null;
public int[] reversePrint(ListNode head) {
flag(head);
return arr;
}
public void flag(ListNode node) {
if (node == null) {
arr = new int[a];
return;
}
a++;
flag(node.next);
arr[b] = node.val;
b++;
}
}
解法三: 题目要求倒序输出节点值,这种先入后出的需求可以借助栈来实现。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
Stack<Integer> s = new Stack<>();
while (head != null) {
s.push(head.val);
head = head.next;
}
int[] arr = new int[s.size()];
for (int j = 0; j < arr.length; j++) {
arr[j] = s.pop();
}
return arr;
}
}
2.题目二:反转链表
解法一:
在遍历链表时,将当前节点的 next\textit{next}next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
ListNode last,current,next=null;
public ListNode reverseList(ListNode head) {
current=head;
while(current!=null){
next=current.next;//保存当前节点指向的下一个节点的指针
current.next=last;//当前节点指针反转,使当前节点的指针指向前一个节点
last=current;//移动指针:当前节点为前一个节点
current=next;//移动指针:下一个节点为当前节点
}
return last;
}
}
解法二:使用递归的方法,这种解法思路不易想到
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
解法三:利用栈先进后出的特点,反转链表,这种解法思路简单,但是算法时空复杂度都较高。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null){//若不判断为空,可能会引起栈为空而继续出栈的情况,后面代码报错
return null;
}
Stack<ListNode> stack=new Stack<>();
while(head!=null){
stack.push(head);
head=head.next;
}
ListNode flag=stack.pop();//弹出新的头结点作为已反转链表,其中flag为该反转链表的尾节点
ListNode newHead=flag;//复制一份头结点用以保存
ListNode current=null;
while(!stack.isEmpty()){
current=stack.pop();
flag.next=current;//将当前节点链接到反转链表的尾节点,成为反转链表的一个成员
current.next=null;//断掉当前节点与其他节点的链接
flag=current;//将当前节点作为新的反转链表的尾节点
}
return newHead;
}
}
3.题目三 复杂链表的复制
解法一:该解法没有太多的思路技巧,比较容易想到,但是算法的时空复杂度都比较差。具体思路是:先将原链表每一个节点一一添加到ArrayList中
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
ArrayList<Node> oldList = new ArrayList<>();
while (head != null) {
oldList.add(head);
head = head.next;
}
int size=oldList.size();
ArrayList<Node> newList = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
newList.add(new Node(oldList.get(i).val));
}
for (int j = 0; j < size; j++) {
newList.get(j).random =oldList.get(j).random == null ? null
: newList.get(oldList.indexOf(oldList.get(j).random));
if (j != newList.size() - 1) {
newList.get(j).next = newList.get(j + 1);
}
}
return newList.get(0);
}
}
解法二:这是官方给出的思路。我们利用回溯的方式,让每个节点的拷贝操作相互独立。对于当前节点,我们首先要进行拷贝,然后我们进行「当前节点的后继节点」和「当前节点的随机指针指向的节点」拷贝,拷贝完成后将创建的新节点的指针返回,即可完成当前节点的两指针的赋值。具体地,我们用哈希表记录每一个节点对应新节点的创建情况。遍历该链表的过程中,我们检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。在实际代码中,我们需要特别判断给定节点为空节点的情况。
class Solution {
private Map<Node,Node> map = new HashMap<>();
public Node copyRandomList(Node head) {
// 终止条件next给过来的node为null;
if(head == null){
return null;
}
// 中间处理方法
// 1. 递归创建节点到最后一个节点,并吧所有创建的节点保存在map中;
Node newNode = new Node(head.val);
map.put(head,newNode);
newNode.next = copyRandomList(head.next);
// 2. 因为递归过程中会创建所有的节点,所以回溯过程中直接进行newNode.random赋值
newNode.random = map.get(head.random);
return newNode;
}
}
解法三:这是官方给出的思路。我们首先将该链表中每一个节点拆分为两个相连的节点,例如对于链表 A→B→C,我们可以将其拆分为 A→A′→B→B′→C→C′ 。对于任意一个原节点 S,其拷贝节点 S′ 即为其后继节点。这样,我们可以直接找到每一个拷贝节点 S′的随机指针应当指向的节点,即为其原节点 S的随机指针指向的节点 T 的后继节点 T′。需要注意原节点的随机指针可能为空,我们需要特别判断这种情况。当我们完成了拷贝节点的随机指针的赋值,我们只需要将这个链表按照原节点与拷贝节点的种类进行拆分即可,只需要遍历一次。同样需要注意最后一个拷贝节点的后继节点为空,我们需要特别判断这种情况。
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
/*
原地拼接+拆分:
1.构建"旧->新->旧->新"交替的链表
2.构建新链表的random指向
3.将链表拆分恢复现场
*/
public Node copyRandomList(Node head) {
// 阴间案例
if(head == null) {
return null;
}
// 1.创建新旧节点交替的链表
Node cur = head;
while(cur != null) {
// 创建新的链表节点
Node tmp = new Node(cur.val);
// tmp右边连接
tmp.next = cur.next;
// tmp左边连接
cur.next = tmp;
// cur指针移动
cur = tmp.next;
}
// 2.构建新链表的random指向
// cur指针指向旧链表头结点
cur = head;
while(cur != null) {
// 新的链表random指向旧的链表的random的下一个
// 这里记住要判断一下cur.random是否为空,防止空指针
// 如示例1:head.random==null,因此head.next也指向null,也就是默认值
if(cur.random != null) {
cur.next.random = cur.random.next;
}
// cur后移两位
cur = cur.next.next;
}
// 3.将链表拆分恢复现场
// pre指向旧链表的节点,res存储新链表头结点
Node pre = head, res = head.next;
// cur指针指向新链表的节点
cur = head.next;
// 这里进入循环的条件是cur.next!=null,表明不是最后一个cur,也防止了下面空指针
while(cur.next != null) {
// 这里注意顺序:防止目标丢失
pre.next = pre.next.next;
cur.next = cur.next.next;
// pre和cur指针各移动一位即可,因为已经改变指向
pre = pre.next;
cur = cur.next;
}
// 最后一个pre的指针还粘着cur(画图看看就知道),需要手动去除一下
pre.next = null;
// 返回新链表头结点
return res;
}
}