445.用链表表示的两个数相加
题目描述:(情况1)
两个非空链表代表两个非负整数,数字最高位是头节点,每个节点存储一位数字,两数相加返回一个和的新链表。可以假设除了0之外,数字都不会以0开头。(这个假设没有实质性作用)
示例:
输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 8 -> 0 -> 7
特殊示例:(对于进位处理的深度考虑)
输入:5 + 5
输出:1->0
思路分析:
加法从低位开始,即从链表的尾节点往前操作,但是单链表只能从前往后。
从而考虑到利用栈来存放链表中的数字(链表的逆序处理要想到用栈,这点要想到!)
其中while循环的条件注意下!进位操作以及条件注意!
1.定义两个栈并将两个链表放入栈中
2.不断的将两个栈的栈顶相加,注意进位
3.循环条件:任意一个链表不为空 或者 进位c>0
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//由于加法的顺序(从低位开始)与链表的顺序相反,所以需要按逆序处理,则考虑到利用栈进行处理
Stack<Integer> s1 = new Stack<>();
Stack<Integer> s2 = new Stack<>();
while(l1 != null){
s1.push(l1.val);
l1 = l1.next;
}
while(l2 != null){
s2.push(l2.val);
l2 = l2.next;
}
//两个栈的栈顶相加(逐个),以及注意进位!
//注意一种特殊情况,5+5=10,所以再下面的循环判断中需要加一下如果 c>0 时循环继续!!!
int c = 0;
ListNode last = null;
while(!s1.isEmpty() || !s2.isEmpty() || c>0){
int sum = (s1.isEmpty()?0:s1.pop()) + (s2.isEmpty()?0:s2.pop()) + c;
c = sum/10;
ListNode temp = new ListNode(sum%10);
temp.next = last;
last = temp;
}
return last;
}
}
情况2:
反序存放,即链表首部存放个位,这样就可以直接从链表首部开始相加。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pre = new ListNode(-1);
ListNode head = pre;
int c = 0;//进位
while(l1 != null || l2 != null || c > 0){
int add = (l1==null? 0:l1.val) + (l2==null? 0:l2.val) + c;
c = add / 10;
int v = add % 10;
ListNode tmp = new ListNode(v);
pre.next = tmp;
pre = tmp;
if(l1 != null) l1 = l1.next;//如果哪个链表为空了,该链表就不往前走了,持续一直保持空
if(l2 != null) l2 = l2.next;
}
return head.next;
}
}
24.两两交换链表中的节点
题目描述:
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。>
示例:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
特殊样例分析:
[]; [1]; [1,2]; 分别是链表长度为0,1,2的情况;
当0或者1时,提前判断一下;
当长度为2时,由于有一个辅助节点(总长度为3),所以这种情况可跟其余的一起正常考虑。
即进入循环的链表长度需要 >=3(这种情况需要考虑,之前没有考虑到这种长度为2的情况)
思路分析:
首先,链表长度 <= 1时
若链表长度为2时,必须有辅助的第三个节点才能进行改变箭头的操作
错解:交换节点,如果按照交换两个值的那种思想(引入一个temp,交换),这样实际上只是指针在移动,而实际节点没有动,就跟遍历链表时一样只是指针在移动而已
正确做法:改变链表中箭头的方向的思想,画图可直观的增加理解
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null) return head;
//链表长度必须大于2,所以需要再增加一个节点使得两个节点的链表也能交换
ListNode pre = new ListNode(0);
pre.next = head;
head = pre;
while(pre.next != null && pre.next.next != null){//节点数有奇数和偶数两种,但是对循环条件不影响,由于要往后走两步,所以要确保后面两步分别都要不为空才能移动
ListNode slow = pre.next;
ListNode fast = pre.next.next;
pre.next = fast;
slow.next = fast.next;
fast.next = slow;
pre = slow;
}
return head.next;
}
}
82.删除链表中重复的所有元素
示例:
输入: 1->2->3->3->4->4->5
输出: 1->2->5
输入: 1->1->1->2->3
输出: 2->3
输入:1->1
输出:[]
思路分析:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head == null || head.next == null) return head;
ListNode pre = new ListNode(0);
pre.next = head;
head = pre;
while(pre.next != null && pre.next.next != null){
if(pre.next.val != pre.next.next.val){
pre = pre.next;
}
else{
ListNode p = pre.next;
ListNode q = pre.next.next;
while(p.val == q.val && q.next != null){
q = q.next;
}
**//以下两个if是上面循环退出的两个原因,需要分别分析做不同的后续处理**
if(q.next == null){
pre.next = null;
}
if(p.val != q.val){
pre.next = q;
}
}
}
return head.next;
}
}
143.重排链表
示例 1:
给定链表 1->2->3->4, 重新排列为 1->4->2->3.
示例 2:
给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.
思路分析
1.找中间节点(奇数为中间节点,偶数为中间第一个节点)
2.将前半段链表和后半段链表断开(断开时注意把中间节点后面的那个节点,即后半段链表的头节点保存一下)
3. 将后半段链表逆序
4. 将前半段链表和逆序后的后半段链表合并
这个思路很好,但是有可能不容易想到,需要加强
代码中注意的地方:
后半段逆序的时候,while的循环条件
合并两段链表时的while循环条件,只需要是L1 != null && L2 != null
在L1往后面移动时,是L1 = tmp1;而不是L1 = L1.next;(粗心错误陷入死循环,运行超时,因为那条链已经断了)
class Solution {
public void reorderList(ListNode head) {
if(head == null) return;
ListNode midNode = findmidNode(head);//找到中间节点
ListNode last = recovers(midNode.next);//这就是逆序后的后半段
midNode.next = null;//则从head,到midNode结束,这位前半段,砍断后面的
merge(head,last);
}
//利用快慢指针寻找中间节点,中间节点的next则为后半段(奇数则为中间节点,偶数则为中间左节点)
private ListNode findmidNode(ListNode head){
ListNode fast = head;
ListNode slow = head;
while(fast.next != null&& fast.next.next != null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
//链表逆序
private ListNode recovers(ListNode head){
ListNode last = null;
ListNode pre = head;
while(pre != null){//循环判断时直接判断该节点是否为空就可,不用判断pre.next,因为循环内指到了pre.next,但是它如果为空,那就直接是pre..next=null,这是可以的。空指针报错的情况应该是用到pre.next,但是pre为空了,这样是不行的。
ListNode tmp = pre.next;
pre.next = last;
last = pre;
pre = tmp;
}
return last;
}
//逐个合并两段链表
private ListNode merge(ListNode L1,ListNode L2){
ListNode pre = new ListNode(0);
pre.next = L1;
ListNode tmp1 = L1;
ListNode tmp2 = L2;
while(L1 != null && L2 != null){
tmp1 = L1.next;
L1.next = L2;
//L1 = L1.next;这个会陷入死循环,导致一直是时间按超出限制的错误,这个错误实在是不该犯!!!
L1 = tmp1;
tmp2 = L2.next;
L2.next = L1;
//L2 = L2.next;
L2 = tmp2;
}
return pre.next;
}
}

链表组件
题目描述:
给定链表头结点 head,该链表上的每个结点都有一个 唯一的整型值 。
同时给定列表 G,该列表是上述链表中整型值的一个子集。
返回列表 G 中组件的个数,这里对组件的定义为:链表中一段最长连续结点的值(该值必须在列表 G 中)构成的集合。
示例 1:
输入:
head: 0->1->2->3
G = [0, 1, 3]
输出: 2
解释: 链表中,0 和 1 是相连接的,且 G 中不包含 2,所以 [0, 1] 是 G 的一个组件,同理 [3] 也是一个组件,故返回 2。
示例 2:
输入:
head: 0->1->2->3->4
G = [0, 3, 1, 4]
输出: 2
解释: 链表中,0 和 1 是相连接的,3 和 4 是相连接的,所以 [0, 1] 和 [3, 4] 是两个组件,故返回 2。
思路分析:
首先利用无序集合set来加速查找速度;(用set加快查找速度很妙!!!)
然后将列表G复制给集合set中;
遍历链表,若当前节点在set中,但是其下一个节点为空或者不在set中的话,说明该节点是一段连续段的末尾,则组件数加1。(这个地方需要反向思维一下,因为一般看到这个题目可能会从列表遍历,去查找链表)
class Solution {
public int numComponents(ListNode head, int[] G) {
//使用set无序集合来加速查找速度,set的查找速度很快吗
Set<Integer> set = new HashSet<>();
//将列表G复制到无序集合中
for(int x: G){
set.add(x);
}
//遍历链表,如果当前节点在集合set中,但是下一个节点不在set中或者下一个节点为空,则这个节点是一段组件的末尾,需要组件数加1
ListNode cur = head;
int cnt = 0;
while(cur != null){
if(set.contains(cur.val) && (cur.next == null || !set.contains(cur.next.val))){
cnt++;
}
cur = cur.next;
}
return cnt;
}
}
02.08 环路检测
题目描述:
给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。
思路分析:
两种思路解析,精妙易懂!
public class Solution {
public ListNode detectCycle(ListNode head) {
//快慢指针
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){//当fast走到倒数第二个,其fast.next!=null,进入循环后使得fast=null,再判断时fast.next即空指针了,所以需要保证fast!=null
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
while(slow != head){
slow = slow.next;
head = head.next;
}
return slow;//当再次相遇时即为环的开始点,返回,注意返回的位置
}
}
return null;//如果没有进入while循环,说明时空链表或者只有一个节点,那么都是无环,返回null。
}
}
//执行用时:0 ms, 在所有 Java 提交中击败了100%的用户
//内存消耗:38.3 MB, 在所有 Java 提交中击败了84.10%的用户
/*
public class Solution {
public ListNode detectCycle(ListNode head) {
//用哈希表,定义一个set集合,判断链表中的节点是否存在于集合set中
Set<ListNode> set = new HashSet<>();
while(head != null){
if(!set.add(head)){//如果添加为false,则说明该节点重复出现了
return head;
}
head = head.next;
}
return null;
}
}
//执行用时:4 ms, 在所有 Java 提交中击败了19.10%的用户
//内存消耗:39.7 MB, 在所有 Java 提交中击败了5.61%的用户
*/
725. 分隔链表
题目描述:
将给定的链表分隔成k个连续的部分。每个部分的长度尽可能相等,差距不超过1,且前面部分的长度>=后面部分的长度。这意味着有的部分是为null。
输入:
root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释:
输入输出各部分都应该是链表,而不是数组。
输入:
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
思路分析:
- 先得需要知道链表的总长度cnt,然后每个部分的平均长度average = cnt / k;
若 cnt % k != 0的话,需要将余下的节点分别给前面的部分(每个部分只能给1个),即前cnt %k部分的节点数为average+1;后面部分的节点数为average。- 对于后面一些部分为null,这部分的处理。(需要注意!!!)
class Solution {
public ListNode[] splitListToParts(ListNode root, int k) {
ListNode cur = root;
int cnt = 0;
while(cur != null){
cnt ++;
cur = cur.next;
}
ListNode pre = cur;//得设置一个pre节点在cur节点前面,以便于断链处理
cur = root;
ListNode[] list = new ListNode[k]; //定义一个列表,用来装链表段
int len = cnt / k; //平均长度
int add = cnt % k; //前add个链表段每个需要额外加一个(因为任意两部分长度差距不超过1;前面的长度>=后面的)
for(int i = 0;i < k; i++){
ListNode head = cur;
for(int j = 0;j < len + (i<add?1:0);j ++){
if(cur != null){ //要注意不为空时是这样判断,如果是空了得话,直接把head=cur=null给列表就行了
pre = cur;
cur = cur.next;
}
}
if(cur != null){
pre.next = null;
}
list[i] = head; //此处列表,for(i)循环,每段(头节点)装入列表这三个过程很好的思想,需要好好回味!
}
return list;
}
}
1669. 合并链表
题目描述:
思路分析:
注意:要找到真正的那个节点,但是不要将该节点再另定义为临时节点。指的是就用找到的真实节点指,而不是再定义的临时指针。具体看程序中注释。
class Solution {
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode head = list1;
ListNode list1B = list1;
ListNode list1A = list1;
//先找到list1链表的a-1节点和b+1节点
int num = 0;
while(num <= b){
list1B = list1B.next;
num ++;
//if(num == a-1){
//ListNode tmpA = list1; //此时list1指在a-1节点,tmpA节点即为a-1节点,这种错误不要再犯了,因为这样只是定义了一个临时节点,它指向别的节点时相当于这个临时节点又跑去指向别的节点了,而不是原来的链断了指向别处了
//}
}
//此时list1B在b+1节点
num = 0;
while(num < a-1){
list1A = list1A.next;
num ++;
}//此时list1A在a-1节点
//再找到list2链表的头节点和尾节点
ListNode list2A = list2;
while(list2A.next != null){
list2A = list2A.next;
}//此时list2指在节点m-1
list1A.next = list2;
list2A.next = list1B;
return list1;
}
}
1171. 从链表中删去总和值为零的连续节点
题目描述:
给你一个链表的头节点 head,请你编写代码,反复删去链表中由 总和 值为 0 的连续节点组成的序列,直到不存在这样的序列为止。
删除完毕后,请你返回最终结果链表的头节点。
你可以返回任何满足题目要求的答案。
输入:head = [1,2,-3,3,1]
输出:[3,1]
提示:答案 [1,2,1] 也是正确的。
class Solution {
public ListNode removeZeroSumSublists(ListNode head) {
//非常精妙的方法,只需要利用哈希表HashMap遍历两遍
ListNode pre = new ListNode(0);//定义辅助节点0,以更好处理所有节点和为0的情况
pre.next = head;
ListNode nodeValue = pre;//nodeValue指将节点作为哈希表的value
Map<Integer,ListNode> map = new HashMap<>();
//遍历链表,以建立节点和(key)<-->节点(value)的哈希表
//当出现相同的和,之前节点value会被覆盖,value更新为当前节点
int sumKey = 0;//节点和作为哈希表的key
while(nodeValue != null){
sumKey += nodeValue.val;
map.put(sumKey,nodeValue);
nodeValue = nodeValue.next;
}
//第二遍遍历链表求和,并与哈希表进行对比处理
//若当前节点出的和在下一个节点处出现了,说明两个节点之间的所有节点和为0,直接删除之间的所有节点,若不是,同样与上面的程序语句也可以使得正常指向下一个节点
nodeValue = pre;
sumKey = 0;
while(nodeValue != null){
sumKey += nodeValue.val;
nodeValue.next = map.get(sumKey).next;
nodeValue = nodeValue.next;
}
return pre.next;
}
}
//双指针法
class Solution{
public ListNode removeZeroSumSublists(ListNode head){
ListNode pre = new ListNode(0);
pre.next = head;
if(head == null) return null;
if(head.next == null){
if(head.val == 0) return null;
else return head;
}
ListNode p = pre;
int sum = 0;
while(p != null){//p.next
ListNode q = p.next;
sum = 0;
while(q != null){
sum += q.val;
if(sum == 0){
break; //如果有多个循环,最内层循环break的话,是跳出最内层循环
}
q = q.next;
}
//判断内层循环结束的原因并分别处理
if(sum == 0 && q != null){
p.next = q.next;
//p = p.next;
}
else{//q==null,说明这一趟没有和为0
p = p.next;
}
}
return pre.next;
}
}
19. 删除链表中的倒数第n个节点
//法1:用快慢指针,先让快指针走n步,再快慢指针一起走;当先走的快指针到末尾节点时,慢指针正好到倒数第n个节点的前一个节点(便于删除)。看间隔更容易理解(快慢指针间隔为n)
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode pre = new ListNode(0);//定义虚拟节就可使得头结点不必另外单独处理
pre.next = head;
ListNode slow = pre;
ListNode fast = pre;
if(head==null || head.next==null) return null;
while(n > 0){
fast = fast.next;
n--;
}
while(fast.next != null){//使slow指在倒数第n个节点的前一个节点,便于删除
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return pre.next;
}
}
//法2:栈
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
Stack<ListNode> stack = new Stack();
ListNode dummy = new ListNode(0);
dummy.next = head;
head = dummy;
while(head != null){
stack.push(head);
head = head.next;
}
for(int i = 0;i < n;i++){
stack.pop();
}
//通过以上出栈,将得到栈定元素为倒数第n个节点的前一个节点,便于删除
ListNode pre = stack.peek();
pre.next = pre.next.next;
return dummy.next;
}
}
判断环的入口和是否有环
1.判断是否有环
//根据龟兔赛跑原理,快指针一定会追上慢指针,并套了很多圈
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null) return false;//空链表或一个节点链表无环
ListNode slow = head;
//ListNode fast = head;
ListNode fast = head.next;//这个地方fast的初始化需要注意!!!
while(slow != fast){
if(fast==null || fast.next==null){
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
2.判断环的入口
//快慢指针法
//注意点:slow和fast的初始化为啥不在同一节点head?若相同,则一开始slow=fast,那么while循环是先判断再执行,所以while循环就进不去了。
//可以理解为:有虚拟节点pre,slow从pre开始走了一步到head,fast从pre开始走两步到head.next,这样就相当于是从同一节点出发的,符合龟兔赛跑原理
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null) return head;//空链表或一个节点链表无环
ListNode slow = head;
//ListNode fast = head;
ListNode fast = head.next;//这个地方fast的初始化需要注意!!!
while(slow != fast){
if(fast==null || fast.next==null){
return null;
}
slow = slow.next;
fast = fast.next.next;
}
ListNode pre = new ListNode(0);
pre.next = head;
while(slow != pre){//按照slow和fast的初始化条件,同一出发点是pre节点,而不是head,若写成head的话,就死循环了!!!
slow = slow.next;
pre = pre.next;
}
return slow;
}
}
147.对链表进行插入排序!!!
class Solution {
public ListNode insertionSortList(ListNode head) {
if(head == null || head.next == null) return head;
//插入排序
ListNode dummy = new ListNode(-1);//虚拟节点,便于在头结点之前插入
dummy.next = head;
ListNode lastSort = head;//lastSort为有序部分的最后一个节点
ListNode cur = lastSort.next;//cur为当前待插入节点
//插入排序处理
while(cur != null){
if(lastSort.val <= cur.val){
lastSort = lastSort.next;
cur = lastSort.next;
}
else{
lastSort.next = cur.next;//先把cur后的节点保存,免得链断了找不到了
ListNode prev = dummy;//pre用于在有序部分遍历
while(prev.next.val <= cur.val){
prev = prev.next;
}
//插入cur节点
cur.next = prev.next;//在两个节点之间插入cur,断链重指有顺序要求
prev.next = cur;
cur = lastSort.next;
}
}
return dummy.next;
}
}
86.分隔链表
题目描述:给你一个链表和一个特定值 x ,请你对链表进行分隔,使得所有小于 x 的节点都出现在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
思路:设置左右虚拟节点,以及左右遍历指针。
对于当前节点,首先保存其后驱节点,再将其从原链中断开连接到左或右链表。因为有断链(即每个当前节点都会从原链中断开),所以每个处理节点在原链表中都是首节点,所以很好处理。
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode leftOne = new ListNode(-1);
ListNode left = leftOne;
ListNode rightOne = new ListNode(-1);
ListNode right = rightOne;
ListNode cur = head;
if(head == null) return null;
while(cur != null){
ListNode next = cur.next;
if(cur.val < x){
left.next = cur;
cur.next = null;//断链,将该节点与链断开;因为每个被处理节点将与链断开,所以每个处理节点都是原来链的头结点
left = left.next;
}
else{
right.next = cur;
cur.next = null;
right = right.next;
}
cur = next;
}
left.next = rightOne.next;
right.next = null;
return leftOne.next;
}
}
328.奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head == null || head.next == null) return head;
ListNode cur = head;
ListNode last = cur.next;
ListNode tmp1 = new ListNode(0);
tmp1.next = cur;
ListNode tmp2 = new ListNode(0);
tmp2.next = last;
while(cur.next != null && last.next != null){
cur.next = last.next;
cur = cur.next;
last.next = cur.next;
last = last.next;
}
cur.next = tmp2.next;
return tmp1.next;
}
}
判断两个链表是否相交并返回交点
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//双指针法
//走完自己的路,再走对方的路,到交叉点时走了相同距离的路,即相遇,
//h1+s+h2 = h2+s+h1
//这道题的思路特别好,主要是一种思路包含了多种情况
ListNode L1 = headA;
ListNode L2 = headB;
while(L1 != L2){
//L1 = L1.next;
//L2 = L2.next;
if(L1 == null)
L1 = headB;
else
L1 = L1.next;
if(L2 == null)
L2 = headA;
else
L2 = L2.next;
}
return L1;
}
}
*/
//法二:计算两个链表的长度,长的一个先走一部分,再一起从相同的距离走
//这种情况的话,只需要走一遍,若中途有相同节点则有交点,若走到尾相同节点是null,则没有交点。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode LA = headA;
ListNode LB = headB;
int lenA = length(LA);
int lenB = length(LB);
int len = 0;
if(LA == null || LB == null) return null;
//先走多余长度的那部分
if(lenA > lenB){
len = lenA - lenB;
while(len > 0){
LA = LA.next;
len--;
}
}
else if(lenA < lenB){
len = lenB - lenA;
while(len > 0){
LB = LB.next;
len--;
}
}
//从相同的位置开始寻找交点
while(LA != LB){
LA = LA.next;
LB = LB.next;
}
return LA;
}
//计算链表长度方法,记住另外定义的方法一定要放在原来主方法的外面,类里面
int length(ListNode head){
int len = 0;
while(head != null){ //先指再加
len++;
head = head.next;
}
return len;
}
}
本文深入讲解了多种链表算法,包括两数相加、两两交换节点、删除重复元素等核心问题及其解决思路,提供了丰富的示例和代码实现。



&spm=1001.2101.3001.5002&articleId=111053699&d=1&t=3&u=7dd8437050bc42c89f38293c264559a9)
3067

被折叠的 条评论
为什么被折叠?



