Leetcode_ 入门_链表
1、找出两个链表的交点(160、Easy)
1)题目要求
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在节点 c1 开始相交。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。
注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存
2)我的解法
c++
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
ListNode*current1=headA;
int tag=0;
while(current1!=NULL)
{
ListNode*current2=headB;
while(current2!=NULL)
{
if(current1==current2)return current1;
else current2=current2->next;
}
current1=current1->next;
}
return NULL;
}
};
3)其他解法
(1) 哈希表法
遍历链表 A 并将每个结点的地址/引用存储在哈希表中。然后检查链表 B 中的每一个结点 bi ,是否在哈希表中。若在,则 bi为相交结点。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
std::unordered_set<ListNode*> set;
ListNode* cur_a = headA;
while (cur_a)
{
set.insert(cur_a);
cur_a = cur_a->next;
}
ListNode* cur_b = headB;
while (cur_b)
{
if(set.find(cur_b) != set.end()) //找到了
{
return cur_b;
}
cur_b = cur_b->next;
}
return nullptr;
}
};
时间复杂度 : O(m+n)。
空间复杂度 : O(m)或 O(n)。
作者:eric-345
链接:link
来源:力扣(LeetCode)
(2)双指针法
A的指针遍历完A 接着从headB开始遍历
B的指针遍历完B 接着从headA开始遍历
两个指针都最多走m + n + 1步。
当两个指针同时为空时,表示不相交;当两个都非空且相等时,表示相交
时间复杂度O(m + n) 空间复杂度O(1)
动画展示:
link
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA == nullptr || headB == nullptr)
return nullptr;
ListNode* cur_a = headA;
ListNode* cur_b = headB;
while(cur_a != cur_b)
{
cur_a = (cur_a == nullptr ? headB : cur_a->next);
cur_b = (cur_b == nullptr ? headA : cur_b->next);
}
return cur_a;
}
};
作者:eric-345
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
ListNode*cur1=headA;
ListNode*cur2=headB;
if(!headA||!headB)return NULL;
while(cur1!=cur2)
{
if(cur1)cur1=cur1->next;
else cur1=headB;
if(cur2)cur2=cur2->next;
else cur2=headA;
}
return cur1;//若不相交,cur1就是null
}
5)学到的东西
哈希表知识: std::unordered_set<ListNode*> set;
双指针
思想:两个指针走的路程相等,a+c+b=b+c+a
2、链表反转(206、Easy)
1)题目要求
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
2)我的解法
c++
(1)迭代
class Solution {
public:
ListNode* reverseList(ListNode* head)
{
if(head==NULL)return head;
ListNode*current=head;
while(current!=NULL&¤t->next!=NULL)
{
current=current->next;
}
ListNode*head1=current;
while(current!=head)
{
ListNode*p=head;
while(p!=NULL&&p->next!=NULL)
{
if(p->next==current){current->next=p;current=current->next;break;}
else p=p->next;
}
}
current->next=NULL;
return head1;
}
};
时间复杂度:O(n^2)
空间复杂度:O(1)
(2)递归
class Solution {
public:
void findpre(ListNode* current,ListNode* head)
{
if(current==head){current->next=NULL;return;}
ListNode*p=head;
while(p->next!=NULL&&p->next!=current)
{
p=p->next;
}
current->next=p;
findpre(p,head);
}
ListNode* reverseList(ListNode* head)
{
if(head==NULL)return head;
ListNode*current=head;
while(current!=NULL&¤t->next!=NULL)
{
current=current->next;
}
ListNode*head1=current;
findpre(head1,head);
return head1;
}
};
时间复杂度:O(n^2)
空间复杂度:O(1)
3)其他解法
(1)神奇解法
C++
// Time 8ms, 99%
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *p;
for(p=NULL; head; swap(head,p))
swap(p,head->next);
return p;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
作者:Joy
来源:Leetcode评论区
(2)双指针迭代
java
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode nextTemp = curr.next;
curr.next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
时间复杂度:O(n)
空间复杂度:O(1)
作者:LeetCode
链接:link
来源:力扣(LeetCode)
(3)递归
java
使用递归函数,一直递归到链表的最后一个结点,该结点就是反转后的头结点,记作 ret
此后,每次函数在返回的过程中,让当前结点的下一个结点的 next 指针指向当前节点。
同时让当前结点的 next指针指向 NULL,从而实现从链表尾部开始的局部反转
当递归函数全部出栈后,链表反转完成。
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode p = reverseList(head.next);
head.next.next = head;
head.next = null;
return p;
}
时间复杂度:O(n)
空间复杂度:O(n)
作者:LeetCode
链接:link
来源:力扣(LeetCode)
(4)另一种双指针
java
原链表的头结点就是反转之后链表的尾结点,使用 head 标记 .
定义指针 cur,初始化为 head.
每次都让 head 下一个结点的 next指向 cur ,实现一次局部反转
局部反转完成之后,cur和 head的 next 指针同时 往前移动一个位置
循环上述过程,直至 cur到达链表的最后一个结点 .
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL) { return NULL; }
ListNode* cur = head;
while (head->next != NULL) {
ListNode* t = head->next->next;
head->next->next = cur;
cur = head->next;
head->next = t;
}
return cur;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
作者:huwt
链接: link
来源:力扣(LeetCode)
4)自己的优化代码
c++
ListNode* reverseList(ListNode* head)
{
if(head==NULL)return head;
ListNode*current=head;
ListNode*pre=NULL;//重点
while(current!=NULL)//这里用current->next的话,while后面还得再加一行,代码会比较啰嗦
{
ListNode*NextItem=current->next;
current->next=pre;
pre=current;
current=NextItem;
}
return pre;
}
时间复杂度:O(n)
空间复杂度:O(1)
5)学到的东西
递归思想
双指针算法
局部一点点变换
swap函数的应用
3、删除链表的倒数第 n 个节点(19、Medium)
1)题目要求
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
2)我的解法
c++
(1)暴力
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
ListNode*current=head;
if(head==NULL)return NULL;
int i=1;
while(current->next!=NULL)
{
current=current->next;
i++;
}
current=head;
if(i-n==0){head=current->next;return head;}
int j=1;
while(j<i-n)
{
current=current->next;
j++;
}
current->next=current->next->next;
return head;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
(2)反转链表
先反转链表,再删除元素,再反转链表。
ListNode* removeNthFromEnd(ListNode* head, int n)
{
if(head==NULL)return head;
ListNode*current=head;
ListNode*pre=NULL;//重点
while(current!=NULL)
{
ListNode*NextItem=current->next;
current->next=pre;
pre=current;
current=NextItem;
}
ListNode*head1=pre;
if(n-1==0)head1=head1->next;
else{
int i=1;
current=pre;
while(i<n-1)
{
current=current->next;
i++;
}
current->next=current->next->next;
}
current=head1;
pre=NULL;//重点
while(current!=NULL)
{
ListNode*NextItem=current->next;
current->next=pre;
pre=current;
current=NextItem;
}
return pre;
}
时间复杂度:O(n)
空间复杂度:O(1)
3)其他解法
(1)双指针
上述算法可以优化为只使用一次遍历。我们可以使用两个指针而不是一个指针。第一个指针从列表的开头向前移动 n+1步,而第二个指针将从列表的开头出发。现在,这两个指针被 个结点分开。我们通过同时移动两个指针向前来保持这个恒定的间隔,直到第一个指针到达最后一个结点。此时第二个指针将指向从最后一个结点数起的第 n 个结点。我们重新链接第二个指针所引用的结点的 next 指针指向该结点的下下个结点。
java
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode first = dummy;
ListNode second = dummy;
// Advances first pointer so that the gap between first and second is n nodes apart
for (int i = 1; i <= n + 1; i++) {
first = first.next;
}
// Move first to the end, maintaining the gap
while (first != null) {
first = first.next;
second = second.next;
}
second.next = second.next.next;
return dummy.next;
}
作者:LeetCode
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
if(head==NULL)return head;
ListNode*cur1=head;
ListNode*cur2=head;
int i=0;
while(i<n&&cur2)
{
cur2=cur2->next;
i++;
}
while(cur2&&cur2->next)
{
cur1=cur1->next;
cur2=cur2->next;
}
if(cur2)cur1->next=cur1->next->next;
else head=cur1->next;
return head;
}
};
5)学到的东西
双指针算法
思想:保持一定的间隔,当后一个指针到最后一个时,前一个便会刚好到要删结点之前那个结点
4、归并两个有序的链表(21、Easy)
1)题目要求
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
2)我的解法
c++
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
if(!l1)return l2;
if(!l2)return l1;
ListNode*cur1=nullptr;
ListNode*cur2=nullptr;
ListNode*p=nullptr;
if(l1->val<=l2->val)
{
cur1=l1;
p=l1;
cur2=l2;
}
else
{
cur1=l2;
p=l2;
cur2=l1;
}
ListNode*temp=nullptr;
while(cur1&&cur2)
{
if(cur2->val>=cur1->val)
{
if(!cur1->next||cur2->val<=cur1->next->val)
{
temp=cur2->next;
cur2->next=cur1->next;
cur1->next=cur2;
cur1=cur1->next;
cur2=temp;
}
else{cur1=cur1->next;}
}
else{cur2=cur2->next;}
}
return p;
}
3)其他解法
方法一:递归
思路
我们可以如下递归地定义两个链表里的 merge 操作(忽略边界情况,比如空链表等):
\left{ \begin{array}{ll} list1[0] + merge(list1[1:], list2) & list1[0] < list2[0] \ list2[0] + merge(list1, list2[1:]) & otherwise \end{array} \right.
{
list1[0]+merge(list1[1:],list2)
list2[0]+merge(list1,list2[1:])
list1[0]<list2[0]
otherwise
也就是说,两个链表头部值较小的一个节点与剩下元素的 merge 操作结果合并。
算法
我们直接将以上递归过程建模,同时需要考虑边界情况。
如果 l1 或者 l2 一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。如果两个链表有一个为空,递归结束。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == nullptr) {
return l2;
} else if (l2 == nullptr) {
return l1;
} else if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
} else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
};
作者:LeetCode-Solution
链接: link
来源:力扣(LeetCode)
方法二:迭代
思路
我们可以用迭代的方法来实现上述算法。当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。
算法
首先,我们设定一个哨兵节点 prehead ,这可以在最后让我们比较容易地返回合并后的链表。我们维护一个 prev 指针,我们需要做的是调整它的 next 指针。然后,我们重复以下过程,直到 l1 或者 l2 指向了 null :如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 prev 节点的后面同时将 l1 指针往后移一位。否则,我们对 l2 做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把 prev 向后移一位。
在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* preHead = new ListNode(-1);
ListNode* prev = preHead;
while (l1 != nullptr && l2 != nullptr) {
if (l1->val < l2->val) {
prev->next = l1;
l1 = l1->next;
} else {
prev->next = l2;
l2 = l2->next;
}
prev = prev->next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev->next = l1 == nullptr ? l2 : l1;
return preHead->next;
}
};
作者:LeetCode-Solution
链接: link
来源:力扣(LeetCode)
4)自己的优化代码
c++
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
ListNode*p=new ListNode(-1);
ListNode*head=p;
while(l1||l2)
{
if(!l2||(l1&&l2&&l1->val<=l2->val)){p->next=l1;l1=l1->next;}
else if(!l1||(l1&&l2&&l1->val>l2->val)){p->next=l2;l2=l2->next;}
p=p->next;
}
return head->next;
}
5)学到的东西
思想:链表的结点可以一个个连接,这样比插入结点快。
5、从有序链表中删除重复节点(83、Easy)
1)题目要求
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
2)我的解法
c++
ListNode* deleteDuplicates(ListNode* head)
{
ListNode*cur=head;
while(cur&&cur->next)
{
if(cur->val==cur->next->val)
{
cur->next=cur->next->next;
}
else cur=cur->next;
}
return head;
}
3)其他解法
递归套路解决链表问题:
找终止条件:当head指向链表只剩一个元素的时候,自然是不可能重复的,因此return
想想应该返回什么值:应该返回的自然是已经去重的链表的头节点
每一步要做什么:宏观上考虑,此时head.next已经指向一个去重的链表了,而根据第二步,我应该返回一个去重的链表的头节点。因此这一步应该做的是判断当前的head和head.next是否相等,如果相等则说明重了,返回head.next,否则返回head
java
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head == null || head.next == null){
return head;
}
head.next = deleteDuplicates(head.next);
if(head.val == head.next.val) head = head.next;
return head;
}
}
作者:mata川
来源:力扣(LeetCode)评论区
4)自己的优化代码
c++
ListNode* deleteDuplicates(ListNode* head)
{
ListNode*cur=head;
while(cur&&cur->next)
{
if(cur->val==cur->next->val)
{
cur->next=cur->next->next;
}
else cur=cur->next;
}
return head;
}
5)学到的东西
链表的递归思想
6、回文链表(234、Easy)
1)题目要求
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
2)我的解法
c++
bool isPalindrome(ListNode* head)
{
ListNode*cur=head;
stack<int> s;
if(!head||!head->next)return true;
s.push(cur->val);cur=cur->next;
while(cur)
{
if(s.empty())s.push(cur->val);
else if(cur->val==s.top())s.pop();
else if(cur->next&&s.top()==cur->next->val){s.pop();cur=cur->next->next;continue;}
else s.push(cur->val);
cur=cur->next;
}
if(s.empty())return true;
else return false;
}
3)其他解法
(1)将值复制到数组中后用双指针法
算法:
我们可以分为两个步骤:
复制链表值到数组列表中。
使用双指针法判断是否为回文。
第一步,我们需要遍历链表将值复制到数组列表中。我们用 currentNode 指向当前节点。每次迭代向数组添加 currentNode.val,并更新 currentNode = currentNode.next,当 currentNode = null 则停止循环。
class Solution {
public boolean isPalindrome(ListNode head) {
List<Integer> vals = new ArrayList<>();
// Convert LinkedList into ArrayList.
ListNode currentNode = head;
while (currentNode != null) {
vals.add(currentNode.val);
currentNode = currentNode.next;
}
// Use two-pointer technique to check for palindrome.
int front = 0;
int back = vals.size() - 1;
while (front < back) {
// Note that we must use ! .equals instead of !=
// because we are comparing Integer, not int.
if (!vals.get(front).equals(vals.get(back))) {
return false;
}
front++;
back--;
}
return true;
}
}
作者:LeetCode
链接: link
来源:力扣(LeetCode)
(2)用快慢指针遍历的同时翻转前半部分,然后与后半部分比较即可。
我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。
或者可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针到链表的中间。通过慢指针将链表分为两部分。
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(!head || !head->next)
return 1;
ListNode *fast = head, *slow = head;
ListNode *p, *pre = NULL;
while(fast && fast->next){
p = slow;
slow = slow->next; //快慢遍历
fast = fast->next->next;
p->next = pre; //翻转
pre = p;
}
if(fast) //奇数个节点时跳过中间节点
slow = slow->next;
while(p){ //前半部分和后半部分比较
if(p->val != slow->val)
return 0;
p = p->next;
slow = slow->next;
}
return 1;
}
};
作者:zed-65536
链接:link
来源:力扣(LeetCode)
(3)将所有节点值入栈,然后一一出栈并比较
class Solution {
public:
bool isPalindrome(ListNode* head) {
stack<int> s;
ListNode *p = head;
while(p){
s.push(p->val);
p = p->next;
}
p = head;
while(p){
if(p->val != s.top()){
return 0;
}
s.pop();
p = p->next;
}
return 1;
}
};
作者:zed-65536
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
bool isPalindrome(ListNode* head)
{
ListNode*cur=head;
stack<int> s;
while(cur){s.push(cur->val);cur=cur->next;}
cur=head;
while(cur)
{
if(cur->val!=s.top())return false;
else {s.pop();cur=cur->next;}
}
return true;
}
5)学到的东西
回文字符串特性:从前往后和从后往前是完全相同的
思想:快慢指针,快指针到最后时,慢指针到中间
链表也可以进行分割操作
7、交换链表中的相邻结点(24、Medium)
1)题目要求
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
2)我的解法
c++
class Solution {
public:
ListNode*Swap(ListNode* fir)
{
if(!fir||!fir->next)return fir;
ListNode*sec=fir->next;
ListNode*temp=sec->next;
sec->next=fir;
fir->next=Swap(temp);
return sec;
}
ListNode* swapPairs(ListNode* head)
{
return Swap(head);
}
};
第一次纯靠自己写出双100%的解法,而且还是一次通过,我觉得值得纪念一下,嘿嘿。
3)其他解法
方法一:递归
这个题目要求我们从第一个节点开始两两交换链表中的节点,且要真正的交换节点。
算法:
从链表的头节点 head 开始递归。
每次递归都负责交换一对节点。由 firstNode 和 secondNode 表示要交换的两个节点。
下一次递归则是传递的是下一对需要交换的节点。若链表中还有节点,则继续递归。
交换了两个节点以后,返回 secondNode,因为它是交换后的新头。
在所有节点交换完成以后,我们返回交换后的头,实际上是原始链表的第二个节点。
java
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
// If the list has no node or has only one node left.
if ((head == null) || (head.next == null)) {
return head;
}
// Nodes to be swapped
ListNode firstNode = head;
ListNode secondNode = head.next;
// Swapping
firstNode.next = swapPairs(secondNode.next);
secondNode.next = firstNode;
// Now the head is the second node
return secondNode;
}
}
时间复杂度:O(N),其中 N指的是链表的节点数量。
空间复杂度:O(N),递归过程使用的堆栈空间。
作者:LeetCode
链接:link
来源:力扣(LeetCode)
(2)方法二:迭代
我们把链表分为两部分,即奇数节点为一部分,偶数节点为一部分,A 指的是交换节点中的前面的节点,B 指的是要交换节点中的后面的节点。在完成它们的交换,我们还得用 prevNode 记录 A 的前驱节点。
算法:
firstNode(即 A) 和 secondNode(即 B) 分别遍历偶数节点和奇数节点,即两步看作一步。
交换两个节点:
firstNode.next = secondNode.next
secondNode.next = firstNode
还需要更新 prevNode.next 指向交换后的头。
prevNode.next = secondNode
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode swapPairs(ListNode head) {
// Dummy node acts as the prevNode for the head node
// of the list and hence stores pointer to the head node.
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode prevNode = dummy;
while ((head != null) && (head.next != null)) {
// Nodes to be swapped
ListNode firstNode = head;
ListNode secondNode = head.next;
// Swapping
prevNode.next = secondNode;
firstNode.next = secondNode.next;
secondNode.next = firstNode;
// Reinitializing the head and prevNode for next swap
prevNode = firstNode;
head = firstNode.next; // jump
}
// Return the new head node.
return dummy.next;
}
}
作者:LeetCode
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
ListNode* swapPairs(ListNode* head)
{
if(!head||!head->next)return head;
ListNode*sec=head->next;
ListNode*temp=sec->next;
sec->next=head;
head->next=swapPairs(temp);
return sec;
}
5)学到的东西
递归思想
迭代时可利用多个指针,不一定非得2个
8、链表求和(445、Medium)
1)题目要求
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
2)我的解法
c++
利用栈处理
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
{
stack<int> s,s1,s2;
ListNode*l=new ListNode(0);
ListNode* head=l;
ListNode*newnode=NULL;
while(l1)
{
s1.push(l1->val);
l1=l1->next;
}
while(l2)
{
s2.push(l2->val);
l2=l2->next;
}
int Jinwei=0;//进位
int x=0;
while(!s1.empty()||!s2.empty()||Jinwei)
{
x=0;
if(Jinwei==1){x++;Jinwei=0;}
if(!s1.empty()&&!s2.empty()){x+=s1.top()+s2.top();s1.pop();s2.pop();}
else if(s1.empty()&&!s2.empty()){x+=s2.top();s2.pop();}
else if(!s1.empty()&&s2.empty()){x+=s1.top();s1.pop();}
if(x>=10){Jinwei=1;x=x-10;}
s.push(x);
}
while(!s.empty())
{
newnode=new ListNode(s.top());
s.pop();
newnode->next=l->next;
l->next=newnode;
l=l->next;
}
return head->next;
}
};
3)其他解法
1、反转链表
java
class Solution {
public:
ListNode* reverse(ListNode* head)
{
if(!head || !head->next)
return head;
ListNode* p = head->next, *q = head;
while(p)
{
head->next = p->next;
p->next = q;
q = p;
p = head->next;
}
return q;
}
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* n1 = reverse(l1);
ListNode* n2 = reverse(l2);
ListNode head, *tail = &head;
int carry = 0;
while(n1 || n2 || carry)
{
int n1val = n1 ? n1->val : 0;
int n2val = n2 ? n2->val : 0;
int sum = n1val + n2val + carry;
carry = sum/10;
ListNode* node = new ListNode(sum % 10);
tail->next = node;
tail = node;
if(n1)
n1 = n1->next;
if(n2)
n2 = n2->next;
}
ListNode* res = reverse(head.next);
return res;
}
};
作者:Sunny_SMILE
链接:link
来源:力扣(LeetCode)
2、栈实现
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
stack<int> s1, s2;
while(l1)
{
s1.push(l1->val);
l1 = l1->next;
}
while(l2)
{
s2.push(l2->val);
l2 = l2->next;
}
int carry = 0;//进位
ListNode* ans = nullptr;
while(!s1.empty()||!s2.empty()||carry!=0)
{
int a = s1.empty() ? 0 : s1.top();
int b = s2.empty() ? 0 : s2.top();
if(!s1.empty()) s1.pop();
if(!s2.empty()) s2.pop();
int cur = a + b + carry;
carry = cur/10;
cur %= 10;
auto curnode = new ListNode(cur);
curnode->next = ans;
ans = curnode;
}
return ans;
}
};
作者:Sunny_SMILE
链接:link
来源:力扣(LeetCode)
3、原地计算(参考@aninvalidname)
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int count = 0, temp = 0;
ListNode *head, *last;
//计算两链表长度,使l1指向长链,l2指向短链
for(head = l1; head; head = head->next)
count++;
for(head = l2; head; head = head->next)
count--;
if(count < 0)
swap(l1,l2);
//在链首加一个值为0的节点作为初始的last节点,如果最终该节点值仍为0则删除该节点
last = head = new ListNode(0);
head->next = l1;
for(int i = abs(count); i != 0; i--){ //将两链数位对齐
if(l1->val != 9)
last = l1;
l1 = l1->next;
}
while(l1)
{
temp = l1->val + l2->val;
if(temp > 9){ //如果发生进位,则更新last到l1之间所有数位的值
temp -= 10; //进位后当前数位最大值为8,故将last指针指向当前数位
last->val += 1;
last = last->next;
while(last != l1)
{
last->val = 0;
last = last->next;
}
}
else if(temp != 9)
last = l1;
l1->val = temp;
l1 = l1->next;
l2 = l2->next;
}
return head->val == 1 ? head : head->next;
}
};
作者:Sunny_SMILE
链接:link
来源:力扣(LeetCode)
4、递归
(参考link)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
int length(ListNode* l)
{
int len = 0;
while(l)
{
len++;
l = l->next;
}
return len;
}
void add(ListNode* l1, ListNode* l2, ListNode* p, int m, int n)
{
if(!l1)
return;
if(m > n)
add(l1->next, l2, l1, m - 1, n);
else
{
add(l1->next, l2->next, l1, m - 1, n - 1);
l1->val += l2->val;
}
int carry = l1->val/10;
l1->val = l1->val % 10;
p->val += carry;
}
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int m = length(l1), n = length(l2);
ListNode* node = new ListNode(0);
if(m > n)
{
add(l1, l2, node, m, n);
node->next = l1;
}
else
{
add(l2, l1, node, n, m);
node->next = l2;
}
return node->val ? node : node->next;
}
};
作者:Sunny_SMILE
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
{
stack<int> s1,s2;
ListNode*l=NULL;
ListNode* head=l;
ListNode*newnode=NULL;
while(l1){s1.push(l1->val);l1=l1->next;}
while(l2){s2.push(l2->val);l2=l2->next;}
int Jinwei=0;//进位
int x=0;
while(!s1.empty()||!s2.empty()||Jinwei)
{
x=0;
if(Jinwei==1){x++;Jinwei=0;}
if(!s1.empty()&&!s2.empty()){x+=s1.top()+s2.top();s1.pop();s2.pop();}
else if(s1.empty()&&!s2.empty()){x+=s2.top();s2.pop();}
else if(!s1.empty()&&s2.empty()){x+=s1.top();s1.pop();}
if(x>=10){Jinwei=1;x=x-10;}
newnode=new ListNode(x);
newnode->next=l;
l=newnode;
}
return l;
}
5)学到的东西
学会利用栈
给链表添加元素可以从后往前加
9、分隔链表(725、Medium)
1)题目要求
给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。
每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。
这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。
返回一个符合上述规则的链表的列表。
举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]
示例 1:
输入:
root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释:
输入输出各部分都应该是链表,而不是数组。
例如, 输入的结点 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。
第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。
最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。
示例 2:
输入:
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。
提示:
root 的长度范围: [0, 1000].
输入的每个节点的大小范围:[0, 999].
k 的取值范围: [1, 50].
2)我的解法
c++
vector<ListNode*> splitListToParts(ListNode* root, int k)
{
vector<ListNode*> vec;
int size=0;//从0开始数
ListNode*cur=root;
ListNode*p=NULL;
while(cur){cur=cur->next;size++;}//数到cur为NULL为止
cur=root;int m=0;
int kk=k;
for(int j=0;j<kk;j++)
{
if(size%k!=0)m=size/k+1;//把多余出来的结点分配到前面
//size%k所得即余数,即多余出来的结点
else m=size/k;
vec.push_back(cur);
for(int i=1;i<m;i++){cur=cur->next;}
if(cur){p=cur->next;cur->next=NULL;cur=p;}
size=size-m;k--;
}
return vec;
}
3)其他解法
方法一、
本题实质:考察如何划分一个整数n
巧妙类比:
(1)先数出有多少块小糖,得n块;
(2)将n块小糖分给k个小朋友,从左往右均分(n/k)块;
(3)将剩下的(n%k)块小糖再从左往右1人1块,直至分完。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
vector<ListNode*> splitListToParts(ListNode* root, int k) {
//(1)先数有多少块小糖,得n块;——> 计算链表长度length
ListNode *p = root;
int length = 0;
while(p){
length++;
p = p->next;
}
//(2)将n块小糖分给k个小朋友:从左往右均分(n/k)块;——> 向量vector直接初始化为k个均分值(length/k)
vector<int> splitLength(k,length/k);
//(3)将剩下的(n%k)块小糖再从左往右1人1块,直至分完。——> 向量vector从0到length%k增加1
for(int i = 0; i < length % k; i++)
splitLength[i] += 1;
//分割链表
vector<ListNode*> splitList;
//虚拟头结点:dummyHead大法好
ListNode dummyHead(0);
dummyHead.next = root;
auto pre = root;
int i = 0;//记录splitLength中序号,已取出索引值
int num = 0;//记录子链表的长度
while(root){
num++;
pre = root;
root = root->next;
if(num == splitLength[i]){
i++;//准备记录下一个splitLength中的序号
num = 0;//恢复子链表长度初始值,准备记录下一个子链表
pre->next = NULL;
splitList.push_back(dummyHead.next);//将子链表压入栈中
pre = root;
dummyHead.next = root;
}
}
//糖果小于小朋友人数,每个人分零块
while(i++ < k)
//将root(上面遍历一遍为空,无需再申请空node)直接压入栈中,然后i值加1,简单干练粗暴
splitList.push_back(root);
return splitList;
}
};
作者:wangxiaole
链接:link
来源:力扣(LeetCode)
方法二
1,遍历链表获取长度 length(这个跑不掉 😓);
2,length 除以 k 得到每段链表的平均长度 aveLength 和 余数 remainder,remainder 的值就是有多少个长度为 (aveLength + 1) 的子链表排在前面。
2.1,举个例子帮助理解一下 11 / 3 = 3 余 2: 一共有3段,每段平均3个节点,但是剩下了2个节点,剩下的2个节点不能丢啊,得全部塞到子链表里面去,怎么塞呢?
2.2,根据题意长的链表排前面,短的链表排后面,所以只有前面的两个子链表一人分担一个多余的节点,如此一来便形成了 4 4 3 的结构。
3,接下来的事儿就比较简单了,按照每个子链表应该的长度[4, 4, 3]去截断给定的链表。
vector<ListNode*> splitListToParts(ListNode* root, int k) {
ListNode *temp = root;
int length = 0;
while (temp) { //遍历链表得到其长度
temp = temp->next;
length ++;
}
int aveLength = length / k; //每个子链表平均元素的个数
int remainder = length % k; //余数
vector<ListNode *> result(k, nullptr);
ListNode *head = root;
ListNode *pre = nullptr;
for (int i = 0; i < k; i ++) { //数组有k个元素需要遍历k次
result[i] = head;
int tempLength = remainder > 0 ? (aveLength + 1) : aveLength;
for (int j = 0; j < tempLength; j ++) {
pre = head;
head = head->next;
}
if (pre) pre->next = nullptr; //一个子链表已经生成,断开连接
if (remainder) remainder --;
}
return result;
}
作者:CoderYQ
来源:力扣(LeetCode)评论区
4)自己的优化代码
c++
vector<ListNode*> splitListToParts(ListNode* root, int k)
{
vector<ListNode*> vec(k);//避免使用push_back,可降低时间
int size=0;
ListNode*cur=root;
ListNode*p=NULL;
while(cur){cur=cur->next;size++;}
cur=root;int m=0;
int kk=k;
for(int j=0;j<kk;j++)
{
m=size%k!=0?(size/k+1):size/k;
vec[j]=cur;
for(int i=1;i<m;i++){cur=cur->next;}
if(cur){p=cur->next;cur->next=NULL;cur=p;}
size=size-m;k--;
}
return vec;
}
5)学到的东西
初始化vector时如果知道数组长度就尽量指定好长度,避免之后重新申请内存空间。
vector< int > vec(10)比直接初始化vector< int > vec 再调用vec.push_back要快
10、链表元素按奇偶聚集(328、Medium)
1)题目要求
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
2)我的解法
c++
ListNode* oddEvenList(ListNode* head)
{
if(!head||!head->next)return head;
ListNode*ji=head;
ListNode*ou=head->next;
ListNode*h=ou;
while(ji->next)
{
if(ji->next->next){ji->next=ji->next->next;ji=ji->next;}
else ji->next=NULL;
if(!ou->next)continue;
if(ou->next->next){ou->next=ou->next->next;ou=ou->next;}
else ou->next=NULL;
}
ji->next=h;
return head;
}
3)其他解法
public class Solution {
public ListNode oddEvenList(ListNode head) {
if (head == null) return null;
ListNode odd = head, even = head.next, evenHead = even;
while (even != null && even.next != null) {
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
odd.next = evenHead;
return head;
}
}
作者:LeetCode
链接:link
来源:力扣(LeetCode)
4)自己的优化代码
c++
ListNode* oddEvenList(ListNode* head)
{
if(!head||!head->next)return head;
ListNode*ji=head;
ListNode*ou=head->next;
ListNode*h=ou;
while(ji->next)
{
if(ji->next->next){ji->next=ji->next->next;ji=ji->next;}
else ji->next=NULL;
if(!ou->next)continue;
if(ou->next->next){ou->next=ou->next->next;ou=ou->next;}
else ou->next=NULL;
}
ji->next=h;
return head;
}
5)学到的东西
双指针法