文章目录
Leetcode 206. 反转链表
思路:从链表中依次取节点—>>头插入新链表,为了避免元素丢失,需要记录原链表没插入节点
优点:重用节点,不需要创建另外节点对象,节省空间
public ListNode reverseList(ListNode o1) {
if (o1 == null || o1.next == null) {
return o1;
}
ListNode n1 = null;
while (o1 != null) {
ListNode o2 = o1.next;
o1.next = n1;
n1 = o1;
o1 = o2;
}
return n1;
}
根据值删除节点-力扣 203 题
思路:加哨兵,要删除得记录前一个节点,用其next指向跳过当前要删除得节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode s = new ListNode(-1, head);
ListNode pre = s;
ListNode now = s.next;
while(now != null){
if(now.val == val){
//删除,now后移
pre.next = now.next;
now = now.next;
}else{
//pre、now都后移
pre = pre.next;
now = now.next;
}
}
return s.next;
}
}
删除倒数节点-力扣 19 题
思路:要删除倒数第n个节点,就要知道倒数第n个节点的前一个节点,再将其next跳过删除节点
定义p1指向待删除节点前一个节点,p2为相对p1的后n+1个节点,p2为null时表示此时遍历链表结束,此时p1为待删除节点的前一个节点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//快慢指针法,p1指向待删除前一个节点,p2相对p1多走n+1步
ListNode s = new ListNode(-1, head);
ListNode p1 = s;
ListNode p2 = s;
for(int i=0; i<n+1; i++){
p2 = p2.next;
}
while(p2 != null){
p1 = p1.next;
p2 = p2.next;
}
p1.next = p1.next.next;
return s.next;
}
}
有序链表去重-力扣 83 题
题目数据保证链表已经按升序 排列
思路:链表升序有序,定义p1指向head,p2指向head后一个,比较p1与p2,一样则p1的next跳过p2,不一样则都向后平移
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
// ListNode s = new ListNode(-101, head);
// ListNode p1 = s;
// ListNode p2 = p1.next;
// while(p2 != null){
// if(p1.val == p2.val){
// p1.next = p2.next;
// p2 = p2.next;
// }else{
// p1 = p1.next;
// p2 = p2.next;
// }
// }
// return s.next;
// 链表节点 < 2
if (head == null || head.next == null) {
return head;
}
// 链表节点 >= 2
ListNode p1 = head;
ListNode p2;
while ((p2 = p1.next) != null) {
if (p1.val == p2.val) {
p1.next = p2.next;
} else {
p1 = p1.next;
}
}
return head;
}
}
删除链表重复元素-力扣 82 题
思路:定义三节点p1、p2、p3,p1指向待删除节点的前一个节点,p2和p3用于寻找重复元素边界,p2指向重复元素开始,p3指向最后一个重复元素的下一个位置,则用p1.next指向p3即可删除全部重复元素
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null || head.next==null){
return head;
}
ListNode s = new ListNode(-1,head);
ListNode p1 = s;
ListNode p2, p3;
while((p2 = p1.next) != null && (p3 = p2.next) != null){
if(p2.val == p3.val){
while((p3 = p3.next) !=null && p3.val == p2.val){}
//找到了不重复的值
p1.next = p3;
}else{
p1 = p1.next;
// p2 = p2.next;
// p3 = p3.next;
}
}
return s.next;
}
}
合并有序链表-力扣 21 题
思路:依次比较两个链表的值,谁小则吧谁加入新链表,当两个链表有一个为空时,因为链表有序,直接将不为空的加入新链表即可
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode s = new ListNode(-1,null);
ListNode p = s;
while(list1 != null && list2 != null){
if(list1.val <= list2.val){
p.next = list1;
p = p.next;
list1 = list1.next;
}else{
p.next = list2;
p = p.next;
list2 = list2.next;
}
}
if(list1 != null){
p.next = list1;
}
if(list2 != null){
p.next = list2;
}
return s.next;
}
}
合并多个有序链表-力扣 23 题
思路:递归方式:想二叉树,多个链表,在最底层,我们将其变为两个链表,再合并两个链表,递归解决多个链表的合并(不太准确:类比左右中的遍历,左右都为单个链表,中操作为对这两个链表合并,)。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
return split(lists, 0, lists.length - 1);
}
//返回合并后的链表,i,j代表左右边界
public ListNode split(ListNode[] lists, int i, int j) {
if (j == i) {
return lists[i];
}
int m = (i + j) >>> 1;
ListNode left = split(lists, i, m);
ListNode right= split(lists, m+1, j);
return mergeTwoLists(left, right);
}
public ListNode mergeTwoLists(ListNode p1, ListNode p2) {
ListNode s = new ListNode(-1, null);
ListNode p = s;
while (p1 != null && p2 != null) {
if (p1.val < p2.val) {
p.next = p1;
p1 = p1.next;
} else {
p.next = p2;
p2 = p2.next;
}
p = p.next;
}
if (p1 != null) {
p.next = p1;
}
if (p2 != null) {
p.next = p2;
}
return s.next;
}
}
查找链表中间节点-力扣 876 题
思路:使用快慢指针法,考虑链表个数是单数还是偶数的情况,如下图:
class Solution {
public ListNode middleNode(ListNode head) {
ListNode p1 = head;
ListNode p2 = head;
while(p2 != null && p2.next != null){
p1 = p1.next;
p2 = p2.next.next;
}
return p1;
}
}
判断回文链表-力扣 234 题
用 O(n) 时间复杂度和 O(1) 空间复杂度解决
思路:找到原链表的中间位置,将此位置以后的链表(即原链表的后半段)反转,再将反转后的链表和原链表元素逐一比较,单比较到有不相等元素时表示不是回文链表,比较完都一样表示是回文链表。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
//思路:找到原链表的中间位置,将此位置以后的链表(即原链表的后半段)反转,
// 再将反转后的链表和原链表元素逐一比较,单比较到有不相等元素时表示不是回文链表,
// 比较完都一样表示是回文链表。
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode middle = middle(head);
ListNode reverse = reverse(middle);
while(reverse != null){
//只要有一个不等,则返回假
if(reverse.val != head.val){
return false;
}
reverse = reverse.next;
head = head.next;
}
return true;
}
//找中间节点并返回中间节点,快慢指针法
private ListNode middle(ListNode head){
ListNode p1 =head;//慢指针,一次一步
ListNode p2 = head;//快指针,一次两步
while(p2 != null && p2.next != null){
p1 = p1.next;
p2 = p2.next.next;
}
return p1;
}
//反转链表 本题不用考虑链表为空的情况,题目中链表节点做了规定
private ListNode reverse(ListNode o1){
ListNode n1 = null;
while(o1 != null){
ListNode o2 = o1.next;
o1.next = n1;
n1 = o1;
o1 = o2;
}
return n1;
}
}
上面算法运行时间大leetcode上拿不上高分,对其改进:将反转操作与找中间位置操作同步进行,反转的是前半部分,当找到中间位置时也反转了前半部分,用反转的前半部分与后半部分比较(后半部分的开始就是此时的中间位置即慢指针此时所指的位置。但注意原链表基数和偶数对结果的影响:
当原链表为奇数时,让p1(中间节点的后一个位置)与n1节点中的值逐一比较。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
//思路:找到原链表的中间位置,将此位置以后的链表(即原链表的后半段)反转,
// 再将反转后的链表和原链表元素逐一比较,当比较到有不相等元素时表示不是回文链表,
// 比较完都一样表示是回文链表。但要注意基数偶数情况
//1->2->3->4->5 基数 此时p2不为null
//12 345
//1->2->3->4->5->6 偶数
//123 456
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode p1 = head;//慢指针
ListNode p2 = head;//快指针
ListNode n1 = null;//新头
ListNode o1 = head;//旧头
//实现反转前半部分链表与找中间位置
while(p2 != null && p2.next != null){
p1 = p1.next;
p2 = p2.next.next;
ListNode o2 = o1.next;
o1.next = n1;
n1 = o1;
o1 = o2;
}
//判断原链表是基数还是偶数
if(p2 != null){//奇数节点
p1 = p1.next;
}
while(n1 != null){
if(n1.val != p1.val){
return false;
}
n1 =n1.next;
p1 = p1.next;
}
return true;
}
}
龟兔赛跑算法
如果链表上存在环,那么在环上以不同速度前进的两个指针必定会在某个时刻相遇。算法分为两个阶段:
阶段1
- 龟一次走一步,兔子一次走两步
- 当兔子能走到终点时,不存在环
- 当兔子能追上龟时,可以判断存在环
阶段2
- 从它们第一次相遇开始,龟回到起点,兔子保持原位不变
- 龟和兔子一次都走一步
- 当再次相遇时,地点就是环的入口
为什么呢?
当它们第一次相遇时,直线上兔子都比龟快,所以相遇只能在环中,它们都走了直线距离,而兔子走的是龟的两倍,兔=2龟---->兔-龟=龟---->直线+n圈+k(相遇点距离入口距离)-(直线+m圈+k)=(n-m)圈,也是绕环的倍数,
入口:对于龟来说=直线+绕环倍数圈
此时相遇的位置就是绕环(n-m)圈,所以在此位置再走一个直线距离就是入口。
环形链表-力扣 141 题
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next !=null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){
return true;
}
}
return false;
}
}
环形链表检测入口-力扣 142 题
易错点:当我们在第二个阶段找入口时,将龟放入起点,此时若兔子也在起点时表示他们的第二此相遇,此时就是环的入口。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
// public ListNode detectCycle(ListNode head) {
// ListNode fast = head;
// ListNode slow = head;
// while(fast != null && fast.next !=null){
// slow = slow.next;
// fast = fast.next.next;
// if(slow == fast){//进入第二阶段
// slow = head;
// while(true){
// fast = fast.next;
// slow = slow.next;
// if(fast == slow){
// return slow;
// }
// }
// }
// }
// return null;
// }
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next !=null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){//进入第二阶段
slow = head;
while(true){
if(fast == slow){//当此时龟放到起点,而恰好兔子也在起点是说明
//这是第二此相遇,直接返回入口即可(这种情况是没有直线只有环的
//情况)
return slow;
}
fast = fast.next;
slow = slow.next;
}
}
}
return null;
}
}
return 语句的作用域是整个函数,而不仅仅是内层循环。当执行 return 语句时,函数会立即结束,并返回指定的值。