大家好!下面是我(一个小小的搬运工)在秋招的时候在Leetcode上整理的一些链表的题目(中等难度),笔试和面试考相似思路题目的概率比较大,大家如果准备春秋季招聘可以先根据这些题目复习(具体思路可以看Leetcode中的讲解——困难的题有链接):
定义链表结构
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
//2019_05_03
//1、反转指定区间的链表顺序
//Given1->2->3->4->5->NULL, m = 2 and n = 4,
//return1->4->3->2->5->NULL
/*
对于reverse部分有点迷糊。网上看到的解释,也许更能帮助理解.https://yq.aliyun.com/articles/3867
不妨拿出四本书,摞成一摞(自上而下为 A B C D),要让这四本书的位置完全颠倒过来(即自上而下为 D C B A):
盯住书A,每次操作把A下面的那本书放到最上面
初始位置:自上而下为 A B C D
第一次操作后:自上而下为 B A C D
第二次操作后:自上而下为 C B A D
第三次操作后:自上而下为 D C B A
*/
class Solution {
public:
ListNode *reverseBetween(ListNode *head, int m, int n) {
ListNode *dummy = new ListNode(0);
dummy->next = head;
ListNode *preStart = dummy;
ListNode *start = head;
for(int i = 1; i < m; i++)
{
preStart = start;
start = start->next;
}
for(int i = 0; i < n - m; i++)
{
ListNode *tmp = start->next;
start->next = tmp->next;
tmp->next = preStart->next;
preStart->next = tmp;
}
return dummy->next;
}
};
//2019_05_05
//1、将两个已经排序的链表合并起来
/*
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) {
ListNode *head = new ListNode(0);
ListNode *t = head;
while (l1 != NULL || l2 != NULL) {
if (l1 == NULL) {
t->next = l2;
l2 = l2->next;
}
else if (l2 == NULL) {
t->next = l1;
l1 = l1->next;
}
else if (l1->val < l2 -> val){
t->next = l1;
l1 = l1->next;
}
else {
t->next = l2;
l2 = l2->next;
}
t = t->next;
}
return head->next;
}
};
//2019_05_23
//构建一个LRU(最近最少使用)缓存机制
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
/*
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lru-cache
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
核心思想就是构建出带有表头和表尾的双链表,并用一个map存储所有节点,更新和添加
时将节点放在双链表头部,超限时删除表尾。获取时直接从map中获取节点值。
//定义三个private数据,LRU尺寸,LRU pair<key,value>, LRU map<key,iterator of pair>
//利用splice操作erase,makepair 等完成LRUcache
//put()
如果m中有对应的key,value,那么移除l中的key,value
如果m中没有,而size又==cap,那么移除l最后一个key,value,并移除m中对应的key,iterator
在l的开头插入key,value,然后m[key]=l.begin();
*/
class LRUCache {
public:
LRUCache(int capacity){
cap=capacity;
}
int get(int key){
unordered_map<int,list<pair<int,int>>::iterator>::iterator it=m.find(key);
if(it==m.end()) return -1;
l.splice(l.begin(),l,it->second);
return it->second->second;
}
void put(int key,int value){
auto it=m.find(key);
if(it!=m.end()) l.erase(it->second);
if(l.size()==cap){
int k=l.rbegin()->first;
l.pop_back();
m.erase(k);//map可以根据key值和迭代器值移除,查找
}
l.push_front(make_pair(key,value));
m[key]=l.begin();
}
private:
int cap;//LRU size
list<pair<int,int>>l;//pair<key,value>
unordered_map<int,list<pair<int,int>>::iterator>m;//unordered_map<key,key&value's pair iterator>
};
//2019_06_18
/*
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head==nullptr)
return nullptr;
if(head->next==nullptr)
return nullptr;
ListNode *slow=head;
ListNode *fast=head->next;
int count=1;
while(slow!=fast){
if(fast->next==nullptr||fast->next->next==nullptr)
return nullptr;
else{
slow=slow->next;
fast=fast->next->next;
count++;
}
}
slow=head;
fast=head;
while(count--)
fast=fast->next;
if(slow==fast)
return slow;
else{
while(slow!=fast){
fast=fast->next;
slow=slow->next;
}
return slow;
}
}
};
//2019_07_04
/**
1、给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例 :
给定这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
说明 :
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换
**/
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if(k==1)
return head;
if(!head||!head->next)
return head;
int len=findlen(head);
int num=len/k;
int fin=k-1;
ListNode* pre=new ListNode(-1);
pre->next=head;
ListNode* now=head;
ListNode* next=now->next;
ListNode* dump=pre;
while(num--){
fin=k-1;
while(fin--){
now->next=next->next;
next->next=pre->next;
pre->next=next;
next=now->next;
}
pre=now;
if(pre->next)
now=pre->next;
if(now->next)
next=now->next;
}
return dump->next;
}
int findlen(ListNode* head){
ListNode* p=head;
int count=0;
while(p){
count++;
p=p->next;
}
return count;
}
};
/*
2、设计一个支持在平均 时间复杂度 O(1) 下, 执行以下操作的数据结构。
注意: 允许出现重复元素。
insert(val):向集合中插入元素 val。
remove(val):当 val 存在时,从集合中移除一个 val。
getRandom:从现有集合中随机获取一个元素。每个元素被返回的概率应该与其在集合中的数量呈线性相关。
*/
/*
在 insert(val) 和 remove(val) 都可以使用一个 unordered_map<int,int> 来解决。
但是直接使用 hash 不能够 真正的使用一个 getRandom ,所以我们可以使用一个额外的 数组 q[] 来存储所有的数字,而我们的 unordered_map<int,vector<int> > 来存储每一个数字加入之后他在 q[] 中的下标 idx 当要移除某一个数字的时候,我们取到他的下标值 idx 之后再交换再 q[]的对应位置的数字 ,把需要删除的那个数字放到之后 再 cnt-- 就行了。
而获取一个随机数只需要 rand() %cnt 就能生成一个随机的数据,然后返回一个q[r_idx] 的值就行了
*/
class RandomizedCollection {
int q[10000] = {};
public:
unordered_map<int,vector<int> > m;
int cnt = 0;
/** Initialize your data structure here. */
RandomizedCollection() {
srand(time(0));
}
/** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */
bool insert(int val) {
q[cnt] = val;
m[val].push_back(cnt++);
return m[val].size()<=1;
}
/** Removes a value from the collection. Returns true if the collection contained the specified element. */
bool remove(int val) {
if(m[val].size()){
int idx = m[val].back();
m[val].pop_back();
int back = q[cnt-1];
for(int & n: m[back]){
if(n==cnt-1){
n = idx;
q[idx] = back;
break;
}
}
cnt--;
return true;
}
return false;
}
/** Get a random element from the collection. */
int getRandom() {
int ridx = rand()%(cnt);
return q[ridx];
}
};
//2019_07_09
/*
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
int len=lists.size();
if(len==0)
return nullptr;
if(len==1)
return lists[0];
return mergesort(lists,0,len-1);
}
ListNode* mergesort(vector<ListNode*>& lists,int start,int end)
{
if(start==end)
return lists[start];
int mid=(start+end)/2;
ListNode* L1=mergesort(lists,start,mid);
ListNode* L2=mergesort(lists,mid+1,end);
return merge(L1,L2);
}
ListNode* merge(ListNode* L1,ListNode* L2)
{
if(L1==nullptr)return L2;
if(L2==nullptr)return L1;
if(L1->val<L2->val)
{
L1->next=merge(L1->next,L2);
return L1;
}
else
{
L2->next=merge(L1,L2->next);
return L2;
}
}
};
//2019_07_20
/*
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
*/
class Solution {
public:
ListNode* findMiddle(ListNode* head){
ListNode* chaser = head;
ListNode* runner = head->next;
while(runner != NULL && runner->next != NULL){
chaser = chaser->next;
runner = runner->next->next;
}
return chaser;
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == NULL){
return l2;
}
if(l2 == NULL){
return l1;
}
ListNode* dummy = new ListNode(0);
ListNode* head = dummy;
while(l1 != NULL && l2 != NULL){
if(l1->val > l2->val){
head->next = l2;
l2 = l2->next;
}
else{
head->next = l1;
l1 = l1->next;
}
head = head->next;
}
if(l1 == NULL){
head ->next = l2;
}
if(l2 == NULL){
head->next = l1;
}
return dummy->next;
}
ListNode* sortList(ListNode* head) {
if(head == NULL || head ->next == NULL){
return head;
}
ListNode* middle = findMiddle(head);
ListNode* right = sortList(middle->next);
middle -> next = NULL;
ListNode* left = sortList(head);
return mergeTwoLists(left, right);
}
};
//2019_07_21
/*
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
整体思路是先用快慢指针找中点(慢指针的最终位置)
然后再反转中点指向的后半部分链表
前半部分再与后半部分逐元素比较
时间复杂度为O(n)
空间复杂度关键看反转链表是迭代还是递归,我这里用迭代所以是O(1),递归则是O(n)
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(!head||!head->next) return true; //空链表表或链表只有一个元素
ListNode *slow=head,*fast=head; //快慢指针
for( ; fast&&fast->next ; fast=fast->next->next,slow=slow->next); //快慢指针迭代找中点
ListNode* back=rev(slow); //反转后半链表
for(ListNode* front=head ; front&&back ; front=front->next,back=back->next)
if(back->val!=front->val) return false; //前半链表与后半链表逐元素比较
return true;
}
private:
//反转链表的工具函数
ListNode* rev(ListNode* head){
ListNode* pre=new ListNode(-1);
ListNode* post=nullptr;
pre->next=head;
post=head->next;
while(head&&head->next){
head->next=post->next;
post->next=pre->next;
pre->next=post;
post=head->next;
}
return pre->next;
}
};