前言
所有题目均来自力扣题库中的hot 100,之所以要记录在这里,只是方便后续复习
19.删除链表的倒数第N个节点
题目:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
解题思路:
【链表遍历】删除某个节点好办,就是将该节点的前节点的下一个节点等于下一个节点的下一个节点,关键是如何找到要删除节点的前一个节点。我们可以先计算整个链表的长度length,然后要删除的节点就是第length-n+1个节点,其前一个节点就是第length-n个节点。还有一个问题是如果要删除的节点是头节点,头节点没有前节点怎么办?这样我们可以定义一个虚拟头节点,将原头节点放在整个虚拟头节点后面,但需要注意此时要删除节点的前节点的位置就随之变成了length - n + 1
代码(python):
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
node = ListNode(0, head)
first = head
second = node
for i in range(0, n):
first = first.next
while first :
first = first.next
second = second.next
second.next = second.next.next
return node.next
代码(java):
/**
* 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) {
// 获取链表长度
int length = getLength(head);
// 定义一个虚拟头,将参数节点添加到该节点的后面,为了删除原参数节点头位置时方便
ListNode newHead = new ListNode(0, head);
// 定义一个索引节点,用来遍历链表
ListNode cur = newHead;
// 计算得到要删除节点的 前一个节点 的位置即第几个节点,注意此时的头位置是 新定义的虚拟头
int index = length - n + 1;
//滑动索引节点 到 要删除节点的前一个节点的位置,注意滑动是 index - 1,而不是 index
for (int i = 0; i < index - 1; i ++){
cur = cur.next;
}
//将 删除节点的前一个节点 的 下一个节点 变为下一个节点的下一个节点 ,从而达到删除效果
cur.next = cur.next.next;
// 返回虚拟头节点的下一个节点,即原参数节点部分
return newHead.next;
}
public int getLength(ListNode head){
int length = 0;
ListNode newHead = head;
while (newHead != null){
length ++;
newHead = newHead.next;
}
return length;
}
}
知识点:
- 无
原题链接:删除链表的倒数第N个节点
206.反转链表
题目:
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
解题思路:
【反转链表】固定方式,记住即可
代码(python):
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
# 定义 索引节点
cur = head
# 定义之前节点,用来存储已经反转过的链表头
pre = None
# 如果索引节点不为空
while cur:
# 先存储索引节点的下一节点
next_node = cur.next
# 更新索引节点的下一节点为 之前节点
cur.next = pre
# 更新之前节点为 索引节点
pre = cur
# 更新索引节点 为原下一节点
cur = next_node
# 最后 之前节点 即为反转过的链表头
return pre
代码(java):
/**
* 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 pre = null;
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;;//存储下一个节点
cur.next = pre;//让下一个节点指向前一个节点
pre = cur;//更新前一个节点为当前节点,下一个循环使用
cur = next;//更新当前节点为刚刚存储的下一个节点,下一个循环使用
}
return pre;
}
}
知识点:
- 无
原题链接:反转链表
234.回文链表
题目:
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
示例 1:
输入:head = [1,2,2,1]
输出:true
示例 2:
输入:head = [1,2]
输出:false
解题思路:
【快慢指针+翻转链表】根据回文的性质,从两侧开始向中间滑动,一一对比对应位置的值,若都相等则为回文链表;但是这种单向链表没办法从后面向前遍历,那怎么办呢?我们可以先获取这个链表的中间位置,然后将中间位置的后面的链表翻转,这样遍历得到的新链表就达到了从后向前遍历的效果,再和前半部分链表一一对比,就可以判断是否为回文链表了;那么如果获取链表的中间位置呢,可以通过快慢指针,快指针一次走两部,慢指针一次走一步,快指针走到头,慢指针就在中间位置了
代码(python):
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def isPalindrome(self, head):
"""
:type head: ListNode
:rtype: bool
"""
# 通过快慢指针获取 链表中间节点
mid = self.get_mid(head)
# 将中间节点的后面的链表翻转
new_last_half = self.reverse(mid.next)
# 依次比较前半部分节点 和 翻转后的后半部分节点
cur_head = head
cur_last = new_last_half
while cur_head is not None and cur_last is not None:
if cur_head.val != cur_last.val:
return False
cur_head = cur_head.next
cur_last = cur_last.next
return True
def get_mid(self, head):
# 快慢指针
slow = head
fast = head
# 如果快指针可以走下去的话,注意条件是 next 和 next.next 均不为空,而不是 fast 和 next 不为空
while fast.next is not None and fast.next.next is not None:
# 快指针一次走两步,慢指针一次走一步
slow = slow.next
fast = fast.next.next
# 快指针走到不能再走 慢指针就是中间位置
return slow
def reverse(self, head):
# 定义索引节点
cur = head
# 定义上一个节点
pre = None
while cur is not None:
#存储当前节点的下一个节点
next_node = cur.next
# 当前节点的下一节点等于上一个的节点
cur.next = pre
# 更新上个节点
pre = cur
# 当前节点等于原下一节点, 达到滑动效果
cur = next_node
return pre
代码(java):
/**
* 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) {
if (head == null){
return true;
}
ArrayList<Integer> list = toList(head);
int left = 0;
int right = 0;
for(int i = 1; i < list.size()-1; i++){
left = i - 1;
right = i + 1;
while(left >= 0 && right <= list.size()-1){
if(!list.get(left).equals(list.get(right))){
break;
}else{
left--;
right++;
}
}
if(left < 0 && right > list.size()-1){
return true;
}
}
return false;
}
public ArrayList<Integer> toList(ListNode head){
ArrayList<Integer> list = new ArrayList<>();
list.add(0);
while (head != null){
list.add(head.val);
list.add(0);
head = head.next;
}
return list;
}
}
知识点:
- 无
原题链接:回文链表
148.排序链表
题目:
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
示例 1:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例 2:
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
示例 3:
输入:head = []
输出:[]
解题思路:
【快慢指针+归并排序】链表排序起始和数组排序思想是一样的,时间复制度Onlogn可以用归并排序;归并排序的思想:从中间将数组一分为二,用递归分别对两个新数组排序(递归的中止条件是数组只有一个元素或者为空),最后再合并两个已排好序的数组;那么我们怎么获取链表的中间位置呢?快慢指针:快指针一次走两部,慢指针一次走一步,快指针走到头,慢指针就在中间位置了;那怎么合并俩个有序链表呢?在两个链表都有值的情况下遍历两个链表,选取较小值添加到结果链表,同时滑动选中的链表以及结果链表,遍历完成后,在将两个链表有剩余的部分添加到结果链表中即可
代码(python):
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def sortList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
return self.guibingSortList(head, None)
def guibingSortList(self, head, tail):
if head is None :
return head
if head.next == tail:
head.next = None
return head
fast = slow = head
while fast != tail:
slow = slow.next
fast = fast.next
if fast != tail:
fast = fast.next
return self.merge(self.guibingSortList(head, slow), self.guibingSortList(slow, tail))
def merge(self, node1, node2):
head = ListNode()
node = head
while node1 and node2:
if node1.val < node2.val:
node.next = node1
node1 = node1.next
else:
node.next = node2
node2 = node2.next
node = node.next
while node1:
node.next = node1
node1 = node1.next
node = node.next
while node2:
node.next = node2
node2 = node2.next
node = node.next
return head.next
代码(java):
/**
* 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 sortList(ListNode head) {
//如果当前链表是空,或者当前链表只有一个节点,则直接返回当前链表
if(head == null || head.next == null){
return head;
}
// 获取中间节点,根据中间节点将链表一分为二
ListNode mid = getMid(head);
ListNode last = mid.next;
mid.next = null;
// 两个分出来的链表分别排序 得到对应的排序链表 递归
ListNode sortedHead = sortList(head);
ListNode sortedLast = sortList(last);
// 合并两个排序好的链表 并返回
return merage(sortedHead, sortedLast);
}
public ListNode getMid(ListNode head){
//快慢指针
ListNode slow = head;
ListNode fast = head;
// 如果快指针还能走,快指针一次走两步,慢指针一次走一步
while(fast.next != null && fast.next.next != null){
slow = slow.next;
fast = fast.next.next;
}
// 快指针走不了了,满指针即为中间位置
return slow;
}
// 合并两个有序链表
public ListNode merage(ListNode head1, ListNode head2){
// 定义一个虚拟头
ListNode fakeHead = new ListNode(0);
// 定义各个索引节点
ListNode cur1 = head1;
ListNode cur2 = head2;
ListNode curH = fakeHead;
// 如果两个索引节点位置都不为空,选取较小值添加到 结果链表中,并滑动选取的节点和结果链表
while(cur1 != null && cur2 != null){
if(cur1.val <= cur2.val){
curH.next = new ListNode(cur1.val);
cur1 = cur1.next;
curH = curH.next;
}else{
curH.next = new ListNode(cur2.val);
cur2 = cur2.next;
curH = curH.next;
}
}
// 若两个链表还有剩余值,则将剩余值添加到结果链表中
if(cur1 != null){
curH.next = cur1;
}
if(cur2 != null){
curH.next = cur2;
}
//返回虚拟头的下一节点
return fakeHead.next;
}
}
知识点:
- 无
原题链接:排序链表
146.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) 的平均时间复杂度运行。
解题思路:
【Hash表+双向链表】问题的关键有两点,第一个点是维护一个使用的先后顺序,由于这个顺序可能会随时被调整,所以用什么队列、栈都不合适,合适的是链表结构,链表头为最先使用的节点,尾为最后使用的节点;第二个点是要在O1时间内获取对应值,可以联想到用hash键值对结构;综上,我们可以用这两个数据结构配合,初始化LRU时初始化这两个结构,在get时从hash键值对中获取该值,并更新该节点到链表尾位置,在put时,新建节点添加到键值对结构中 ,且追加到链表尾位置,再此之前若长度达到了容量要删除链表头位置以及键值对存储;这里有一个技巧,如果要删除或新增链表的头位置或者尾位置,最好定义虚拟头和虚拟尾,规避掉头位置没有上一个节点以及尾位置没有后一个节点此类的判断
代码(python):
class LRUCache(object):
def __init__(self, capacity):
"""
:type capacity: int
"""
# 初始化虚拟头 和 虚拟尾 并让虚拟头和虚拟尾形成双向链表
self.fake_head = TreeNode(0, 0, None, None)
self.fake_tail = TreeNode(0, 0, self.fake_head, None)
self.fake_head.next = self.fake_tail
# 定义容量
self.capacity = capacity
# 定义当前长度
self.size = 0
# 定义hash结构存储键值对 key-val = int-TreeNode
self.map = dict()
def get(self, key):
"""
:type key: int
:rtype: int
"""
# 如果当前节点包含该key 则根据key到字典中获取该节点,并将该节点移到头位置,并将该节点值返回
if self.map.has_key(key):
node = self.map.get(key)
self.remove_node(node)
self.append_node(node)
return node.val
# 如果当前节点不包含该key 则返回-1
else:
return -1
def put(self, key, value):
"""
:type key: int
:type value: int
:rtype: None
"""
# 如果当前节点包含该key 则根据key到字典中获取该节点,并将该节点移到头位置,并更新该节点值
if self.map.has_key(key):
node = self.map.get(key)
node.val = value
self.remove_node(node)
self.append_node(node)
# 如果当前节点不包含该key
else:
# 判断当前长度是否已达到容量,若达到则 删除双向链表头节点(即虚拟头下一个节点) 并根据该节点存的key从字典中删除
if self.size == self.capacity:
self.map.pop(self.fake_head.next.key)
self.remove_node(self.fake_head.next)
# 建立新节点 并将该节点添加到字典中 以及添加到尾位置(即虚拟尾上一个节点)
new_node = TreeNode(key, value, None, None)
self.map[key] = new_node
self.append_node(new_node)
def append_node(self, node):
# 让虚拟尾的上一个节点 与 当前节点 建立双向链表关系
self.fake_tail.pre.next = node
node.pre = self.fake_tail.pre
# 让虚拟尾 和 当前节点 建立双向链表关系
node.next = self.fake_tail
self.fake_tail.pre = node
# 更新长度
self.size += 1
def remove_node(self, node):
# 让当前节点的前一个节点 和 当前节点的后一个节点 建立双向链表关系
node.pre.next = node.next
node.next.pre = node.pre
# 更新长度
self.size -= 1
class TreeNode():
def __init__(self, key, val, pre, next):
# 注意一定要存key, 用来删除节点时,能知道从字典中对应删除的key
self.key = key
self.val = val
self.pre = pre
self.next = next
代码(java):
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
}
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
知识点:
- 如果要删除或新增链表的头位置或者尾位置,最好定义虚拟头和虚拟尾,规避掉头位置没有上一个节点以及尾位置没有后一个节点此类的判断
- python删除dict中某个键值对,可以用dict.pop(key)或者del dict[key]
原题链接:LRU缓存