1.160. 相交链表
理解:
代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA =headA; ListNode pB =headB;
while (pA!=pB) {
pA=pA==null?headB:pA.next;
pB=pB==null?headA:pB.next;
}
return pA;// 返回交点,如果没有交点,这里将返回null
//同时到达尾端都等于null
}
}
2.206. 反转链表
方法1:双指针
循环停止:
cur=null,说明所有元素已经遍历完成
代码:
/**
* 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 reverseList(ListNode head) {
ListNode cur=head; ListNode pre=null;
while (cur!=null) {
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
}
方法2:递归
反向连接(4连5,变成5连4,(head为4))
断开之前4连接5(先反连接了,5连4)
cur一直是5
代码:
class Solution {
public ListNode reverseList(ListNode head) {
if (head==null||head.next==null) {
return head;
}
ListNode NewHead=reverseList(head.next);
head.next.next=head;//反向连接
head.next=null;//断开正向的连接
return NewHead; //返回的是最后一次递归的值,最后一个节点
}
}
3.回文链表
错误方法:直接翻转链表
翻转链表,回文链表翻转前后一致
将翻转前后的链表逐一比较,不一致则返回false
(迭代双指针的翻转(o(n),o(1)))
class Solution { public boolean isPalindrome(ListNode head) { if (head==null||head.next==null) { return true; } ListNode ReverLink=reverse(head); //翻转链表,回文链表翻转前后一致 ListNode p1=head;ListNode p2=ReverLink; while (p1!=null) { if (p1.val!=p2.val) { return false; } p1=p1.next; p2=p2.next; } return true; } public ListNode reverse(ListNode head){ ListNode cur=head;ListNode pre=null; while (cur!=null) { ListNode temp=cur.next; cur.next=pre; pre=cur; cur=temp; } return pre; } }
正确方法:只翻转后半部分(快慢指针+翻转)
class Solution {
public boolean isPalindrome(ListNode head) {
if (head==null||head.next==null) {
return true;
}
//快慢指针找中点,翻转后半部分
ListNode slow=head;ListNode fast=head;
while (fast != null && fast.next != null) {
slow=slow.next;
fast=fast.next.next;
}
ListNode ReverLink=reverse(slow);
//翻转后半部分链表,回文链表翻转前后一致
ListNode p1=head;ListNode p2=ReverLink;
while (p1!=null&p2!=null) {
if (p1.val!=p2.val) {
return false;
}
p1=p1.next;
p2=p2.next;
}
return true;
}
public ListNode reverse(ListNode head){
ListNode cur=head;ListNode pre=null;
while (cur!=null) {
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
}
4.环形链表
快慢指针检测环--不存在环,快指针一定到null;存在环,快慢指针一定能相遇
代码:
正确的:
public class Solution { public boolean hasCycle(ListNode head) { if (head == null || head.next == null) { return false; } ListNode fast=head.next; ListNode slow=head; while (fast!=slow) { if (fast == null || fast.next == null) { return false; } fast=fast.next.next; slow=slow.next; } return true; } }
5.环形链表 II
这几个环形链表都是尾部连接环,题目已经说明
索引从0开始
思想:两次相遇
第一次相遇,慢指针刚好走了n圈
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head==null||head.next==null) return null;
ListNode fast=head;
ListNode slow=head;
while (true) {
if (fast==null ||fast.next==null) return null;
slow=slow.next;
fast=fast.next.next;
if (fast==slow) break;
}
//构建第二次相遇
fast=head;
while (fast!=slow) {
slow=slow.next;
fast=fast.next;
}
return fast;
}
}
6.合并两个排序列表
方法1:迭代
将链表1的1->4的连接改为到第二条链表的连接
class Solution {
public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
//创建哨兵节点
ListNode prehead=new ListNode(-1);
ListNode prev = prehead;//记录当前加到哪里了
while (head1!=null & head2!=null) {
if (head1.val<head2.val) {
prev.next=head1;
head1=head1.next;
}else{
prev.next=head2;
head2=head2.next;
}
prev=prev.next;
}
//如果某条链已经全部安排完
//将另外那条链的剩余部分全加进去
prev.next=head1==null?head2:head1;
return prehead.next;
}
}
方法2:递归
递归头,结束条件:某条链为空,都比较完了
递归体,循环:从后往前,从一条链仅剩最后一个元素开始,另外那条链都是好的
class Solution {
public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
if (head1 == null)
return head2;
if (head2 == null)
return head1;
if (head1.val < head2.val) {
head1.next = mergeTwoLists(head1.next, head2);
return head1;
} else {
head2.next = mergeTwoLists(head2.next, head1);
return head2;
}
}
}
7.两数相加
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pre = new ListNode(-1);
ListNode cur = pre;
int carry = 0;
while (l1 != null || l2 != null) {
// 用0补齐链
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
int sum = x + y + carry;
carry = sum / 10;
sum = sum % 10;
cur.next = new ListNode(sum);
cur = cur.next;
if (l1 != null)
l1 = l1.next;
if (l2 != null)
l2 = l2.next;
}
if (carry == 1)
cur.next = new ListNode(carry);
return pre.next;
}
}
8.删除链表的倒数第 N 个结点
方法一:暴力解法
先查找到倒数第n个节点---两次遍历(一次找全长L,第二次找该节点)
再删掉
方法二:双指针
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode pre = new ListNode(0); pre.next = head; ListNode start = pre, end = pre; while(n != 0) { start = start.next; n--; } while(start.next != null) { start = start.next; end = end.next; } end.next = end.next.next; return pre.next; } }
9.两两交换链表中的节点
方法一:迭代
class Solution { public ListNode swapPairs(ListNode head) { // 边界判断 至少要有两个元素才可以 if(head== null) return head; if(head.next == null) return head; ListNode dummy = new ListNode(0); dummy.next = head; ListNode pre = dummy; while(pre.next != null && pre.next.next != null) { ListNode temp=pre.next.next; ListNode cur=pre.next; pre.next=temp; cur.next=temp.next; pre.next.next=cur; pre=cur;别忘了把 pre 的位置变一下 } return dummy.next; } }
方法二:递归
递归头:链表中没有节点,或者链表中只有一个节点,此时无法进行交换。
递归体:(1)两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。
(2)在对链表中的其余节点递归地两两交换之后,更新节点之间的指针关系
class Solution { public ListNode swapPairs(ListNode head) { // 递归头,终止条件 if (head==null || head.next==null) { return head ; } ListNode newHead=head.next; //head的第二个节点是newHead的第一个 head.next=swapPairs(newHead.next); //重新排列好 两个节点后面的节点对(两个两个的排) newHead.next=head; //前两个对换完成 return newHead; } }
10.K 个一组翻转链表
k个一组反转链表
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(0, head);
ListNode pre = dummy;
// ListNode cur = head;
while (head != null) {
ListNode tail = pre;
for (int i = 0; i < k; i++) {
tail = tail.next;
// 查看剩余部分长度是否大于等于 k
if (tail == null) {
return dummy.next;
}
}
ListNode tNext = tail.next;
// 反转链表
ListNode[] reverseNode = reverseListNode(head, tail);
head = reverseNode[0];
tail = reverseNode[1];
// 子链表重新接回原链表
pre.next = head;
tail.next = tNext;
pre = tail;
head = tNext;
}
return dummy.next;
}
private ListNode[] reverseListNode(ListNode head, ListNode tail) {
// 不直接操作head,tail用于返回,相当于head和tail的位置不变,值发生了变化
ListNode tNext = tail.next; // 用于交换后head指向
ListNode cur = head;
while (tNext != tail) {
ListNode cNext = cur.next; // tail的临时变量
cur.next = tNext; // cur下个节点指向交换成tail的指向
tNext = cur; // tail下个节点交换成cur,
cur = cNext; // 此时cNext由于cur.next的改变发生变化,指向tail.next,即cur=tail.next,
}
return new ListNode[] { tail, head };
}
}
11.随机链表的复制
class Solution {
public Node copyRandomList(Node head) {
if(head==null) return null;
Node p=head;
while (p!=null) {
Node newNode=new Node(p.val);
newNode.next=p.next;
p.next=newNode;
p=p.next.next;
}
p=head;
while (p!=null) {
if(p.random!=null) p.next.random=p.random.next;
p=p.next.next;
}
//拆分成两条链表
Node ans=new Node(-1);
Node pre=ans;
p=head;
while (p!=null) {
pre.next=p.next;
pre=pre.next;
p.next=pre.next;
p=p.next;
}
return ans.next;
}
}
12.排序链表
归并排序
public ListNode sortList(ListNode head) {
if(head==null || head.next==null) return head;
//fast=head.next 确保slow
ListNode fast=head.next; ListNode slow=head;
while (fast!=null && fast.next!=null) {
fast=fast.next.next;
slow=slow.next;
}
ListNode mid=slow.next;
slow.next=null;
ListNode left=sortList(head);
ListNode right=sortList(mid);
ListNode ans=merge(left, right);
return ans;
}
合并两个链表
static ListNode merge(ListNode l1,ListNode l2){
ListNode ans=new ListNode(1);
ListNode pre=ans;
while (l1!=null && l2!=null) {
if (l1.val<l2.val) {
pre.next=l1;
l1=l1.next;
}else{
pre.next=l2;
l2=l2.next;
}
pre=pre.next;
}
pre.next=l1==null?l2:l1;
return ans.next;
}
13.合并 K 个升序链表
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists,0, lists.length-1);
}
//合并
static ListNode merge(ListNode[] lists,int l,int r){
if(l==r) return lists[l];
if(l>r) return null;
int mid=(l+r)/2;
return mergeTwo(merge(lists, l, mid), merge(lists, mid+1, r));
}
static ListNode mergeTwo(ListNode l1,ListNode l2){
ListNode ans=new ListNode(-1);
ListNode pre=ans;
while(l1!=null&&l2!=null){
if(l1.val<l2.val){
pre.next=l1;
l1=l1.next;
}else{
pre.next=l2;
l2=l2.next;
}
pre=pre.next;
}
pre.next=l1==null?l2:l1;
return ans.next;
}
}
14 LRU缓存--HashMap+双向链表
双向链表--头插尾删--维护最近最少使用--每次使用了,就删掉原来的,头插入
满了--尾删--最久未使用
LRU属性
/*
HashMap+双向链表
*/
HashMap<Integer,Node> map=new HashMap<>();
int capacity;
Node head, tail; // 双向链表节点
双向链表
/*
双向链表
*/
//节点类--内部类
class Node{
int key;
int val;
Node next; //双向链表
Node pre;
//节点类构造函数
public Node(int key, int val){
this.key=key;
this.val=val;
}
}
//节点的增删改操作-哨兵的头尾节点
//头插法
private void insertHead(Node node){
Node nex=head.next;
head.next=node;
node.pre=head;
node.next=nex;
nex.pre=node;
}
//尾删法,并返回被删除的节点
private Node cutTail(){
Node last=tail.pre;
last.pre.next=tail;
tail.pre=last.pre;
last.pre=null;
last.next=null;
return last;
}
//删除特定节点node
private void cutNode(Node node){
node.pre.next=node.next;
node.next.pre=node.pre;
node.next=null;
node.pre=null;
}
LRU类方法
/*
LRU缓存类方法
*/
//构造函数
public LRUCache(int capacity) {
this.capacity=capacity;
//哨兵的头尾节点
head=new Node(-1, -1);
tail=new Node(-1, -1);
head.next=tail;
tail.pre=head;
}
public int get(int key) {
if (map.containsKey(key)) {
Node node=map.get(key);
//取时,使用了,需要删掉原来的,头插入(最近使用)
cutNode(node);
insertHead(node);
return node.val;
}
return -1;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
Node node=map.get(key);
node.val=value;
//放入时,不管存不存在,使用了,需要删掉原来的,头插入(最近使用)
cutNode(node);
insertHead(node);
}else{
Node node=new Node(key, value);
map.put(key, node);
insertHead(node);
if (map.size()>capacity) {
//满了,从尾巴切除,头插尾删--保证满了,逐出最久未使用的关键字
Node delNode =cutTail();
map.remove(delNode.key); // map里也删掉相应的键值对
}
}
完整代码
class LRUCache {
/*
HashMap+双向链表
*/
HashMap<Integer,Node> map=new HashMap<>();
int capacity;
Node head, tail; // 双向链表节点
/*
双向链表
*/
//节点类--内部类
class Node{
int key;
int val;
Node next; //双向链表
Node pre;
//节点类构造函数
public Node(int key, int val){
this.key=key;
this.val=val;
}
}
//节点的增删改操作-哨兵的头尾节点
//头插法
private void insertHead(Node node){
Node nex=head.next;
head.next=node;
node.pre=head;
node.next=nex;
nex.pre=node;
}
//尾删法,并返回被删除的节点
private Node cutTail(){
Node last=tail.pre;
last.pre.next=tail;
tail.pre=last.pre;
last.pre=null;
last.next=null;
return last;
}
//删除特定节点node
private void cutNode(Node node){
node.pre.next=node.next;
node.next.pre=node.pre;
node.next=null;
node.pre=null;
}
/*
LRU缓存类方法
*/
//构造函数
public LRUCache(int capacity) {
this.capacity=capacity;
//哨兵的头尾节点
head=new Node(-1, -1);
tail=new Node(-1, -1);
head.next=tail;
tail.pre=head;
}
public int get(int key) {
if (map.containsKey(key)) {
Node node=map.get(key);
//取时,使用了,需要删掉原来的,头插入(最近使用)
cutNode(node);
insertHead(node);
return node.val;
}
return -1;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
Node node=map.get(key);
node.val=value;
//放入时,不管存不存在,使用了,需要删掉原来的,头插入(最近使用)
cutNode(node);
insertHead(node);
}else{
Node node=new Node(key, value);
map.put(key, node);
insertHead(node);
if (map.size()>capacity) {
//满了,从尾巴切除,头插尾删--保证满了,逐出最久未使用的关键字
Node delNode =cutTail();
map.remove(delNode.key); // map里也删掉相应的键值对
}
}
}
}