题目要求:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 代码及思路
遍历所有节点,将所有节点的next指向前一个节点 由于要改变节点的next指向,而链表是单向的,因此需要使用一个节点变量先保存当前节点的next节点,然后再指向前一个节点 代码
class Solution {
public ListNode reverseList( ListNode head ) {
if( head== null|| head.next == null) return head ;
ListNode pre = null;
ListNode cur = head;
while( head!= null) {
cur = head.next;
head.next = pre;
pre = head;
head = cur;
}
return pre;
}
}
2. LRU 缓存(高频) 题目链接
题目要求:请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。 void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。 函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。 代码及思路
使用双向链表进行实现,便于调整节点的位置 需要注意的是还需要一个hashmap来存储关键字和节点的对应的关系,并且在相应的操作时不要忘记修改hashmap 代码
class LRUCache {
class DNode{
int val;
int key;
DNode next;
DNode pre;
public DNode ( ) { }
public DNode( int val,int key) {
this.val = val;
this.key = key;
}
}
DNode head ;
DNode tail ;
int size;
int capacity;
Map< Integer,DNode> cache = new HashMap<> ( ) ;
public LRUCache( int capacity) {
head = new DNode( ) ;
tail = new DNode( ) ;
head.next = tail;
tail.pre = head;
this.capacity = capacity;
size = 0 ;
}
public int get( int key) {
if( cache.containsKey( key)) {
DNode node = cache.get( key) ;
moveToHead( node) ;
return node.val;
}
return -1;
}
public void put( int key, int value) {
if( cache.containsKey( key)) {
DNode node = cache.get( key) ;
node.val = value;
moveToHead( node) ;
} else{
DNode node = new DNode( value,key) ;
cache.put( key,node) ;
insertHead( node) ;
size++;
if( size> capacity) {
int lastKey = moveTail( ) ;
cache.remove( lastKey) ;
size--;
}
}
}
private void insertHead( DNode node ) {
node.next = head.next;
head.next.pre = node;
head.next = node;
node.pre = head;
}
private void moveToHead( DNode node ) {
node.pre.next = node.next;
node.next.pre = node.pre;
insertHead( node) ;
}
private int moveTail ( ) {
int key = tail.pre.key;
tail.pre = tail.pre.pre;
tail.pre.next = tail;
return key;
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache( capacity) ;
* int param_1 = obj.get( key) ;
* obj.put( key,value) ;
*/
3.合并两个有序链表 题目链接
题目要求:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 代码及思路
先遍历两个链表,选择值小的节点加入合并后的链表 当其中一个链表为空时结束循环,将不为空的链表剩下节点整个加入合并后的链表 最好使用一个虚拟头接点,便于处理 代码
/**
* 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 dumpy = new ListNode( ) ;
ListNode cur = dumpy;
while( list1!= null&& list2!= null) {
if( list1.val<= list2.val) {
cur.next = list1;
list1 = list1.next;
} else{
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1== null?list2:list1;
return dumpy.next;
}
}
题目要求:给你一个链表的头节点 head ,判断链表中是否有环。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。 如果链表中存在环 ,则返回 true 。 否则,返回 false 。 代码及思路
使用快慢指针解决问题,慢指针每次移动一步,快指针每次移动两步(相遇问题) 当快慢指针相遇表明存在环 代码
public class Solution {
public boolean hasCycle ( ListNode head) {
ListNode slow= head;
ListNode fast= head;
while ( fast!= null && fast. next!= null ) {
slow= slow. next;
fast= fast. next. next;
if ( slow== fast) return true ;
}
return false ;
}
}
5.环形链表 II 题目链接
题目要求:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 不允许修改 链表。 代码及思路
本题是在找链表是否为环的基础上找出环的入口点 在找环的过程中,当快慢指针相遇时慢指针从头开始,然后快指针和慢指针每次相同速度移动,下一次相遇的节点即为环入口(具体分析需要数学) 代码
public class Solution {
public ListNode detectCycle ( ListNode head) {
ListNode slow= head;
ListNode fast= head;
while ( fast!= null && fast. next!= null ) {
slow= slow. next;
fast= fast. next. next;
if ( slow== fast) {
slow= head;
while ( slow!= null ) {
if ( slow== fast) return slow;
slow= slow. next;
fast= fast. next;
}
}
}
return null ;
}
}
6. 合并 K 个升序链表 题目链接
题目要求:给你一个链表数组,每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中,返回合并后的链表。 代码及思路
采用逐个合并的方法,每次合并当前链表前已经合并的链表和当前链表 代码
class Solution {
public ListNode mergeKLists ( ListNode[ ] lists) {
if ( lists. length== 0 ) return null ;
if ( lists. length== 1 ) return lists[ 0 ] ;
ListNode newHead= new ListNode ( ) ;
newHead. next= mergeTwo ( lists[ 0 ] , lists[ 1 ] ) ;
for ( int i= 2 ; i< lists. length; i++ ) {
newHead. next= mergeTwo ( newHead. next, lists[ i] ) ;
}
return newHead. next;
}
private ListNode mergeTwo ( ListNode l1, ListNode l2) {
ListNode dumpy= new ListNode ( ) ;
ListNode cur= dumpy;
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 dumpy. next;
}
}
题目要求:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交: 代码及思路
计算两个链表的长度,让较长的链表先走 (lengthA - lengthB) 步,这样两个链表就从同一节点开始遍历 然后同时遍历两个链表,找到第一个相同的节点,即为相交节点 代码
public class Solution {
public ListNode getIntersectionNode ( ListNode headA, ListNode headB) {
ListNode curA= headA;
ListNode curB= headB;
int l1= 0 ;
int l2= 0 ;
while ( curA!= null ) {
l1++ ;
curA= curA. next;
}
while ( curB!= null ) {
l2++ ;
curB= curB. next;
}
int move= Math. abs ( l1- l2) ;
curA= l1> l2? headA: headB;
curB= l1> l2? headB: headA;
while ( move-- > 0 ) {
curA= curA. next;
}
while ( curA!= null && curB!= null ) {
if ( curA== curB) return curA;
curA= curA. next;
curB= curB. next;
}
return null ;
}
}
8. 删除链表的倒数第 N 个结点 题目链接
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 代码及思路
使用快慢指针,快指针先移动n次 然后快慢指针以相同速度每次移动一个节点一起移动,当快指针到达最后一个节点时,慢指针的next指向要被删除的节点,删除即可 为了统一处理,使用一个虚拟节点 代码
class Solution {
public ListNode removeNthFromEnd ( ListNode head, int n) {
ListNode dumpy= new ListNode ( ) ;
dumpy. next= head;
ListNode slow= dumpy;
ListNode fast= dumpy;
while ( n-- > 0 ) {
fast= fast. next;
}
while ( fast. next!= null ) {
slow= slow. next;
fast= fast. next;
}
slow. next= slow. next. next;
return dumpy. next;
}
}
题目要求:给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 代码及思路
使用快排的思想,每次每次找到链表的中间节点,然后将链表分成两个子链表进行排序 然后将两个排序后的子链表合并成一个链表(之前已经做过) 代码
class Solution {
public ListNode sortList ( ListNode head) {
if ( head== null || head. next== null ) return head;
ListNode mid= findMid ( head) ;
ListNode rightHead= mid. next;
mid. next= null ;
ListNode n1= sortList ( head) ;
ListNode n2= sortList ( rightHead) ;
return mergr ( n1, n2) ;
}
private ListNode findMid ( ListNode head) {
if ( head== null || head. next== null ) {
return head;
}
ListNode slow= head;
ListNode fast= head. next. next;
while ( fast!= null && fast. next!= null ) {
slow= slow. next;
fast= fast. next. next;
}
return slow;
}
private ListNode mergr ( ListNode n1, ListNode n2) {
ListNode dumpy= new ListNode ( ) ;
ListNode cur= dumpy;
while ( n1!= null && n2!= null ) {
if ( n1. val<= n2. val) {
cur. next= n1;
n1= n1. next;
} else {
cur. next= n2;
n2= n2. next;
}
cur= cur. next;
}
cur. next= n1!= null ? n1: n2;
return dumpy. next;
}
}
10. 两数相加 题目链接
题目要求:给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。 代码及思路
遍历两个链表,每一次分别使用两个整数取出两个链表中节点值 如果链表节点为空,则该整数值为0即可 每次相加之后注意需要进位,因此当前结果值应该是两个链表节点值和上一节点和的进位之和与10取余 最后注意两个链表都为空后还要看最后有没有进位 代码
class Solution {
public ListNode addTwoNumbers ( ListNode l1, ListNode l2) {
int c= 0 ;
ListNode dumpy= new ListNode ( ) ;
ListNode cur= dumpy;
while ( l1!= null || l2!= null ) {
int a= 0 ;
int b= 0 ;
if ( l1!= null ) {
a= l1. val;
l1= l1. next;
}
if ( l2!= null ) {
b= l2. val;
l2= l2. next;
}
int num= ( a+ b+ c) % 10 ;
c= ( a+ b+ c) / 10 ;
ListNode node= new ListNode ( num) ;
cur. next= node;
cur= cur. next;
}
if ( c!= 0 ) {
cur. next= new ListNode ( c) ;
}
return dumpy. next;
}
}