首先明确两个概念:头结点和头指针
头结点:
头结点是为了操作的统一与方便而设立的,放在第一个元素结点之前,其数据域一般无意义(当然有些情况下也可存放链表的长度);头结点不是必须的!;头结点后面的第一个节点称为首元结点。
头指针:
头指针是指链表指向第一个结点的指针,若链表有头结点,则头指针就是指向链表头结点的指针。没有头结点,头指针就指向首元节点,如图所示。
LeetCode中的 head,一般是
头指针
,指向首元节点!解题时可将head视作第一个节点
一、删除节点
1.1 删除链表指定值的结点
注:输入为头指针。头指针是指链表指向第一个结点的指针,若链表有头结点,则头指针就是指向链表头结点的指针。此处没有头结点,所以默认是从第一个节点开始的!
【法一】
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode deleteNode(ListNode head, int val) {
if(head == null) return head;
//head=[4,5,1,9],head为头指针,此处没有头结点,所以默认是从第一个节点开始的!
if(head.val==val) return head.next;//如果要删除的是第一个节点,要从head.next开始返回
ListNode cur = head;
while(cur.next != null && cur.next.val != val){
cur = cur.next;
}
if(cur.next != null){
cur.next = cur.next.next;
}
return head;
}
}
【法二】伪头结点法
也可以设计一个伪头结点,ListNode header = new ListNode<-1>; header.next = head;
class Solution {
public ListNode removeElements(ListNode head, int val){ //head
if(head == null) return head;
ListNode header = new ListNode(-1);
header.next = head;
ListNode cur = header;
while(cur.next != null){
if(cur.next.val == val){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
return header.next;
}
}
——前面的删除单链表的一个值,亦可用该方法!
1.2 删除指定位置上的结点
public static void delectIndexNode(ListNode head,int index)
{
ListNode cur = head;
int count = 1;
while(cur.next!=null)
{
if(count==index)
{
cur.next=cur.next.next;//执行删除操作
}
else
{
count++;
cur=cur.next;
}
}
}
也可以考虑用双指针法来做:
//删除指定位置上的结点,从0开始
public void delectIndexNode(ListNode head.int index){
ListNode cur = head;
ListNode pre = new ListNode(-1);
pre.next = head;
int count = 0;
while(cur !=null){
if(count == index){
pre.next = cur.next;
}else{
cur = cur.next;
pre = pre.next;
count++;
}
}
}
1.3 删除链表中的重复结点
(1) 未排序链表
class Solution {
public ListNode removeDuplicateNodes(ListNode head) {
if(head==null) return null;
Set<Integer> set = new HashSet<Integer>();
set.add(head.val);
ListNode dummyhead = head;
while(dummyhead.next!=null){
ListNode cur = dummyhead.next;//待删除节点
if(set.add(cur.val)){
dummyhead = dummyhead.next;
}else{
dummyhead.next = dummyhead.next.next;
}
}
return head;
}
}
(2) 排序链表
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode cur = head;
if(head == null) return head;
while(cur.next != null){
if(cur.val == cur.next.val){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
return head;
}
}
1.4 删除倒数第N个节点——双指针
//双指针p,q,当q先行n步,p后行,当q到达null时,p就是应该删除的点的前一个结点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode p = dummyHead;//p为head的前一个
ListNode q = head;
for(int i=1;i<=n;i++){//使p,q之间间隔n+1个节点
q = q.next;
}
while(q!=null){//当q到达了null时,p在倒数第n个节点的前一个结点
p = p.next;
q = q.next;
}
p.next = p.next.next;
//此处不要直接返回head,因为有可能删除掉的结点就是head
return dummyHead.next;
}
}
1.5 输出倒数第K个节点——双指针
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
if(head==null) return null;
ListNode p = head;
ListNode q = head;
for(int i=0;i<k;i++){
q = q.next;
}
while(q!=null){
p = p.next;
q = q.next;
}
return p;
}
}
1.6 删除排序链表所有重复结点
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null) return null;
ListNode dummyhead = new ListNode(-1);
dummyhead.next = head;
ListNode fast = head;
ListNode slow = dummyhead;
while(fast != null){
if(fast.next != null && fast.val == fast.next.val){ // 3 1 1 1 1 2 2
while(fast.next != null && fast.next.val == fast.val){
fast = fast.next; //此时fast在最后一个重复的位置
}
slow.next = fast.next;//删除了所有重复结点 3 2 2
fast = fast.next;//此时快指针在2,慢指针在3
}else{
fast = fast.next;
slow = slow.next;
}
}
return dummyhead.next;
}
}
二、单链表反转
(1)全表反转
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while(cur != null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
(2)部分反转
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
for(int i=1;i<m;i++){
pre = pre.next;//将pre移动到m的前一个
}
head = pre.next;
for(int i=m;i<n;i++){
ListNode tmp = head.next;
head.next = tmp.next;
tmp.next = pre.next;
pre.next = tmp;
}
return dummy.next;
}
}
(3)旋转链表:将每个节点右移k个位置
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
示例 2:
输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(head == null || k==0) return head;
ListNode cur = head;
ListNode tail = null;
int len=1;
while(cur.next != null){//获取总长度
cur = cur.next;
len++;
}
tail = cur;//尾指针指向尾结点
cur.next = head;//此时cur指向最后一个结点,现将其改为循环链表
cur = head;
int loop = len - (k%len);//难理解:循环链表移动次数,即将cur指针移动到末尾的k%len处
for(int i=0;i<loop;i++){
cur = cur.next;
tail = tail.next;
}
tail.next = null;
return cur;
}
}
三、判断单链表是否有环 + 找到环的入口
【判断是否有环】
【方法一】用哈希表存储访问过的链表结点
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode cur = head;
Set<ListNode> nodeSet = new HashSet<>();
while(cur != null){
if(nodeSet.contains(cur)){ //或者: if(!nodeSet.add(cur))
return true;
}else{
nodeSet.add(cur);
}
cur = cur.next;
}
return false;
}
}
【方法二】快慢指针法
//判断是否有环
public class Solution {
public boolean loopList(ListNode head){
if(head == null) return false;
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
}
【找到环的入口】
数学求证:假设一个简单的带环单链表,快慢指针相遇时,快指针走了x+y+z+y,慢指针走了x+y,x+y+z+y = 2(x+y),得:x = z,因而,从相遇开始,以相同速度从head开始走的距离等于从相遇点开始走的距离,即再次相遇时,相遇点为环的入口!
public ListNode loopPoint(ListNode head){
if(head==null){
return null;
}
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){//此时快指针比慢指针多走了一圈环
break;
}
}
slow = head;//慢指针回到头节点
//开始相同速度行进,当快慢指针再次相遇,快慢指针刚好在环的入口
while(fast != slow){
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
四、在链表指定位置插入
public void insertNode(int index,ListNode head,ListNode node)
{
ListNode cur = head;
int count=1;
while(cur.next!=null)
{
if(count==index)
{
node.next=cur.next;
cur.next=node;
}
else
{
count++;
cur=cur.next;
}
}
}
五、合并排序链表
5.1 合并两个排序链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
【递归法】
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null) return l2;
if(l2 == null) return l1;
if(l1.val <= l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
}
【非递归法】
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dum = new ListNode(0), cur = dum;
while(l1 != null && l2 != null) {
if(l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
}else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 != null ? l1 : l2;
return dum.next;
}
}
5.2 合并k个排序链表
import java.util.*;
//合并 k 个已排序的链表并将其作为一个已排序的链表返回。分析并描述其复杂度。
public class Solution {
public ListNode mergeKLists(List<ListNode> lists){
if(lists==null || lists.size() == 0) return null;
if(lists.size()==1) return lists.get(0);
if(lists.size()%2 != 0) lists.add(null);//偶数才行
List<ListNode> sum = new ArrayList<>();
//两两合并的结果放进sum中
for(int i=0;i<lists.size();i+=2){
sum.add(mergeTwoLists(lists.get(i),lists.get(i+1)));
}
//递归,sum中的结果再两两合并
return mergeKLists(sum);
}
//合并两个
public ListNode mergeTwoLists(ListNode l1,ListNode l2){
if(l1 == null) return l2;
if(l2 == null) return l1;
if(l1.val <= l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
}
六、两个链表求和(反向+正向)
LeetCode 面试题 02.05:链表求和 给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = null,rear = null;
boolean upFlag = false;
while (l1 != null || l2 != null) {
int sum = 0;
if(l1 != null) {
sum += l1.val;
l1 = l1.next;
}
if(l2 != null) {
sum += l2.val;
l2 = l2.next;
}
if(upFlag) {
sum += 1;
upFlag = false;
}
int nodeNum = sum % 10;
if(sum > 9) {
upFlag = true;
}
//挂节点
if(head == null) {
head = rear = new ListNode(nodeNum);
}else {
ListNode rearNode = new ListNode(nodeNum);
rear = rear.next = rearNode;
}
}
if(upFlag) {
rear.next = new ListNode(1);
}
return head;
}
}
进阶:
进阶:假设这些数位是正向存放的,请再做一遍。
示例:
输入:(6 -> 1 -> 7) + (2 -> 9 -> 5),即617 + 295 输出:9 -> 1 -> 2,即912
public ListNode reverseList(ListNode l1) {
ListNode pre = null;
ListNode next = null;
while(l1 != null) {
next = l1.next;
l1.next = pre;
pre = l1;
l1 = next;
}
return pre;
}
//后面再加上上面的即可。
七、链表的相交节点 / 公共节点
【思路】:
A和B同时走,走到头后再走对方的路,这样:
A链表走的路径是:A——D——C——B——D,长度L1 = AD+DC+BD
B链表走的路径是:B——D——C——A——D,长度L2 = BD+DC+AD
所以:L1 = L2,最终必然会在D点相遇!
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA==null || headB==null) return null;
ListNode curA = headA;
ListNode curB = headB;
while(curA!=curB){ //直到找到相交点才结束循环
if(curA == null){
curA = headB;
}else{
curA = curA.next;
}
if(curB == null){
curB = headA;
}else{
curB = curB.next;
}
}
return curA;
}
}