JZ 链表
JZ-3 从尾到头打印链表
题目描述
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
方法1:使用std::reverse()函数
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> ret;
while(head){
ret.push_back(head->val);
head = head->next;
}
std::reverse(ret.begin(),ret.end());
return ret;
}
};
方法2:递归版本
我们可以回顾一下二叉树的递归遍历方法:
void dfs (TreeNode* root) {
if (!root) return;
dfs(root->left);
dfs(root->right);
// 处理头结点
}
以此借鉴,得到我们的代码:
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> ret;
if(!head) return ret;
ret = printListFromTailToHead(head->next);
ret.push_back(head->val);
return ret;
}
};
方法3:反转链表
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
ListNode* pre = nullptr;
ListNode* cur = head;
ListNode* temp = cur;
while(cur){ // 1 翻转链表
temp = cur->next;//需要保存一下现场
cur->next = pre;
pre = cur; //pre和cur分别向右平移
cur = temp;
}
vector<int> ret;
while(pre){ //2 遍历翻转后的链表,并存储到vec中。
ret.push_back(pre->val);
pre = pre->next;
}
return ret;
}
};
JZ-14 链表中倒数第k个结点
严格的O(n)解法,相差k的快慢指针
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
// 1 指针不为空 || k <= 0
if(!pListHead || k <= 0) return nullptr;
auto slow = pListHead,fast = pListHead;
while(k--){ //2 快指针先前进k个
if(fast)
fast = fast->next;
else return nullptr;
}
while(fast){ //3 快慢指针一同前进,直到快指针到达终点
slow = slow->next;
fast = fast->next;
}
return slow;
}
};
JZ-15 反转链表
题目描述
输入一个链表,反转链表后,输出新链表的表头。
方法1:标准翻转链表方法
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode* nex = nullptr;
ListNode* pre = nullptr;
ListNode* cur = pHead;
while (cur){
nex = cur->next;
cur -> next = pre;
pre = cur;
cur = nex;
}
return pre;
}
};
方法2 :重新构造链表
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if (!pHead) return nullptr;
vector<ListNode*> v;
while (pHead) {
v.push_back(pHead);
pHead = pHead->next;
}
reverse(v.begin(), v.end()); // 反转vector,也可以逆向遍历
ListNode *head = v[0];
ListNode *cur = head;
for (int i=1; i<v.size(); ++i) { // 构造链表
cur->next = v[i]; // 当前节点的下一个指针指向下一个节点
cur = cur->next; // 当前节点后移
}
cur->next = nullptr; // 切记最后一个节点的下一个指针指向nullptr
return head;
}
};
JZ-16 合并两个排序的链表
迭代法
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
ListNode *vhead = new ListNode(-1);
ListNode *cur = vhead;
while(pHead1 && pHead2){
if(pHead1->val <= pHead2->val){
cur->next = pHead1;
pHead1 = pHead1->next;
}
else{
cur->next = pHead2;
pHead2 = pHead2->next;
}
cur = cur->next;
}
cur ->next = pHead1? pHead1:pHead2;
return vhead->next;
}
};
方法2:递归法
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(pHead1 == nullptr) return pHead2;
if(pHead2 == nullptr) return pHead1;
if(pHead1->val <= pHead2->val){
pHead1->next = Merge(pHead1->next,pHead2);
return pHead1;
}
else{
pHead2->next = Merge(pHead1,pHead2->next);
return pHead2;
}
}
};
JZ-25 复杂链表的复制
题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
方法1:辅佐退位法(待调试)
/*
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 == nullptr) return pHead;
RandomListNode* currentNode = pHead;
//1 复制每一个结点,如复制结点A得到A1,将结点A1插到结点A的后面
while(currentNode != nullptr){
RandomListNode* cloneNode = new RandomListNode(currentNode->label);
cloneNode->label = currentNode->label;
cloneNode->next = currentNode->next;
cloneNode->random = currentNode->random;
RandomListNode* nextNode = currentNode->next;
currentNode->next = cloneNode;
cloneNode->next = nextNode;
currentNode = nextNode;
}
currentNode = pHead;
//2 重新遍历链表,复制老结点的随机指针给新结点,例如 A1.random = A.random.next;
while(currentNode != nullptr){
currentNode->next->random = currentNode->random==nullptr? nullptr:currentNode->random->next;
currentNode = currentNode->next->next;
}
//3 拆分链表,将链表分为原链表和复制后的链表
currentNode = pHead;
RandomListNode* pCloneHead = pHead->next;
while(currentNode != nullptr){
RandomListNode* cloneNode = currentNode->next;
currentNode->next = cloneNode->next;
cloneNode->next = cloneNode->next==nullptr?nullptr:cloneNode->next->next;
currentNode = currentNode->next;
}
return pCloneHead;
}
};
方法2:哈希法–>map映射
/*
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 == nullptr)
return pHead;
// map<label, 位置>
map<int, int> imap;
int count = 0;
auto pin2 = pHead;
// 初始化
RandomListNode* head = new RandomListNode(pHead->label);
auto pin1 = head;
// pair是将2个数据组合成一个数据,当需要这样的需求时就可以使用pair,
// 如stl中的map就是将key和value放在一起来保存。
// 另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。
// pair的实现是一个结构体,主要的两个成员变量是first second
// 因为是使用struct不是class,所以可以直接使用pair的成员变量。
imap.insert(make_pair(pin2->label, count++));
while (pin2->next != nullptr){
// 插入点
auto temp = new RandomListNode(pin2->next->label);
pin1->next = temp;
pin1 = pin1->next;
pin2 = pin2->next;
// 记录值
imap.insert(make_pair(pin2->label, count++));
}
// 此时所有节点新建完成
pin1 = head;
pin2 = pHead;
while(pin2 != nullptr){
if (pin2->random != nullptr){
auto pin = head;
auto target = pin2->random->label;
auto n = imap.find(target)->second;
for (int i = n; i != 0; --i)
pin = pin->next;
// pin此时所指是pin2的random节点
pin1->random = pin;
}
pin1 = pin1->next;
pin2 = pin2->next;
}
return head;
}
};
JZ-26 二叉搜索树与双向链表
题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
二叉搜索树介绍:对于树中的每个节点X,它的左子树中所有关键字值小于X的关键字值,而它的右子树中所有关键字值大于X的关键字值。
中序遍历+递归
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree)
{
if(!pRootOfTree) return NULL;
auto pairs = dfs(pRootOfTree);
return pairs.first;
}
//定义一个pair类型的函数,pair里面是两个树的结点
pair<TreeNode*,TreeNode*> dfs(TreeNode* root){
if(!root->left && !root->right) return{root,root};
if(root->left && root->right){
auto lsides = dfs(root->left),rsides = dfs(root->right);
//双向链表的修改
//左边的返回 {1,2} 2->root root->2
lsides.second->right= root;
root->left = lsides.second;
//右边的返回 {1,2} 1->root root->1
rsides.first -> left = root;
root -> right = rsides.first;
return {lsides.first,rsides.second};
}
if(root->left){
auto lsides = dfs(root->left);
//双向链表的修改
//左边的返回 {1,2} 2->root root->2
lsides.second->right= root;
root->left = lsides.second;
return {lsides.first,root};
}
if(root->left && root->right){
auto rsides = dfs(root->right);
//双向链表的修改
//右边的返回 {1,2} 1->root root->1
rsides.first -> left = root;
root -> right = rsides.first;
return {root,rsides.second};
}
}
};
中序遍历+非递归
思路:大家普遍的解法是中序遍历+递归,这种方法主要抓住了二叉搜索树的中序遍历结果是有序的特点!
但这里提供一种非递归的方法,这种方法借鉴于平衡二叉树的单旋转方法,目标是把这个二叉搜索树变成一个简易二叉搜索树(这里的简易二叉树是我杜撰的概念,它指根节点的左边没有右子树,根节点的右边没有左子树)。图示说明如下,对于每个二叉搜索树(例如图左),如果通过一定方法将它变为简易二叉搜索树(例如图中),那么自然也就是有序的双向链表(图右)。
下面说说怎么将普通二叉搜索树变为简易二叉搜索树,这里借鉴了平衡二叉树中的单旋转方法。举个例子,对于根节点10来说,在它左边的数显然应该是8,右边的数是12。那么8和12分别具有什么样的特点呢?观察可以看出,8是根节点10的左子树的最右边的节点,而12是根节点10的右子树的最左边的结点。那么现在如果我令10的左指针指向8,右指针指向12,再让剩余的左子树和右子树的部分续接在8和12两个节点上。就可以实现节点8,10,12的有序性,以此方法往下就可以得到一个简易二叉搜索树。具体的实现方式见代码
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree)
{
if (pRootOfTree == nullptr) return nullptr;
TreeNode* pRoot = pRootOfTree;
// 先处理pRootOfTree的右子树
while (pRootOfTree->right)
{
TreeNode* p_right = pRootOfTree->right;
if (p_right->left == nullptr)
{
p_right->left = pRootOfTree;
pRootOfTree = p_right;
}
else
{
TreeNode* p_3 = p_right;
TreeNode* p_4 = p_3->left;
while (p_4->left)
{
p_3 = p_4;
p_4 = p_4->left;
}
p_3->left = nullptr;
pRootOfTree->right = p_4;
p_4->left = pRootOfTree;
while (p_4->right)
p_4 = p_4->right;
p_4->right = p_right;
pRootOfTree = pRootOfTree->right;
}
}
// 处理pRootOfTree的左子树
while (pRoot->left)
{
TreeNode* p_left = pRoot->left;
if (p_left->right == nullptr)
{
p_left->right = pRoot;
pRoot=p_left;
}
else
{
TreeNode* p_1 = pRoot->left;
TreeNode* p_2 = p_1->right;
while (p_2->right)
{
p_1 = p_2;
p_2 = p_2->right;
}
p_1->right = NULL;
pRoot->left = p_2;
p_2->right = pRoot;
while (p_2->left)
p_2 = p_2->left;
p_2->left = p_left;
pRoot = pRoot->left;
}
}
//pRoot此时就是头节点
return pRoot;
}
};
JZ-36 两个链表的第一个公共结点
题目描述
输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
题目抽象
给定两个单链表A,B,假设一定含有公共结点,返回第一个公共结点的指针。
方法:双指针法
假如例子如下:
显然第一个公共结点为8,但是链表A头结点到8的长度为2,链表B头结点到8的长度为3,显然不好办?
如果我们能够制造一种理想情况,如下:
这里先假设链表A头结点与结点8的长度 与 链表B头结点与结点8的长度相等,那么就可以用双指针。
1 初始化:指针ta指向链表A头结点,指针tb指向链表B头结点
2 如果ta == tb, 说明找到了第一个公共的头结点,直接返回即可。
3 否则,ta != tb,则++ta,++tb
所以现在的问题就变成,如何让本来长度不相等的变为相等的?
假设链表A长度为a, 链表B的长度为b,此时a != b
但是,a+b == b+a
因此,可以让a+b作为链表A的新长度,b+a作为链表B的新长度。
如图:
这样,长度就一致了,可以用上述的双指针解法了。
// 刀刀喜欢用auto
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
auto p = pHead1, q = pHead2;
while (p != q) {
//if(p) p=p->next;
//else p = pHead2;
p=p? p->next : pHead2;
q=q? q->next : pHead1;
}
return q;
}
};
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
ListNode *ta = pHead1, *tb = pHead2;
while (ta != tb) {
ta = ta ? ta->next : pHead2;
tb = tb ? tb->next : pHead1;
}
return ta;
}
};
JZ-46 孩子们的游戏(圆圈中最后剩下的数)
题目描述
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1
方法1:模拟
时间复杂度:O(N^2), 每次删除一个节点,需要先找到那个节点,然后再删除,查找的时间复杂度为O(N)
空间复杂度:O(N)
class Solution {
public:
int LastRemaining_Solution(int n, int m)
{
if (n <= 0) return -1;
list<int> lt;
for (int i=0; i<n; ++i)
lt.push_back(i);
int index = 0;
while (n > 1) {
index = (index + m - 1) % n;//寻找index所在的位置
auto it = lt.begin();
std::advance(it, index); // 让it向后移动index个位置
lt.erase(it);//删除该元素
--n;//修改链表长度
}
return lt.back();
}
};
方法2:递归
class Solution {
public:
//
// f(N,M) = ( f(N−1,M) + M ) % N
int LastRemaining_Solution(int n, int m)
{
if(n <= 0 || m <= 0) return -1;
if(n == 1) return 0;
return (LastRemaining_Solution(n-1,m)+m)%n;
}
};
方法3:for循环
根据方法二可知,
f[1] = 0
f[2] = (f{1] + m) % 2
f[3] = (f[2] + m) % 3
…
f[n] = (f[n-1] + m) % n
class Solution {
public:
// f(N,M) = ( f(N−1,M) + M ) % N
int LastRemaining_Solution(int n, int m)
{
if(n <= 0 || m <= 0) return -1;
int index = 0;
for(int i = 2;i <= n;++i)
index = (index + m)%i;
return index;
}
};
JZ-55 链表中环的入口结点
题目抽象
给定一个单链表,如果有环,返回环的入口结点,否则,返回nullptr
方法一:哈希法
1 遍历单链表的每个结点
2 如果当前结点地址没有出现在set中,则存入set中
3 否则,出现在set中,则当前结点就是环的入口结点
4 整个单链表遍历完,若没出现在set中,则不存在环
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
unordered_set<ListNode*> st;
while(pHead){ // 1 遍历单链表的每个结点
// 2 如果当前结点地址没有出现在set中,则存入set中
if(st.find(pHead) == st.end()){
st.insert(pHead);
pHead = pHead->next;
}
// 3 否则,出现在set中,则当前结点就是环的入口结点
else return pHead;
}
// 4 整个单链表遍历完,若没出现在set中,则不存在环
return nullptr;
}
};
方法2:双指针法
时间复杂度:O(n)
空间复杂度:O(1)
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
//1 初始化:快指针fast指向头结点,慢指针slow指向头结点
ListNode *fast = pHead;
ListNode *slow = pHead;
//2 让fast一次走两步,slow一次走一步,第一次相遇在C处,停止
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
if(fast == slow) break;
}
//3 然后让fast指向头结点,slow原地不动;
// 让后fast,slow每次走一步,当再次相遇,就是入口结点。
if(!fast || !fast->next) return nullptr;
fast = pHead;
while(fast != slow){
fast = fast->next;
slow = slow->next;
}
return fast;
}
};
JZ-56 删除链表中重复的结点
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
在遍历单链表的时候,检查当前节点与下一点是否为相同值,如果相同,继续查找祥同值的最大长度,然后指针改变指向。
直接删除法
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
ListNode *vhead = new ListNode(-1);
vhead -> next = pHead;
ListNode *pre = vhead,*cur = pHead;
while(cur){
if(cur -> next && cur->val == cur->next->val){
cur = cur -> next;
while(cur -> next && cur -> val == cur->next->val)
cur = cur -> next;
cur = cur->next;
pre->next = cur;
}
else{
pre = cur;
cur = cur -> next;
}
}
return vhead -> next;
}
};