1.JZ3 从尾到头打印链表
题目描述:
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
思路一:如果是从尾到头打印链表,我们自然想到可以使用栈存储节点值,然后倒序打印。
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> result;
stack<int> arr;
ListNode* p=head;
while(p!=NULL)
{
arr.push(p->val);
p=p->next;
}
while(!arr.empty())
{
result.push_back(arr.top());
arr.pop();
}
return result;
}
};
思路二:可以原地反转链表,然后反转后的顺序打印链表。需要注意的是,原地反转链表需要借助额外两个节点指针,需要特别注意的是节点反转指针的顺序遵从“由后往前”的次序。
class Solution {
public:
vector<int> printListFromTailToHead(struct ListNode* head) {
vector<int> res;
ListNode *cur=head;
ListNode *pre=cur;
if(head==NULL)
return res;
while(head->next!=NULL){
//将cur节点移到pre节点之前
cur=head->next;
head->next=cur->next;
cur->next=pre;
pre=cur;
}
while(cur){
res.push_back(cur->val);
cur=cur->next;
}
return res;
}
};
2.JZ14 链表中倒数第k个结点
题目描述:
输入一个链表,输出该链表中倒数第k个结点。
思路一:暴力法
先遍历整个链表,统计节点个数n(从1开始),然后从头遍历链表,输出第n-k-1个节点就是结果。
思路二:双指针法
先让一个指针走上k步,此时当前指针距离链表尾部距离为n-k,然后让另一个指针从头开始两个指针同时走,当指针一到达尾部时,指针二距离尾部为n-(n-k)= k,即倒数第k个结点。
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
//p1指针先遍历k个节点,pListHead再从头开始
ListNode* p1=pListHead;
for(int i=0;i<k;i++)
if(!p1)
return nullptr;
else
p1=p1->next;
while(p1){
p1=p1->next;
pListHead=pListHead->next;
}
return pListHead;
}
};
3.JZ15 反转链表 链表
题目描述:
输入一个链表,反转链表后,输出新链表的表头。
思路一:原地反转。与第一题非常类似,只是输出反转后的表头。
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(pHead == NULL || pHead->next == NULL)
{
return pHead;
}
ListNode* pre = NULL;
ListNode* after = NULL;
while(pHead)
{
//此处顺序需要弄清楚,先后再前
after = pHead->next; //after最多为空
pHead->next = pre;
pre = pHead;
pHead = after;
}
return pre;
}
};
思路二:使用栈存储结点,利用栈的先入后出顺序。与第一题非常类似,只是输出反转后的表头。
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(pHead == NULL || pHead -> next == NULL)
return pHead;
ListNode* p = pHead;
stack<ListNode* > s; //链表类型的栈
while(p -> next)
{
s.push(p);
p = p ->next;
}
ListNode* head = p; //原链表的最后指针p,即新反序链表头指针,用head储存
while(!s.empty())
{
p -> next = s.top();
p = p ->next; //移动
s.pop();
}
p -> next = NULL;
return head;
}
};
4.JZ16 合并两个排序的链表
题目描述:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路:先判断链表是否为空,为空就返回另一个。新建一个表头,其下一个结点是两个链表中的较小结点,以此循环直到一个链表结束。然后判断两个链表是否结束,没结束则直接添加到新链表尾部,最后返回的是新表头的下一结点。
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(!pHead1)
return pHead2;
if(!pHead2)
return pHead1; //若有某一个链表为空,则返回另一个
ListNode *p=new ListNode(0);
ListNode *q=p;//两个指针都指向合成后新链表头节点,p用来做插入,q用来保存头结点地址
while(1)
{
if(pHead1->val < pHead2->val)
{
p->next = pHead1; //如果L1值小,则L1向下走
pHead1 = pHead1->next;
}
else
{
p->next = pHead2; //如果L2值小,则L2向下走
pHead2 = pHead2->next;
}
p = p->next;
if(pHead1==NULL||pHead2==NULL) //若某个链表走完,则跳出
break;
}
if(!pHead1)
p->next = pHead2;
if(!pHead2)
p->next = pHead1; //若某个链表走完,则将另一个链表续在合成后链表的末尾
return q->next;
}
};
5.JZ25 复杂链表的复制
题目描述:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路一:对于一个单链表的复制比较简单,但是对于复杂链表,我们可以先复制普通next指针的链表,然后处理random。本方法默认节点值不重复。
class Solution {
public:
RandomListNode* Clone(RandomListNode* pHead)
{
if(pHead == nullptr)
return pHead;
auto p1 = pHead; //保存头结点
auto head = new RandomListNode(pHead->label); //新建表头
auto p2 = head; //p遍历,q保存表头
while(p1 != nullptr)
{
//遍历原链表,建立下一节点
p1 = p1->next;
if(p1)
{
p2->next = new RandomListNode(p1->label);
p2 = p2->next;
}
else p2->next = NULL;
}
p1 = pHead;
p2 = head;
while(p1 != nullptr)
{
//遍历原链表,建立随机节点
if(p1->random != nullptr)
{
//存在random节点,设置从头开始遍历,直到找到random的值相同的节点,默认节点值不重复
auto l = pHead;
auto r = head;
while(l->label != p1->random->label)
{
r = r->next;
l = l->next;
}
p2->random = r;
}
p1 = p1->next;
p2 = p2->next;
}
return head;
}
};
思路二:原地复制,每一个结点后都复制一个与原节点相同的节点,对于random,只需要指向random->next即可。
/*
struct RandomListNode {
int label;
struct RandomListNode *next, *random;
RandomListNode(int x) :
label(x), next(NULL), random(NULL) {
}
};
*/
class Solution {
public:
RandomListNode* Clone(RandomListNode* pHead)
{
if(pHead == NULL) return NULL;
RandomListNode* head = pHead;
//复制
while(head){
RandomListNode* cloneNode = new RandomListNode(head->label);
cloneNode->next = head->next;
head->next = cloneNode;
head = cloneNode->next;
}
head = pHead;
//处理复杂指针
while(head){
if(head->random){
head->next->random = head->random->next;
}
head = head->next->next;
}
//拆分链表
RandomListNode* clonehead = pHead->next;
head = pHead;
while(head){
RandomListNode* cloneNode = head->next;
head->next = cloneNode->next;
//注意cloneNode->next可能为空
cloneNode->next = cloneNode->next == NULL?NULL:cloneNode->next->next;
head = head->next;
}
return clonehead;
}
};
6.JZ26 二叉搜索树与双向链表
题目描述:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向
思路:对于一棵二叉搜索树,其左子树上所有的点小于根节点,右子树节点大于根节点,其子树也是相同。中序遍历即返回二叉搜索树的递增排序。将二叉搜索树中序遍历的结果存入数组中,然后对数组进行双向链表重建。
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree)
{
//二叉搜索树:左子树上所有的点小于根节点,右子树节点大于根节点,其子树也是相同
//中序遍历即返回二叉搜索树的递增排序
vector<TreeNode* > arrayTree; //储存树节点的数组
if(pRootOfTree) //中序遍历所有节点
{
Mid(pRootOfTree,arrayTree);
}
else
return NULL;
for(int i = 0; i<arrayTree.size();i++)
{
if(i == 0)
{
arrayTree[i]->left == NULL;
if(i+1 <= arrayTree.size()-1)
arrayTree[i]->right=arrayTree[i+1];
else //尾结点
arrayTree[i]->right=NULL;
}
else if(i==arrayTree.size()-1)
{
arrayTree[i]->right == NULL;
if(i-1>=0)
arrayTree[i]->left=arrayTree[i-1];
else //头结点
arrayTree[i]->left=NULL;
}
else
{
arrayTree[i]->left=arrayTree[i-1];
arrayTree[i]->right=arrayTree[i+1];
}
}
return arrayTree[0]; //头结点
}
private:
void Mid(TreeNode* pRootOfTree,vector<TreeNode* > &arrayTree) //中序遍历递归
{
if(pRootOfTree == NULL)
return;
Mid(pRootOfTree->left,arrayTree);
arrayTree.push_back(pRootOfTree);
Mid(pRootOfTree->right,arrayTree);
}
};
7.JZ36 两个链表的第一个公共结点
题目描述:
输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
思路一:有公共结点,那么分别统计两个链表的长度,让长的链表先走一部分,使得剩下的部分与短的链表长度相等,随后一一比较,找到公共结点,返回即可。
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
//两个链表在第一个公共节点后使用公共的节点
ListNode* p1 = pHead1;
ListNode* p2 = pHead2;
int len1 = 0,len2 = 0,diff; //两个链表的长度
while(p1){
len1++;
p1 = p1->next;
}
while(p2){
len2++;
p2 = p2->next;
}
if(len1 > len2){
diff = len1 - len2;
p1 = pHead1;
p2 = pHead2;
}
else{
diff = len2 - len1;
p1 = pHead2;
p2 = pHead1;
}
for(int i = 0;i<diff;i++){
//两链表剩下的节点数相等
p1 = p1->next;
}
while(p1!=NULL&&p2!=NULL){
if(p1 == p2)
break;
p1 = p1->next;
p2 = p2->next;
}
return p1;
}
};
思路二:有公共结点,我们要保证两个链表剩下的部分一样长,才可能找到公共结点。可以分别遍历两个链表,一个走完后将其next等于另一个链表的表头,这样两个链表一样长
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
if(pHead1==NULL || pHead2==NULL) return NULL;
ListNode* p1 = pHead1;
ListNode* p2 = pHead2;
while(p1 != p2){
if(p1 == NULL) p1 = pHead2;
if(p2 == NULL) p2 = pHead1;
if(p1 != p2){
p1 = p1->next;
p2 = p2->next;
}
}
return p1;
}
};
8.JZ46 孩子们的游戏(圆圈中最后剩下的数)
题目描述:
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1
思路一:数学法
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if(n==0)
return -1;
int s=0;
for(int i=2;i<=n;i++){
s=(s+m)%i;
}
return s;
}
};
思路二:数组模拟法。利用数组模拟环,记录每次出去的下标并将其置为-1,循环时遇到值为-1的直接跳过,最后那个值不为-1的即是结果。
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if(n<1 || m<1) return -1;
vector<int> array(n,0);
int i = -1,step = 0, count = n;
while(count>0){ //跳出循环时将最后一个元素也设置为了-1
i++; //指向上一个被删除对象的下一个元素。
if(i>=n) i=0; //模拟环。
if(array[i] == -1) continue; //跳过被删除的对象。
step++; //记录已走过的。
if(step==m) { //找到待删除的对象。
array[i]=-1;
step = 0;
count--;
}
}
return i;//返回跳出循环时的i,即最后一个被设置为-1的元素
}
};
9.JZ55 链表中环的入口结点
题目描述:
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
解析:
链表头到环入口长度为–a
环入口到相遇点长度为–b
相遇点到环入口长度为–c
则:相遇时
快指针路程=a+(b+c)k+b ,k>=1 其中b+c*为环的长度,k为绕环的圈数(k>=1,即最少一圈,不能是0圈,不然和慢指针走的一样长,矛盾)。
慢指针路程=a+b
**快指针走的路程是慢指针的两倍,所以:(a+b)2=a+(b+c)k+b
化简可得:
a=(k-1)(b+c)+c
这个式子的意思是: 链表头到环入口的距离 = 相遇点到环入口的距离+(k-1)圈环长度。其中k>=1,所以k-1>=0圈。所以两个指针分别从链表头和相遇点出发,最后一定相遇于环入口。
思路:经典的快慢指针(龟兔赛跑问题),具体推理如上。
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
//龟兔赛跑,如果链表中环有n个结点,fast在链表上向前移动n步
//然后两个节点以相同的速度向前移动
//最终会在入口处相遇,即求出发点到入口的长度
if(pHead==NULL||pHead->next==NULL)
return NULL;
ListNode* fast=pHead;
ListNode* slow=pHead;
while(fast && fast->next)
{
fast=fast->next->next;
slow=slow->next;
//相遇
if(fast==slow)
{
fast=pHead;
break;
}
if(fast == NULL) return NULL;
}
while(fast!=slow)
{
fast=fast->next;
slow=slow->next;
}
return fast;
}
};
10.JZ56 删除链表中重复的结点
题目描述:
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
思路:要跳过重复节点,如果表头是重复节点的话,需要跳过表头,不方便存储新链表的表头。于是我们新建一个表头结点,在遇到新节点时,判断其后面节点是否与当前节点相等,相等就跳过,否则就添加到新链表的后面。
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
ListNode *result = new ListNode(0); //新建链表,result保存表头
ListNode *res = result; //待存储节点
while(pHead && pHead->next)
{
if(pHead->val == pHead->next->val)
{
while(pHead->next && pHead->val == pHead->next->val)
pHead = pHead->next;
}
else{
res->next = pHead;
res = res->next;
}
pHead = pHead->next;
}
res->next = pHead;
return result->next;
}
};