文章目录
反转链表:JZ24
题目链接 OJ链接
题目描述:
给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
数据范围: 0 ≤ n ≤ 1000
要求:空间复杂度 O(1) ,时间复杂度 O(n) 。
- 思路1:
用一个数组存储结点数据,然后遍历数组,创建一个头节点,用尾插的思路连接成表,但是这种方法的空间复杂度为O(N)
public ListNode reverseList(ListNode head) {
ArrayList<Integer> list = new ArrayList<>();
ListNode cur = head;
while (head != null) {
list.add(head.val);
head = head.next;
}
ListNode newHead =null;
for (int x : list) {
cur = new ListNode(x);
cur.next = newHead;
newHead = cur;
}
return newHead;
}
或者直接连接
class Solution {
public ListNode reverseList(ListNode head) {
ListNode ans = null;
for (ListNode x = head; x != null; x = x.next) {
ans = new ListNode(x.val,ans);
}
return ans;
}
}
- 思路2:
迭代:遍历链表时,用pre和next来维护前后指向,先用一个变量tmp存储next中的结点,然后改变前后指向,直到next为空。
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode next = head;
while(next != null) {
ListNode tmp = next.next;
next.next = pre;
pre = next;
next = tmp;
}
return pre;
}
}
移除链表元素
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
- 思路1
先保证头结点不是我们要删除的数据,然后删除元素,删除元素只需要把删除元素的前一个next指向删除元素的next。
/**
* Definition for singly-linked list.
* 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 removeElements(ListNode head, int val) {
//保证头节点有效
while (head != null && head.val == val) {
head = head.next;
}
if (head == null) {
return null;
}
//删除val节点
ListNode pre = head;
ListNode target = pre.next;
while (pre != null && target != null) {
if (target.val == val) {
pre.next = target.next;
} else {
pre = pre.next;
}
target = target.next;
}
return head;
}
}
- 思路2
按照思路1的思路来优化,我们可以创建一个虚拟头节点指向头节点,现在我们就不需要判断头节点的有效,按照规则删除即可。
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return null;
}
//创建一个无效头节点
ListNode newHead = new ListNode(-1);
newHead.next = head;
ListNode pre = newHead;
while (pre.next != null) {
if (pre.next.val == val) {
pre.next = pre.next.next;
} else {
pre = pre.next;
}
}
return newHead.next;
}
}
链表的中间节点
给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
- 方法1
思路:先遍历一遍得到长度,然后在找到长度/2的节点
class Solution {
public ListNode middleNode(ListNode head) {
ListNode cur = head;
int size = 0;
while (cur != null) {
cur = cur.next;
size++;
}
int n = size / 2;
while (head != null && n != 0) {
head = head.next;
n--;
}
return head;
}
}
- 思路:
利用快慢指针,fast前进的步长始终时slow的两倍,当fast到尾部时,slow在中间。
class Solution {
public ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
链表的倒数第k个节点:JZ22
OJ链接
输入一个链表,输出该链表中倒数第k个结点。
- 方法1
思路:暴力遍历求得长度 size,然后遍历得到 size - k 处节点
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
int size = 0;
ListNode tmp = head;
while (tmp != null) {
tmp = tmp.next;
size++;
}
if (head == null || size < k || k <= 0) {
return null;
}
int cut = size - k;
tmp = head;
while(cut != 0) {
tmp = tmp.next;
cut--;
}
return tmp;
}
}
- 方法2
思路:fast指针先走k步,slow指针才开始走,此时二者相对距离为k步,当fast走到末尾时(此时移动size - k步), 对slow指针也走了size - k步,此时指向倒数第k个节点
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
ListNode fast = head;
ListNode slow = head;
while (fast != null) {
if (k != 0) {
k--;
} else {
//slow和fast的相对距离为k
slow = slow.next;
}
//先使fast走k步
fast = fast.next;
}
//判断k是否合法,如果k大于链表长度,此时k != 0,但fast已经为空;如果k小于0,fast和slow一起走,最后都为空,所以返回null
return k == 0 ? slow : null;
}
}
删除链表的倒数第N个节点:JZ18
这道题时上一道题的进阶,思路差不多,如果我们要删除倒数的N个节点,就需要找到其前一个结点,然后改变指向,所以我们只需要让fast走N+1步,当fast到链尾时,slow指向N - 1节点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fast = head;
ListNode slow = head;
//fast先走n步
while (fast != null && n != 0) {
fast = fast.next;
n--;
}
//如果fast==null,说明删除的是第一个节点
if (fast == null) {
return head.next;
}
//先使fast走一步,然后一起走
fast = fast.next;
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
//删除第N个节点
slow.next = slow.next.next;
return head;
}
}
合并两个有序链表:JZ25
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
- 方法1
思路:创建一个虚拟头节点,然后按照大小规则链接每一个节点,最后处理链表为空的情况。
/**
* Definition for singly-linked list.
* 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) {
ListNode dummyHead = new ListNode(0);
ListNode cur = dummyHead;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
//其中一个链表为空,链接另一个链表
cur.next = list1 == null ? list2 : list1;
return dummyHead.next;
}
}
- 方法2
递归:每次都把最小值压入栈,最后出栈的时候,将所有数连在一起就可以了。说白了,就是用一个栈维护了顺序。最后的连接,当然是小的连小的,所以l1 小,就连到 l1,l2 小就连到 l2,最后先返回的,就是最小的头结点。
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if(list1 == null) {
return list2;
}
if (list2 == null) {
return list1;
}
if (list1.val < list2.val) {
list1.next = mergeTwoLists(list1.next, list2);
return list1;
} else {
list2.next = mergeTwoLists(list1, list2.next);
return list2;
}
}
}
回文链表
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
- 方法1
思路:先使用快慢指针找到中间结点,然后反转,然后判断是否回文
/**
* Definition for singly-linked list.
* 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 boolean isPalindrome(ListNode head) {
//1.快慢指针找到中间节点
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//2.反转链表
ListNode cur = slow.next;
while (cur != null) {
ListNode curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}
//3.判断,由于slow的最后一个节点的结点域中存放的是
while (head != slow) {
if (slow.val != head.val) {
return false;
}
//如果是偶数
if (head.next == slow) {
return true;
}
head = head.next;
slow = slow.next;
}
return true;
}
}
从尾到头打印链表:JZ6
输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。
- 利用堆
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList ans = new ArrayList<>();
Stack stack = new Stack();
while (listNode != null) {
stack.push(listNode.val);
listNode = listNode.next;
}
while (!stack.empty()) {
ans.add(stack.pop());
}
return ans;
}
}
- 递归
ArrayList<Integer> ans = new ArrayList<>();
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if (listNode != null) {
printListFromTailToHead(listNode.next);
ans.add(listNode.val);
}
return ans;
}
}
- 头插
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> ans = new ArrayList<>();
while (listNode != null) {
ans.add(0,listNode.val);
listNode = listNode.next;
}
return ans;
}
}
两个链表的第一个公共结点:JZ52
输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。
思路:
假如两个链表是有交点的,我们定义p1,p2指针,分别从链表1和链表2开始遍历;
然后是p1,p2以相同速度移动,当任意一个到末尾时,上图是p2,此时它从链表1的头结点开始移动,也就是说任意一个为空时,从另一个链表头开始移动。由于速度相同它们的路程相同等于(链表1长度 + 链表2长度 - 公共部分长度)
所以:如果它们有公共结点时二者一定会相遇,如果没有那么它们走过的路程为(链表1长度 + 链表2长度) 所以最后都为null。
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while (p1 != p2) {
p1 = (p1 == null) ? pHead2 : p1.next;
p2 = (p2 == null) ? pHead1 : p2.next;
}
return p1;
}
}
删除链表中的重复结点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。
思路:
因为头结点可能会是重复结点,所以我们定义一个虚拟头节点,遍历链表,删除重复元素
public class Solution {
public ListNode deleteDuplication(ListNode pHead) {
ListNode dummyHead = new ListNode(-1);
dummyHead.next = pHead;
ListNode cur = dummyHead;
while (cur.next != null && cur.next.next != null) {
if (cur.next.val == cur.next.next.val) {
int tmp = cur.next.val;
//删除相同元素
while (cur.next != null && cur.next.val == tmp) {
cur.next = cur.next.next;
}
} else {
cur = cur.next;
}
}
return dummyHead.next;
}
}