目录
一、83. 删除排序链表中的重复元素
难度简单
1.题目描述
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
2.分析:
package IMUHERO;
import java.util.HashSet;
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
//给定的是一个排序链表
//public ListNode deleteDuplicates(ListNode head) {
// ListNode current = head;
// while (current != null && current.next != null) {
// if (current.next.val == current.val) {
// current.next = current.next.next;
// } else {
// current = current.next;
// }
// }
// return head;
// }
1.用一个容器hashset存储重复情况
2.cur节点用来遍历所有,head用来连接不重复出现的节点
3.dummyHead节点连接head,用来返回结果
4.每次查询容器中的重复情况,如果不重复,则将其连接到head中
//给定的不是一个排序链表
class Solution83 {
public ListNode deleteDuplicates(ListNode head) {
if(head==null)return null;
HashSet<Integer> set = new HashSet<>();
set.add(head.val);
ListNode dummyHead=new ListNode(-1);
dummyHead.next=head;
ListNode cur=head;
while(cur.next!=null){
if(!set.contains(cur.next.val)){
set.add(cur.next.val);
head.next=cur.next;
head=head.next;
}
cur=cur.next;
}
head.next=null;
return dummyHead.next;
}
}
二、82. 删除排序链表中的重复元素 II
难度中等
1.题目描述
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例 1:
输入: 1->2->3->3->4->4->5
输出: 1->2->5
示例 2:
输入: 1->1->1->2->3
输出: 2->3
2.分析:
(1)定义三个节点:pre/cur/last
(2)cur和last用来判断是否相等,判断过程只移动last,最终找到不相等的第一个节点,让cur=last,last=last.next,就可以删除掉相等的这部分节点啦
(3)pre用来衔接相等节点之前的节点,令pre.next=cur,就可以完成衔接。
注意:1.if(head.next.next==null&&head.val==head.next.val)return null;用来解决[3,3,3,4,5]这类问题
2.if (pre.next.next!=null)pre.next=null;//避免[1,2,2]这种情况,理论上pre/cur/next最终一定是在最后三个位置的节点上的
class Solution {
public ListNode deleteDuplicates(ListNode head) {
//确保前两个节点存在
if(head==null)return null;
if(head.next==null)return head;
if(head.next.next==null&&head.val==head.next.val)return null;
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
ListNode pre = dummyHead;
ListNode cur = dummyHead.next;
ListNode last = dummyHead.next.next;
while(last!=null){
if(cur.val!=last.val){
last=last.next;
cur=cur.next;
pre=pre.next;
}
else{
while(last!=null){
if(cur.val!=last.val){
cur=last;
last=last.next;
pre.next=cur;
break;
}
last=last.next;
}
}
}
if (pre.next.next!=null)pre.next=null;//避免[1,2,2]这种情况,理论上pre/cur/next最终一定是在最后三个位置的节点上的
return dummyHead.next;
}
}
三、21. 合并两个有序链表
难度简单
1.题目描述
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
2.分析:类似于归并排序,使用cur进行穿针引线,使用cur1和cur2进行定位
注意:由于采用的是while(cur1!=null&&cur2!=null)作为循环终止条件,因此会出现一边遍历完,另一边还存在节点的情况,
这时cur.next=(cur1!=null)?cur1:cur2; 一个三目语句可解决问题
因为cur1如果不等于空,表示cur2必然为空
cur1如果为空,那么cur2不为空或者让cur2为空,都可以直接接上去。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummyHead = new ListNode(-1);
ListNode cur1=l1;
ListNode cur2=l2;
ListNode cur=dummyHead;
while(cur1!=null&&cur2!=null){
if(cur1.val<=cur2.val){
cur.next=cur1;
cur1=cur1.next;
}
else{
cur.next=cur2;
cur2=cur2.next;
}
cur=cur.next;
}
cur.next=(cur1!=null)?cur1:cur2;
return dummyHead.next;
}
}
四、203.删除链表中元素e
1.定义虚拟头结点:
class Solution2 {
public ListNode removeElements(ListNode head, int val) {
ListNode dummyhead =new ListNode(1);
dummyhead.next=head;
ListNode prev=dummyhead;
while (prev.next!=null){
if (prev.next.val==val){
ListNode delNode=prev.next;
prev.next=delNode.next;
delNode.next=null;
}else prev=prev.next;
}
return dummyhead.next;
}
}
2.使用递归算法
class Solution3 {
public ListNode removeElements(ListNode head, int val) {
if (head==null)return head;
head.next=removeElements(head.next,val);
if (head.val==val) {
return head=head.next;
}else
return head;
//也可以:return head.val==val?head.next:head;
}
}
五、leetcode 86. 分隔链表
难度中等
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例:
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5
我们可以用两个指针before
和 after
来追踪上述的两个链表。两个指针可以用于分别创建两个链表,然后将这两个链表连接即可获得所需的链表。
- 初始化两个指针 before 和 after。在实现中,我们将两个指针初始化为哑 ListNode。这有助于减少条件判断。
- 利用
head
指针遍历原链表。 - 若
head
指针指向的元素值 小于x
,该节点应当是before
链表的一部分。因此我们将其移到before
中。 - 否则,该节点应当是
after
链表的一部分。因此我们将其移到after
中。 - 遍历完原有链表的全部元素之后,我们得到了两个链表
before
和after
。原有链表的元素或者在before
中或者在after
中,这取决于它们的值。 - 现在,可以将
before
和after
连接,组成所求的链表。
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode before_head = new ListNode(0);
ListNode before = before_head;
ListNode after_head = new ListNode(0);
ListNode after = after_head;
while (head != null) {
if (head.val < x) {
before.next = head;
before = before.next;
} else {
after.next = head;
after = after.next;
}
head = head.next;
}
after.next = null;
before.next = after_head.next;
return before_head.next;
}
}
六、leetcode 328. 奇偶链表
难度中等
题目描述
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
分析:
1.设置两个虚拟头节点,用于存储结果
2.设置cur、curOdd、curEven,分别用于存储当前遍历节点、当前奇数节点、当前偶数节点
3.i用于判断当前应该存储奇数节点或偶数节点
一层循环后:dummyHeadOdd后面存储了奇数链表,dummyHeadEven后面存储了偶数链表
curOdd存储最后一个奇数节点、curEven存储最后一个偶数节点
4.一定要记得curEven.next=null,因为无论总数有多少个,偶数节点的最后一个一定是null,也是整个链表的结尾
5.最后curOdd.next=dummyHeadEven.next,即完成对接。
//我的解答
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head==null||head.next==null)return head;
ListNode dummyHeadOdd=new ListNode(-1);//奇数虚拟头结点
ListNode dummyHeadEven=new ListNode(-1);//偶数虚拟头结点
dummyHeadOdd.next=head;
ListNode cur=dummyHeadOdd; //用于遍历总的数组
ListNode curOdd=dummyHeadOdd; //用于遍历奇数部分
ListNode curEven=dummyHeadEven;//用于存储偶数部分
int i=1;
while(cur.next!=null){
if(i%2!=0){
curOdd.next=cur.next;
curOdd=curOdd.next;
}
if(i%2==0){
curEven.next=cur.next;
curEven=curEven.next;
}
i++;
cur=cur.next;
}
curEven.next=null;
curOdd.next=dummyHeadEven.next;
return dummyHeadOdd.next;
}
}
七、leetcode 2.两数相加
难度中等2667收藏分享切换为英文
1.题目描述
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
2.分析:
(1)就像我们在纸上计算两个数字的和那样,我们首先从最低有效位也就是列表 l1l1 和 l2l2 的表头开始相加。
(2)由于每位数字都应当处于 0 \ldots 90…9 的范围内,我们计算两个数字的和时可能会出现 “溢出”。
例如,5 + 7 = 125+7=12。在这种情况下,我们会将当前位的数值设置为 22,并将进位 carry = 1carry=1 带入下一次迭代。
(3)进位 carrycarry 必定是 00 或 11,这是因为两个数字相加(考虑到进位)可能出现的最大和为 9 + 9 + 1 = 199+9+1=19。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummyHead=new ListNode(-1);
ListNode cur=dummyHead;
ListNode p=l1;
ListNode q=l2;
int carry=0;
while (p!=null||q!=null){
int a=(p!=null)?p.val:0;
int b=(q!=null)?q.val:0;
int sum=a+b+carry;
carry=sum/10;
cur.next=new ListNode(sum%10);
cur=cur.next;
if (p!=null)p=p.next;
if (q!=null)q=q.next;
}
if (carry==1){
cur.next=new ListNode(1);
}
return dummyHead.next;
}
注意:
(1)通过 while (p!=null||q!=null) 来做条件判断,确保长短不一的情况也能全部遍历
(2)通过 int a=(p!=null)?p.val:0; 三目语句,可以更加有效的分辨不同情况
(3)由于carry总是在sum之后才产生,因此最后一位进位不会被包含在循环判断中,要多一次判别
if (carry==1){
cur.next=new ListNode(1);
}
八、leetcode 445. 两数相加 II
难度中等
1.题目描述
给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7
2.解题分析:
(1)整体思路和上题:两数相加 是一致的,都是通过加法和带进位的方式来完成;
(2)不同的是,这题高位在前,低位在后,因此通过两个栈来存储数据,使得先弹出的值是低位的,方便相加。
(3)这里要注意的是,如何按照上题dummyHead和cur遍历的方法,得出结果的cur总是从低位指向高位。而结果需要的是从高位指向低位,因此采用ListNode head = null ;
循环:ListNode cur = new ListNode(sum%10) , cur.next=head ,head=cur 的方式反转链表
最后输出结果return head 即可正确解答。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Stack<Integer> one = new Stack();
Stack<Integer> two = new Stack();
ListNode dummyHead = new ListNode(-1);
ListNode p=l1;
ListNode q=l2;
// ListNode cur = dummyHead;
int carry = 0;
while(p!=null){
one.push(p.val);
p=p.next;
}
while(q!=null){
two.push(q.val);
q=q.next;
}
ListNode head=null;
while(!one.isEmpty()||!two.isEmpty()){
int a = (one.isEmpty())?0:one.pop();
int b = (two.isEmpty())?0:two.pop();
int sum = a+b+carry;
carry = sum/10;
ListNode cur=new ListNode(sum%10); //务必要注意,对结果还要进行一次反转,结果也是从高位到低位
cur.next=head;
head=cur;
}
if(carry!=0){
ListNode cur=new ListNode(carry);
cur.next=head;
head=cur;
}
return head;
}
}
九、剑指offer25 复杂链表的复制
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
分为三步:
1.在每一个节点后面各插入一个复制节点,此时不考虑random指针,只考虑next指针。
2.考虑random指针,复制的链表的所有random都是前一个节点的random.next;即copy.random=pre.random.next。
3.由于题目要求不能包含任何引用,所以需要将原链表,已经复杂链表做拆分。
package IMUHERO;
class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
public class sword25_copy_complex_LinkedList {
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead==null)return null;
//1.复制原来的链表的next直接,直接衔接在每一个节点后面
RandomListNode cur1=pHead;
while(cur1!=null){
RandomListNode copy=new RandomListNode(cur1.label);
copy.next=cur1.next;
cur1.next=copy;
cur1=copy.next;
}
//2.复制random指针,由于之前复制的节点都是直接衔接在后面,所以random也在后面
RandomListNode cur2=pHead;
while(cur2!=null){
if(cur2.random!=null)
cur2.next.random=cur2.random.next;
cur2=cur2.next.next;
}
//3.分割链表
RandomListNode copyHead=pHead.next;
RandomListNode cur3=copyHead;
RandomListNode cur=pHead;
while(cur!=null){
cur.next=cur.next.next;
if(cur3.next!=null){
cur3.next=cur3.next.next;
}
cur3=cur3.next;
cur=cur.next;
}
return copyHead;
}
}
十、剑指offer(十六)合并两个排序的链表
1.题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
2.解题分析
1.利用归并排序的思维;
2.首先保存头节点,用于return;
3.新创建一个链表,对比两边的链表中的节点元素,较小的元素放进新链表,直至这两个链表中有一个为空,那么直接将.next指向不空的链表的当前节点,链接起来就是最终排好序的链表。
3.实现代码
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode pre3=new ListNode(0);
ListNode preRet=pre3;
while(list1!=null){
if(list2==null){
pre3.next=list1;
return preRet.next;
}
else if(list1.val<list2.val){
pre3.next=new ListNode(list1.val);
pre3=pre3.next;
list1=list1.next;
}
else{
pre3.next=new ListNode(list2.val);
pre3=pre3.next;
list2=list2.next;
}
}
pre3.next=list2;
return preRet.next;
}
}
十一、剑指offer(十五)反转链表
输入一个链表,反转链表后,输出新链表的表头。
定义一个初始链表,假设为:1->2->3->4->5->
1.定义三个LiseNode :cur,pre,next,分别指向当前节点,前一个节点和后一个节点;
2.①next=cur.next ②cur.next=pre ③cur=next
注意:此处先next=cur.next的目的是,让cur的下一个元素“找得到”
比如果将1->2->3中1->2反转,那么原来的链表就会变成1<-2 3->4->5,就无法通过cur.next来找到3这个元素所在的节点
3.通过这种方式进行遍历,到达尾节点后,也就是cur==null的时候,这是pre就是我们要的头结点,直接return就好!
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode cur=head;
ListNode pre=null;
ListNode next=null;
while(cur!=null){
next=cur.next;
cur.next=pre;
pre=cur;
cur=next;
}
return pre;
}
}
十二、剑指offer(十四)链表中倒数第K个节点
1.题目描述
输入一个链表,输出该链表中倒数第k个结点。
2.解题分析:
1.定义两个标记:index1,index2,同时指向头节点;
2.移动index1,使之指向第K个节点,此时index1和index2之间的距离为K;
3.接下去同时将index1和index2向后移动,当index1 到达尾节点时,由于index1和index2的距离为K,所以index2所在节点即为倒数第K个节点。
3.实现代码
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
int []arr=new int[2];
public ListNode FindKthToTail(ListNode head,int k) {
/*方法一:定义两个标记,同时指向头结点,先让last节点移动k个位置,然后last和
/cur同时向后移动,当last到达最后一个位置的时候,cur即为倒数第k个元素*/
ListNode cur=head;
ListNode last=head;
int i=0;
while(last!=null){
last=last.next;
i++;//i用于记录队列总共有多少个元素
if(i>k){
cur=cur.next;
}
}
if(k>i)return null;//若k比元素的个数还多,比如5个元素的倒数第6个,则返回空
return cur;
}
}
十三、剑指offer(三十六)两个链表的公共节点
1.题目:
输入两个链表,找出它们的第一个公共结点。
2.分析:
/**
* IMUHERO
* 2019/8/7
* 1.先计算出两条链表的长度
* 2.长度长的先cur=cur.next,向后走lenMax-lenMin
* 3.之后同时向后走,并且判断走到的节点是否相等,如果相等则返回
*/
package IMUHERO;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Sword36_Find_First_Common_Node {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode cur1=pHead1;
ListNode cur2=pHead2;
int len1=0;
int len2=0;
//l1:1->2->null
//l2:1->3->2->null
while(cur1!=null){
len1++;
cur1=cur1.next;
}
while(cur2!=null){
len2++;
cur2=cur2.next;
}
cur1=pHead1;
cur2=pHead2;
if(len1>len2){
int pre=0;
while(pre<len1-len2){
cur1=cur1.next;
pre++;
}
while(cur1!=null){
if(cur1==cur2)return cur1;
cur1=cur1.next;
cur2=cur2.next;
}
}
else{
int pre=0;
while(pre<len2-len1){
cur2=cur2.next;
pre++;
}
while(cur2!=null){
if(cur1==cur2)return cur2;
cur1=cur1.next;
cur2=cur2.next;
}
}
return null;
}
}
十四、剑指offer(五十四)链表中环的入口节点
1.题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
2.分析:
使用一个HashSet来存储每个节点,遍历一遍查看是否有重复的。
3.代码
package IMUHERO;
import java.util.HashSet;
public class Sword54_EntryNodeOfLoop {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
ListNode ret =null;
HashSet<ListNode> set = new HashSet<>();
ListNode cur = pHead ;
while(cur!=null){
if(set.contains(cur)){
return cur;
}
else{
set.add(cur);
cur = cur.next;
}
}
return null;
}
}
十五、剑指offer(五十五)删除链表中重复的节点
1.题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,
重复的结点不保留,返回链表头指针。
例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
2.分析:
1.定义三个节点,分别是pre/cur/next,这三个节点用于指明前一个节点、当前节点 以及后一个节点
2.遍历链表,直至nextt==null,遍历结束
3.遍历过程中,如果出现cur.val==nextt.val ,那么表示出现重复。
由于本题是排序数组,可以躺nextt向后移动直到二者不相等,就是第一个不相等的元素。
令: cur = nextt;
pre.next = cur;
if(nextt!=null){
nextt=nextt.next;
}
这样就可以直接把重复的所有节点直接跳过,剔除!
4.如果不相等,则三个节点同时向后移动一个位置。
5.初始时定义好虚拟头阶点,并将引用指向pHead,最后返回dummyHead作为结果即可。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode deleteDuplication(ListNode pHead)
{
if(pHead==null)return null;
if(pHead.next==null)return pHead;
if(pHead==pHead.next)return null;//保证前两个节点存在,且不相等
ListNode dummyHead = new ListNode(-1);
dummyHead.next = pHead;
ListNode pre = dummyHead ;
ListNode cur = pHead;
ListNode nextt = cur.next;
while(nextt!=null){
if(cur.val==nextt.val){
while(nextt!=null&&nextt.val==cur.val){
nextt=nextt.next;
}
cur = nextt;
pre.next = cur;
if(nextt!=null){
nextt=nextt.next;
}
}
else{
pre = cur;
cur = nextt;
nextt = nextt.next;
}
}
return dummyHead.next;
}
}