力扣-链表【一】
前言
今天开始在力扣上刷有关链表的题目,先从简单的开始~
具体题目
面试题 02.02. 返回倒数第 k 个节点
原问题描述: 戳这里.
解题思路:
快慢指针的问题~
快指针先走k步,然后快慢指针同时走,直到快指针指向最后一个结点时,慢指针所指内容即为倒数第k个节点。
解题代码:
class Solution {
public:
int kthToLast(ListNode* head, int k)
{
ListNode* l1 = head, *l2;
//快指针先走
for (int i = 0; i < k && l1 != NULL; i++)
{
l1 = l1->next;
}
//两个指针同时走
l2 = head;
while (l1!=NULL)
{
l1 = l1->next;
l2 = l2->next;
}
return l2->val;
}
};
AC结果:
剑指 Offer 22. 链表中倒数第k个节点
问题描述:戳这里.
解题思路:
和上一题目一样,只不过是最后需要返回l2而不是l2所指的值。
解题代码:
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode* l1 = head, *l2;
//快指针先走
for (int i = 0; i < k && l1 != NULL; i++)
{
l1 = l1->next;
}
//两个指针同时走
l2 = head;
while (l1!=NULL)
{
l1 = l1->next;
l2 = l2->next;
}
return l2;
}
};
AC结果:
面试题 02.03. 删除中间节点
问题描述:戳这里
解题思路:
题目说得不是很清楚,原来以为给定的参数是链表头,结果却是链表中要删除的节点。【看来程序员除了要会写代码还需要会看懂需求】理解完题意之后挺简单的,按照其他人的评论就是 我变成你之后再鲨了自己【bu shi】
解题代码:
class Solution {
public:
void deleteNode(ListNode* node) {
node->val = node->next->val;
node->next = node->next->next;
}
};
AC结果:
21.合并两个有序链表
问题描述:戳这里
解题思路:
这是一道比较简单的题目,有两种做法,一种是直接创建一条新的链表,然后不断的比较原来的旧链表,把值小的节点添加到新链表中,思路简单,但是需要用到多一点的内存。另一种方法是直接在原来的链表上进行修改【这一种不知道为什么我会超时】,参考了评论中使用递归进行合并(有点像归并排序)
解题代码:
代码参考题目评论,第一次意识到原来指针也可以使用swap。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* head = new ListNode(0);
ListNode* n = head;
while (l1 && l2)
{
if (l1->val < l2->val)
swap(l1, l2);
n->next = l1;
l1 = l1->next;
n = n->next;
}
n->next = l1 ? l1 : l2;
return head->next;
}
};
递归方法的思路也比较容易理解
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1==NULL)
return l2;
if(l2==NULL)
return l1;
if(l1->val < l2->val){
l1->next = mergeTwoLists(l1->next,l2);
return l1;
}else{
l2->next = mergeTwoLists(l1,l2->next);
return l2;
}
}
};
AC结果:
代码一:
代码二:
234.回文链表
问题描述:戳这里
解题思路:
这道题目的解法蛮多的,但有一些实现起来有点困难,也比较难理解。这里使用的是逆向建立一个新链表然后进行比较。时间复杂度还是O(n),空间复杂度也是O(n).
解题代码:
class Solution {
public:
bool isPalindrome(ListNode* head)
{
//反向建立链表
ListNode* l1 = head, *newprev = NULL;
while (l1)
{
ListNode *newone = new ListNode(l1->val, newprev);
newprev = newone;
l1 = l1->next;
}
//一起比较两个链表
l1 = head;
while (l1 && newprev)
{
if (l1->val != newprev->val)
{
return false;
}
l1 = l1->next;
newprev = newprev->next;
}
return true;
}
};
AC结果:
206. 反转链表
问题描述:戳这里
解题思路:
意外的发现可以直接套用上面的结果。
解题代码:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//反向建立链表
ListNode* l1 = head, *newprev = NULL;
while (l1)
{
ListNode *newone = new ListNode(l1->val, newprev);
newprev = newone;
l1 = l1->next;
}
return newprev;
}
};
AC结果:
剑指 Offer 18. 删除链表的节点
问题描述:戳这里
解题思路:
这道题目和之前不太一样的地方在于题目只给出了要删除节点的数值,因此需要遍历链表找到该节点后进行删除。遇到的问题有两个:①一开始我使用了上面题目的我鲨我自己的代码,同时加上了如果要删除的节点是最后一个的话,就将该节点置为NULL,结果发现测试样例是删除最后一个节点时会出错,即链表并没有真正的删除该节点,这是因为出现了虽然从代码上来看该节点已经置为NULL,但是内存并没有删除这一块,链表中的连接还保留着,因此输出的时候还会输出最后一个节点的值。②这里遍历的时候只遍历一次,默认只有一个节点需要删除,如果有多个相同值的节点要删除的话,需要从头遍历到尾。
解题代码:
class Solution {
public:
ListNode* deleteNode(ListNode* head, int val) {
ListNode* l1 = head,*l2=l1;
while (l1->val!=val)
{
l2 = l1;
l1 = l1->next;
}
if (l1->next != NULL)
{
l1->val = l1->next->val;
l1->next = l1->next->next;
}
else
{
l2->next = NULL;
}
return head;
}
};
AC结果:
876.链表的中间节点
问题描述:戳这里
解题思路:
使用快慢指针!
先从头出发,每一次慢指针走一步,快指针走两步,如果快指针已经指向NULL,则慢指针恰好指向链表的中间节点。
解题代码:
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode* slow,*quick;
slow = quick = head;
while (quick && quick->next!=NULL)
{
slow = slow->next;
quick = quick->next;
if (quick != NULL)
quick = quick->next;
}
return slow;
}
};
AC结果:
203.移除链表元素
问题描述:戳这里
解题思路:
依然还是我鲨我自己 ,需要注意一些小细节。
解题代码:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val)
{
ListNode* l1 = head, * l2 = l1;
while (l1!=NULL)
{
while (l1 && l1->val != val)
{
l2 = l1;
l1 = l1->next;
}
if (l1 && l1->val == val)
{
if (l1->next != NULL)
{
l1->val = l1->next->val;
l1->next = l1->next->next;
}
else if (l1 != head)
{
l2->next = NULL;
l1=NULL;
}
else
{
return NULL;
}
}
}
return head;
}
};
AC结果:
剑指 Offer 52. 两个链表的第一个公共节点
问题描述:戳这里
解题思路:
一开始我以为是找到两条链表中第一个相同的值,结果发现并不是,应该是两条链表最后一部分是一样的,借用题目的图:
这道题目想了蛮久的,找不到一个好的方法,看了评论直呼woc。官方的解答很好,大佬的评论很感人(bu shi)。
最后的解题思路是:使用两个指针,分别指向两条链表的头部,如果不相等,则依次向后走,当一个指针走完时,指向另一条链表的头部。再借用评论区一位大佬的话:
解题代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB)
{
if (headA == NULL || headB == NULL)
return NULL;
ListNode* l1 = headA;
ListNode* l2 = headB;
while (l1 != l2)
{
if (l1 == NULL)
l1 = headB;
else
{
l1 = l1->next;
}
if (l2 == NULL)
l2 = headA;
else
{
l2 = l2->next;
}
}
return l1;
}
};
AC结果:
1290.二进制链表的转换
问题描述:戳这里
解题思路:
这里可以简单的使用移位运算!
每次将当前的数值右移一位后加上链表的数值,直到遍历完整个链表。
需要注意符号的优先级顺序~
解题代码:
class Solution {
public:
int getDecimalValue(ListNode* head)
{
int n = 0;
while (head)
{
n =(n<<1)+head->val;
head = head->next;
}
return n;
}
};
AC结果:
83.删除排序链表中的重复元素
问题描述:戳这里
解题思路:
继续我鲨我自己的删除节点法
解题代码:
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head)
{
if (head == NULL)
return NULL;
int help = head->val;
ListNode* l1 = head->next;
ListNode* l2 = head;
while (l1)
{
if (l1->val == help)
{
if (l1->next != NULL)
{
l1->val = l1->next->val;
l1->next = l1->next->next;
}
else
{
l1 = NULL;
l2->next = l1;
}
}
else
{
help = l1->val;
l2 = l1;
l1=l1->next;
}
}
return head;
}
};
AC结果:
141.环形链表
问题描述:戳这里
解题思路:
这里使用了快慢指针,如果有环,快指针总能更快的进入到环中,慢指针慢一步进入环,只要在环内,两个指针终能相遇。
解题代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode* head)
{
if (head == NULL)
return NULL;
ListNode* quick = head;
ListNode* slow = head;
while (quick && slow)
{
quick = quick->next;
if (quick != NULL)
{
quick = quick->next;
slow = slow->next;
}
if (slow == quick)
return true;
}
return false;
}
};
AC结果:
剑指 Offer 06. 从尾到头打印链表
问题描述:戳这里
解题思路:
可以借助algorithm库中的reverse来对vector容器进行逆转。
解题代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
vector<int> reversePrint(ListNode* head) {
vector<int>res;
while (head)
{
res.push_back(head->val);
head = head->next;
}
reverse(res.begin(),res.end());
return res;
}
};
AC结果:
面试题 02.01. 移除重复节点
问题描述:戳这里
解题思路:
借助一个缓冲区visit来记录当前节点的值是否有出现过,如果有出现 继续采用我鲨我自己的删除结点法,如果没有,继续遍历链表。
解题代码:
class Solution {
public:
ListNode* removeDuplicateNodes(ListNode* head) {
int visit[20005]={ 0 };
ListNode* l1 = head,*l2 = l1;
while (l1!=NULL)
{
//有出现 我鲨我自己
if (visit[l1->val]==1)
{
if (l1->next == NULL)
{
l2->next = NULL;
break;
}
else
{
l1->val = l1->next->val;
l1->next = l1->next->next;
}
}
else if(visit[l1->val]==0)
{
visit[l1->val] = 1;
l2 = l1;
l1 = l1->next;
}
}
return head;
}
};
AC结果:
总结
这篇博客中的题目都是力扣中有关链表tag的简单题目。其实写了这么多,感觉很多题目的解法都可以归为两种:快慢指针和删除结点。通过刷题我学会了删除结点中一种比较简便的操作【但是这种操作会有野指针出现,但是在代码里不影响】
明天开始要刷中等难度的题目,看看中等难度的题目是不是也可以用这两种方法来解决。