链表:是一种动态的数据结构,它是由节点组成,每个节点包含一个数据部分和指向下一个节点的指针。可以动态的增加和删除节点,而不像数组那样预先分配固定大小的存储空间。链表的主要类型有单向链表、双向链表和循环链表。单向链表的每个节点只有一个指向后续节点的指针,双向链表的节点则有两个指针,一个指向前一个节点,一个指向后一个节点,循环链表的尾节点指向头节点或某个中间节点,形成一个闭环。下方鄙人写些力扣官网习题和做题总结,方便大家进行巩固知识~
遍历链表
通常用一个指针或者引用来实现,从链表的头节点开始,依次移动到下一个节点直到链表尾节点为空(null)。常用方法有循环和迭代和链表题型(java代码见下方)。
循环
public static void iterateTraversal(ListNode head) {
ListNode current = head; // 初始化当前节点为头节点
while (current != null) { // 循环直到当前节点不为 null
System.out.print(current.val + " "); // 访问当前节点的值
current = current.next; // 移动到下一个节点
}
System.out.println(); // 打印换行
}
迭代
public static void recursiveTraversal(ListNode head) {
if (head == null) {
return; // 基本情况:如果头节点为 null,返回
}
System.out.print(head.val + " "); // 访问当前节点的值
recursiveTraversal(head.next); // 递归调用遍历下一个节点
}
class Solution {
public ListNode mergeNodes(ListNode head) {
ListNode node = head.next;
while(node != null){
if(node.next.val!=0){
node.val+=node.next.val;
node.next=node.next.next;
}else{
node.next=node.next.next;
node = node.next;
}
}
return head.next;
}
}
总结:题目含义:移除链表节点0并将0节点之间非0的节点合并成一个节点(不包含两个连续零的节点)。首先用个指针排除第一个节点从第二个节点开始访问,若(第二个)的下一个节点为0,改变指针到下下个节点;为什么不能合并成node=node.next.next?因为会丢失第一种情况中node.next.val 的值。
class Solution {
public ListNode[] splitListToParts(ListNode head, int k) {
ListNode p1=head;
ListNode[] parts=new ListNode[k];
if(head==null)return parts;
int n=1;
while(p1.next!=null){
n++;p1=p1.next;
}
int quotinent=n/k,remain=n%k;
ListNode curr=head;
for(int i=0;i<k&&curr!=null;i++){
parts[i]=curr;
int partSize=quotinent+(i<remain?1:0);
for(int j=1;j<partSize;j++){
curr=curr.next;
}
ListNode next=curr.next;
curr.next=null;
curr=next;
}
return parts;
}
}
总结:题目含义就是将一个链表拆分成一个大小为k的链表数组。外层数组肯定是k啊,内层的话需要考虑一下,难点就是当前数组元素需要多少个原链表元素(计算公式partSize=quotinent+(i<remain?1:0);)用到取余操作。
817. 链表组件
class Solution {
public int numComponents(ListNode head, int[] nums) {
int num = 0;
Set<Integer> set = new HashSet<>();
for(int x : nums) set.add(x);
while(head!=null){
if(set.contains(head.val)){
while(head!=null&&set.contains(head.val))head = head.next;
num++;
}else{
head = head.next;
}
}
return num;
}
}
总结:链表中一段最长连续结点的值(该值必须在列表 nums 中)构成的集合个数。用一个变量num来统计组件个数和set集合统计nums组件的个数,然后在依次遍历链表进行统计。
删除节点
遍历链表,找到满足题意得节点,进行相关操作
class Solution {
public ListNode removeElements(ListNode head, int val) {
while(head!=null&& head.val==val){
head=head.next;
}
if(head==null)return head;
ListNode present=head;
while(head.next!=null){
if(head.next.val==val){
head.next=head.next.next;
}else{
head=head.next;
}
}
return present;
}
}
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null)return null;
ListNode pre=head;
while(pre.next!=null){
if(pre.val==pre.next.val){
pre.next=pre.next.next;
}else{
pre=pre.next;
}
}
return head;
}
}
总结:遍历链表找到满足题目值的节点。
3217. 从链表中移除在数组中存在的节点
class Solution {
public ListNode modifiedList(int[] nums, ListNode head) {
Set<Integer> set = new HashSet<>(nums.length); // 预分配空间
for (int x : nums) {
set.add(x);
}
ListNode dummy = new ListNode(0, head);
ListNode cur = dummy;
while(cur.next!=null){
if (set.contains(cur.next.val)) {
cur.next = cur.next.next; // 删除
} else {
cur = cur.next; // 向后移动
}
}
return dummy.next;
}
}
总结:利用了set中contains方法,提高代码效率。
1669.合并两个链表
class Solution {
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode p1=list1;
for(int i=0;i<a-1;i++)p1=p1.next;
ListNode p2=p1;
for(int i=0;i<b+1-(a-1);i++)p2=p2.next;
p1.next=list2;
while(list2.next!=null)list2=list2.next;
list2.next=p2;
return list1;
}
}
总结:找到删除节点得前一个节点,怎么删除[a,b]的节点,找到a节点的下一个节点很easy,找到b+1的节点使用(b+1-(a-1)其实理解的话也很简单,删除需要走的步数([a,b]),b+1以后的和a-1以前的不需要删除)。
插入链表
和数组的插入排序差不多
class Solution {
public ListNode insertionSortList(ListNode head) {
if(head==null)return head;
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode lastSorted = head, curr = head.next;
while(curr!=null){
if (lastSorted.val <= curr.val) {
lastSorted = lastSorted.next;
} else {
ListNode prev = dummyHead;
while (prev.next.val <= curr.val) {
prev = prev.next;
}
lastSorted.next = curr.next;
curr.next = prev.next;
prev.next = curr;
}
curr = lastSorted.next;
}
return dummyHead.next;
}
}
反转链表
通常使用三个指针,用两个指针保存反转的两个节点再用一个保存下一个需要反转的节点位置。
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null) return null;
ListNode p1=null;
ListNode p2=head;
if(p2==null)return p1;
while(p2!=null){
ListNode p3=p2.next;
p2.next=p1;
p1=p2;
p2=p3;
}
return p1;
// if(head==null) return null;
// ListNode p1=null;
// ListNode p2=head;
// if(p2==null)return p1;
// while(p2!=null){
// ListNode p3=p2.next;
// p2.next=p1;
// p1=p2;
// p2=p3;
// }
// return p1;
}
}
写了两个代码注释打开都能跑哈~
24. 两两交换链表中的节点
class Solution {
public ListNode swapPairs(ListNode head) {
if(head==null||head.next==null)return head;
ListNode dummy = new ListNode(0, head);
ListNode node0 = dummy;
ListNode node1 = head;
while(node1!=null&&node1.next!=null){
ListNode node2=node1.next;
ListNode node3=node2.next;
node0.next=node2;
node2.next=node1;
node1.next=node3;
node0=node1;
node1=node3;
}
return dummy.next;
}
}
这是个经典例子需要掌握!!!
前后指针
前面一个指针先走N步,后一个指针再走,当前一个指针走到链表尾部时候,后一个指针离链表尾部N步。以下几个题目需掌握~~
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode p1=head,p2=dummy;
while(n!=0){p1=p1.next;n--;}
while(p1!=null){
p1=p1.next;
p2=p2.next;
}
p2.next=p2.next.next;
return dummy.next;
}
}
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(k==0||head==null||head.next==null)return head;
int n=1;
ListNode iter=head;
while(iter.next!=null){
iter=iter.next;
n++;
}
int add=n-k%n;
iter.next=head;
while(add-->0){
iter=iter.next;
}
ListNode ret=iter.next;
iter.next=null;
return ret;
}
}
总结:旋转链表的话和表长有些数学关系add=n-k%n;这个公式推导出来的话不难。
快慢指针
通常快指针走两步,慢指针走一步。最具有代表性的应该是环形链表的题目了
class Solution {
public ListNode middleNode(ListNode head) {
// int n = 0,k=0;
// ListNode p1 = head;
// while (p1 != null) {
// n++;
// p1 = p1.next;
// }
// p1 = head;
// while (k < n / 2) {
// ++k;
// p1 = p1.next;
// }
// return p1;
if(head==null)return null;
ListNode p1=head,p2=head;
while(p2!=null && p2.next!=null){
p2=p2.next.next;
p1=p1.next;
}
return p1;
}
}
简单题两种解法,先练练手。下面开始上正片:
141. 环形链表
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
快慢指针遍历到最后相遇了说明有指针返回true,否则为false。
142. 环形链表 II
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head==null) return null;
ListNode slow=head,fast=head;
while(fast!=null){
slow=slow.next;
if(fast.next==null){
return null;
}else{
fast=fast.next.next;
}
if(fast==slow){
ListNode p=head;
while(p!=slow){
p=p.next;
slow=slow.next;
}
return p;
}
}
return null;
}
}
题目是找到环的入口点,那肯定到快慢指针相遇的地方去找啊。首先肯定一点快慢指针相遇的点(再环中相遇)不一定不是入口点(你想想快指针跑的多快啊嘿嘿)。那么问题来了怎么找入口点呢?假设表头距离环的长度为a,环的长度为b ;慢指针走了s步数,快指针走了s+nb步数 ;快指针数独是慢指针的2倍数;得出s=nb。链表到环入口的节点是a+nb,目前s走了nb步数只要在声明一个指针走a步数就能到环入口点。
双指针
下面几道题都是用两个指针进行操作
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1=headA;
ListNode p2=headB;
while (p1 != p2) {
p1 = (p1 == null) ? headB : p1.next;
p2 = (p2 == null) ? headA : p2.next;
}
return p1;
// boolean found = false;
// if(p1==null || p2==null)return null;
// while(p1!=null){
// while(p2!=null){
// if(p1==p2){
// found = true;
// break;
// } p2=p2.next;
// }
// if (found) {return p1;}
// p1=p1.next;
// p2=headB;
// }
// return null;
}
}
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head==null)return head;
ListNode evenHead = head.next;
ListNode odd = head, even = evenHead;
while(even!=null&&even.next!=null){
odd.next=even.next;
odd=odd.next;
even.next=odd.next;
even=even.next;
}
odd.next=evenHead;
return head;
}
}
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode p1=new ListNode(0),p2=new ListNode(0);
ListNode q1=p1,q2=p2;
while(head!=null){
if(head.val<x){
q1.next=head;
q1=q1.next;
}else{
q2.next=head;
q2=q2.next;
}
head=head.next;
}
q1.next=p2.next;
q2.next=null;
return p1.next;
}
}
合并链表
最后了,写的累死俺了哈哈哈
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// if(list1!=null && list2==null) return list1;
// if(list1==null && list2!=null) return list2;
// if(list1==null &&list2==null) return null;
// ListNode prehead = new ListNode(-1);
// ListNode pre = prehead;
// ListNode p1=list1;ListNode p2=list2;
// while(p1!=null && p2!=null){
// if(p1.val<=p2.val){
// pre.next=p1;
// p1=p1.next;
// }else{
// pre.next=p2;
// p2=p2.next;
// }
// pre=pre.next;
// }
// pre.next=p1==null?p2:p1;
// return prehead.next;
ListNode l1=list1;ListNode l2=list2;
if (l1 == null) {
return l2;
} else if (l2 == null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
声明一个null链表,遍历已知的链表哪个链表元素小加入声明链表中!!!
2816. 翻倍以链表形式表示的数字
class Solution {
public ListNode doubleIt(ListNode head) {
if(head.val>4)head=new ListNode(0,head);
for(ListNode p=head;p!=null;p=p.next){
p.val=p.val*2%10;
if(p.next!=null&&p.next.val>4){
p.val++;
}
}
return head;
}
}
链表元素翻倍时候要进行取余,和判断下一个是否产生进位p.next!=null&&p.next.val>4若下一位有进位则当前元素要+1。
2. 两数相加
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode root=new ListNode(0);
ListNode p=root;
int count=0,sum=0;
while(l1!=null||l2!=null||count!=0){
int count1=l1!=null?l1.val:0;
int count2=l2!=null?l2.val:0;
sum=count1+count2+count;
count=sum/10;
root.next= new ListNode(sum%10);
root=root.next;
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
}
return p.next;
}
}
主要考虑两个数相加是否产生进位,若产生下一次相加记得加上。
445. 两数相加 II
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
Deque<Integer> stack1 = new ArrayDeque<Integer>();
Deque<Integer> stack2 = new ArrayDeque<Integer>();
while (l1 != null) {
stack1.push(l1.val);
l1 = l1.next;
}
while (l2 != null) {
stack2.push(l2.val);
l2 = l2.next;
}
int carry = 0;
ListNode ans = null;
while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
int a = stack1.isEmpty() ? 0 : stack1.pop();
int b = stack2.isEmpty() ? 0 : stack2.pop();
int cur = a + b + carry;
carry = cur / 10;
cur %= 10;
ListNode curnode = new ListNode(cur);
curnode.next = ans;
ans = curnode;
}
return ans;
}
}
和上一题想法一样,这题是从表尾部开始相加的用一个栈保存就和上题解法一样啦~
大家记得点赞和收藏吖~
终于写完啦浪费我三个小时啊啊啊啊啊
如有不足之处,请您在评论区中指出