目录
4.3.1.解法一:将整个链表反转,再将前k个和后n-k个链表反转即可
4.3.3.解法三:找到倒数第k个,也就是正数第lenth-k+1个
遇到问题时首先将常用的数据结构和算法思想都想一遍
常见的数据结构有数组,链表,队,栈,Hash,集合,树,堆,常见的算法思想有查找,排序,双指针,递归,迭代,分治,贪心,回溯和动态规划
注意做链表题目时要时刻注意给你的链表是否为空链表!!!
1.两个链表的第一个公共子节点
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交:
两个链表的头节点已知,相交之后成为一个单链表,但相交的节点未知,需找到两个链表的合并点。
解法一:Hash
先将第一个链表存入Map中,然后遍历第二个链表,检查节点是否在Map存在
public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
if (pHead1==null||pHead2==null){
return null;
}
HashMap<ListNode,Integer> hashMap=new HashMap<>();
while(pHead1!=null){
hashMap.put(pHead1,null);
pHead1=pHead1.next;
}
while (pHead2!=null){
if(hashMap.containsKey(pHead2)){
return pHead2;
}
pHead2=pHead2.next;
}
return null;
}
解法二:集合
思想与Hash一样
public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
Set<ListNode> set=new HashSet<>();
while (headA!=null){
set.add(headA);
headA=headA.next;
}
while(headB!=null){
if (set.contains(headB))
return headB;
headB=headB.next;
}
return null;
}
解法三:栈
将两个链表全部压入栈中,再取栈顶元素,找到最后一组相同的节点(因为栈后进先出)即可
public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
Stack<ListNode> stackA=new Stack<>();
Stack<ListNode> stackB=new Stack<>();
while(headA!=null){
stackA.push(headA);
headA=headA.next;
}
while(headB!=null){
stackB.push(headB);
headB=headB.next;
}
ListNode preNode=null;
while(!stackA.isEmpty() && !stackB.isEmpty()){
if(stackA.peek()==stackB.peek()){
preNode=stackA.pop();
stackB.pop();
}else {
break;
}
}
return preNode;
}
2.回文链表
给你一个单链表的头节点 head
,请你判断该链表是否为回文链表。如果是,返回 true
;否则,返回 false
。
示例 1:
输入:head = [1,2,2,1] 输出:true
解法一:全部压栈,全部比较。
将链表中的元素全部压栈,然后一边出栈,一边重新遍历链表,比较两者的元素值
public static boolean isPalindrome(ListNode head) {
Stack<ListNode> stack=new Stack<>();
ListNode node=head;
while (node!=null){
stack.push(node);
node=node.next;
}
while(!stack.isEmpty() &&head!=null){
if(stack.pop().val!=head.val){
return false;
}
head=head.next;
}
return true;
}
解法二:优化解法一
全部压栈,比较一半,遍历链表,将元素全部压入栈中,获得链表的总长度,比较一半的元素即可
public static boolean isPalindrome(ListNode head) {
Stack<ListNode> stack=new Stack<>();
ListNode node=head;
int count=1;
int newCount=0;
while (node!=null){
stack.push(node);
node=node.next;
count++;
}
newCount=count/2;
while (count!=newCount){
if(stack.pop().val!=head.val){
return false;
}
head=head.next;
count--;
}
return true;
}
解法三:双指针
将链表前半段反转创建一个新的链表,从头开始比较新的链表和slow指向的结点
public static boolean isPalindrome(ListNode head) {
//快慢指针,最优解
ListNode fast=head;
ListNode slow=head;
ListNode newHead=null;//新头
ListNode node=head;
//当链表节点为奇数个时fast.next=null,偶数时fast=null
while(fast!=null&&fast.next!=null){//fast到达最后一个节点,slow到达一半
fast=fast.next.next;
slow=slow.next;
//反转链表,,头插法
node.next=newHead;
newHead=node;
node=slow;
}
//当链表节点为奇数个时slow=slow.next
if(fast!=null){//奇数个
slow=slow.next;
}
while (slow.next!=null){
if (newHead.val!=slow.val){
return false;
}
newHead=newHead.next;
slow=slow.next;
}
return true;
}
3.合并链表
3.1合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
将两个升序链表合并为一个新的升序链表并返回,可以新建一个链表,然后分别遍历两个链表,每次比较并将小的那个节点接到新的链表上去,最后排完,另外可能还有一个链表不为空,直接将不为空的那个链表接到新链表上去。
解法一:新建一个链表
public static ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummyNode=new ListNode(-1);//虚拟节点,为了方便处理头节点
ListNode newHead=dummyNode;
while (list1!=null&&list2!=null){
if (list1.val<=list2.val){
dummyNode.next=list1;
list1=list1.next;
}else {
dummyNode.next=list2;
list2=list2.next;
}
dummyNode=dummyNode.next;
}
/*while (list1!=null){
dummyNode.next=list1;
}
while (list2!=null){
dummyNode.next=list2;
}*/
/*这里用两个while循环也可以,只有一个while会执行,但再leetcode上会运行超时,所以选
用下面这种表达式的写法*/
dummyNode.next=list1==null?list2:list1;
return newHead.next;
}
解法二:递归
public static ListNode mergeTwoListsByRe(ListNode l1, ListNode l2) {
if (l1==null){
return l2;
}
if (l2==null){
return l1;
}
if (l1.val<= l2.val){
l1.next=mergeTwoListsByRe(l1.next,l2);
return l1;
}else {
l2.next=mergeTwoListsByRe(l1,l2.next);
return l2;
}
}
3.2合并K个有序链表
将合并两个有序链表的问题搞清楚了,合并K个也就是一个循环的事了,需要提前将链表装进数组中,再通过循环调用合并两个链表的方法,因为合并两个链表的返回值为新链表的头节点,所以无需担心这里得到的返回值。
public static ListNode mergeKLists(ListNode[] lists) {
ListNode res=null;
for (ListNode list : lists) {
res=mergeTwoLists(res,list);
}
return res;
}
3.3合并两个链表
给你两个链表 list1
和 list2
,它们包含的元素分别为 n
个和 m
个。请你将 list1
中下标从 a
到 b
的全部节点都删除,并将list2
接在被删除节点的位置。
下图中蓝色边和节点展示了操作后的结果:
注意,这里说的是下标a,b,下标是从0开始的,所以找到a的前一节点,b的后一节点,再将list2接进来即可。
public static ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode node1=null;
ListNode node2=null;
ListNode node3=list2;
ListNode dummyNode=new ListNode(-1);//处理头节点,如果a=0即从头节点开始删除
dummyNode.next=list1;
node1=dummyNode;
node2=dummyNode;
int count=0;
while (count<b+2){//node2到达被删的节点b的后一节点
if (count<a)//node到达被删的节点a的前一节点
node1=node1.next;
node2=node2.next;
count++;
}
while (list2.next!=null){
list2=list2.next;
}
node1.next=node3;
list2.next=node2;
return list1;
}
4.双指针专题
4.1.寻找中间节点
寻找一个链表的中间节点,可以利用快慢指针,即双指针,fast一次走两步,slow一次一步,当块fast指针到达链表尾部或外部时,慢指针slow刚好到达链表的中间,链表就直接返回slow,因为不管链表是不是奇数,偶数,slow指针都刚好满足题目的要求。
public ListNode middleNode(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
4.2寻找倒数第k个元素
输入一个链表,输出链表中倒数第k个节点
eg:1->2->3->4->5,k=2;
返回链表:4->5;
将fast向后遍历到第K+1个节点,slow指向第一个节点,此时两个指针之间刚好间隔k个节点,此时两个指针同步向后走,当fast走到最后一个节点时,slow指向倒数第k个节点。
注意k可能大于链表的长度。
public static ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast=head;
ListNode slow=head;
while ((fast!=null&&k>0)){
fast=fast.next;
k--;
}
while (fast!=null){//此时slow指向倒数第k个节点,而fast=null
fast=fast.next;
slow=slow.next;
}
return slow;
}
4.3.旋转链表
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
eg:1->2->3->4->5,k=2;
返回链表:4->5->1->2->3;
4.3.1.解法一:将整个链表反转,再将前k个和后n-k个链表反转即可
4.3.2.解法二:双指针
快指针向前走k,再同步向前走,使slow.next指针指向倒数第k个节点,fast再指向头节点,令slow.next=null即可。简单点说,此题可以理解为就是将链表拆分成两份,然后将后一份接到前一份前面。如上可以分成两个链表1->2->3和4->5。
public ListNode rotateRight(ListNode head, int k) {
ListNode fast=head;
ListNode slow=head;
ListNode temp=head;
int count=0;
if (head==null||k==0){
return head;
}
//注意k可能大于链表的长度,此时要取余
while (temp!=null){
temp=temp.next;
count++;
}
if (k>=count){
k=k%count;
if (k==0){//k与链表长度相等
return head;
}
}
while (k>0){
fast=fast.next;
k--;
}
while (fast.next!=null){//此时fast在最后一个节点,slow.next指向倒数第k个节点
fast=fast.next;
slow=slow.next;
}
temp=slow.next;
fast.next=head;
slow.next=null;
return temp;
}
4.3.3.解法三:找到倒数第k个,也就是正数第lenth-k+1个
要注意取模防止越界。
public ListNode rotateRight(ListNode head, int k) {
if (head==null||k==0){
return head;
}
ListNode fast=head;
ListNode slow=head;
ListNode newHead=head;
int count=1;
while(fast.next!=null){//此时的fast为链表的最后一个节点
fast=fast.next;
count++;
}
if (k>=count){
k=k%count;
if (k==0){
return head;
}
}
int len=count-k;
for (int i = 0; i < len-1; i++) {
slow=slow.next;
}
newHead=slow.next;
fast.next=head;
slow.next=null;
return newHead;
}
5.删除链表元素
5.1.删除特定元素
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
由于此处要求删除所有特定的元素的节点,所以头节点也需要处理,此处应定义一个虚拟节点指向头节点。
public ListNode removeElements(ListNode head, int val) {
ListNode node=null;
ListNode dummyNode=new ListNode(-1);
dummyNode.next=head;
node=dummyNode;
while (node.next!=null){
if (node.next.val==val){
node.next=node.next.next;//此处node和dummyNode的.next是一致的,所以不需要特别指定是不是头节点
}else {
node=node.next;
}
}
return dummyNode.next;
}
5.2.删除倒数第n个节点
双指针法
public static ListNode removeNthFromEnd(ListNode head, int n) {
if (head==null){
return head;
}
ListNode dummyNode=new ListNode(-1);//要处理头结点
dummyNode.next=head;
ListNode fast=dummyNode;
ListNode slow=dummyNode;
while (n>0){//此时fast和slow中间会隔n-1个结点
fast=fast.next;
n--;
}
while (fast.next!=null){//当fast指向最后一个结点时,fast和slow隔n-1个结点,即slow指向倒数第n个结点的前一结点
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
return dummyNode.next;
}
5.3.删除重复结点
5.3.1.保留一个重复的结点,使每个结点只出现一次
将此结点与下一结点比较,如果结点的val值相等,则删除下一结点,因为在删除下一结点的时候只是将当前结点的指向越过了下一结点,所以下一次比较就是当前结点和指向的下一结点。
public ListNode deleteDuplicates(ListNode head) {
if (head==null){
return head;
}
ListNode node=head;
while (node.next!=null){//保证有下一结点
if (node.val==node.next.val){
node.next=node.next.next;
}
else node=node.next;
}
return head;
}
5.3.2.删除所有的重复结点,只留下不同的结点
此时需要将重复的结点全部删除,可以参照上一的思路,但是要注意此时要从虚拟结点开始比较下一结点和下下结点。
//删除重复元素,只留下不同的结点
public ListNode deleteDuplicates(ListNode head) {
if (head==null){
return head;
}
ListNode dummyNode=new ListNode(-1);
dummyNode.next=head;
ListNode node=dummyNode;
while (node.next!=null&&node.next.next!=null){
if (node.next.val==node.next.next.val){
int x=node.next.val;
while (node.next!=null&&node.next.val==x){//因为可能重复结点有两个及以上,又因为是已排好序的链表
node.next=node.next.next;
}
}
else node=node.next;
}
return dummyNode.next;
}