比较简单的题目,头结点标示的个位数字,因此直接模拟就可以了。注意链表相关的题目可以添加一个虚拟头节点,用来简化一些边界情况的处理。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* res = new ListNode(-1);
ListNode* cur = res;
int carry = 0; //进位标示
while (l1 || l2) {
int v1 = l1 ? l1 -> val : 0;
int v2 = l2 ? l2 -> val : 0;
int sum = v1 + v2 + carry;
carry = sum / 10;
cur -> next = new ListNode(sum % 10);
cur = cur -> next;
if (l1) l1 = l1 -> next;
if (l2) l2 = l2 -> next;
}
if (carry) cur -> next = new ListNode(1);
return res -> next;
}
};
翻转即将所有节点的next指针指向前驱节点。由于是单链表,我们在迭代时不能直接找到前驱节点,所以我们需要一个额外的指针保存前驱节点。同时在改变当前节点的next指针前,不要忘记保存它的后继节点。
其实相当于头插法!
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* cur = head;
while (cur) {
ListNode* nxt = cur -> next;
cur -> next = prev;
prev = cur;
cur = nxt;
}
return prev;
}
};
还有一种递归的写法:
首先我们先考虑 reverseList 函数能做什么,它可以翻转一个链表,并返回新链表的头节点,也就是原链表的尾节点。
所以我们可以先递归处理 reverseList(head->next),这样我们可以将以head->next为头节点的链表翻转,并得到原链表的尾节点tail,此时head->next是新链表的尾节点,我们令它的next指针指向head,并将head->next指向空即可将整个链表翻转,且新链表的头节点是tail。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;
ListNode *tail = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return tail;
}
};
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* dummy = new ListNode(-1); // 建立一个虚拟头节点,因为有可能left==1,避免处理边界情况
dummy -> next = head;
auto a = dummy;
for (int i = 0; i < left - 1; i++) a = a -> next; //找到left的前一个节点
auto b = a -> next, c = b -> next;
//接下来是反转链表的逻辑,要做right - left次
for (int i = 0; i < right - left; i++) {
auto d = c -> next;
c -> next = b;
b = c, c = d;
}
//两头的指针搞正确
a -> next -> next = c;
a -> next = b;
return dummy -> next;
}
};
找到前面的那个点,改掉指针即可。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(-1);
dummy -> next = head;
// sz: 包含虚拟头结点的整个链表的长度
int sz = 0;
for (auto p = dummy; p; p = p -> next) sz++;
// 倒数第N个节点,也就是正数的(sz+1-n)个点.
// 要删除这个点,要先到它前面的那个点,也就是第(sz-n)个点,从dummy开始跳,需要跳(sz-n-1)步
auto p = dummy;
for (int i = 0; i < sz - n - 1; i++) p = p -> next;
p -> next = p -> next -> next;
return dummy -> next;
}
};
判断大小,直接模拟即可,注意代码的简洁性。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* dummy= new ListNode(-1);
ListNode* tail = dummy;
while (l1 && l2) {
if (l1 -> val < l2 -> val) tail = tail -> next = l1, l1 = l1 -> next;
else tail = tail -> next = l2, l2 = l2 -> next;
}
if (l1) tail -> next = l1;
if (l2) tail -> next = l2;
return dummy -> next;
}
};
相当于把后面一部分的链表拼接到前面
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if (!head) return head;
int sz = 0; //链表长度
ListNode* tail; //当前链表的尾节点
for (auto p = head; p; p = p -> next) {
tail = p;
sz++;
}
k %= sz;
if (!k) return head;
auto p = head;
//找到前半部分的最后的元素
for (int i = 0; i < sz - k - 1; i++) p = p -> next;
//把后半部分拼到前面即可
//1. 前半部分的尾结点的next指向空
//2. 后半部分的尾结点的next指向链表的头结点
tail -> next = head;
head = p -> next; // 这个是最新的头结点
p -> next = nullptr;
return head;
}
};
类似于双指针,判断一段相等值的个数是只有一个,还是至少两个,来选择不同的策略。注意看代码里的条件判断的部分。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
auto dummy = new ListNode(-1);
dummy -> next = head;
auto p = dummy;
while (p -> next) {
auto first = p -> next;
auto end = first -> next;
// 让end走到和first不等的节点
while (end && end -> val == first -> val) end = end -> next;
// 说明相等这一段只有一个
if (first -> next == end) p = p -> next;
// 至少有两个,那么跳过这一段
else p -> next = end;
}
return dummy -> next;
}
};
其实把上面题的else里面改一下就可以了
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
auto dummy = new ListNode(-1);
dummy -> next = head;
auto p = dummy;
while (p -> next) {
auto first = p -> next;
auto end = first -> next;
while (end && end -> val == first -> val) end = end -> next;
if (first -> next == end) p = p -> next;
else p -> next -> next = end;
}
return dummy -> next;
}
};
当然也有更简单的写法,思路就是判断一下枚举到的值和当前的值是否相等,只保留不相等的节点
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) return head;
auto cur = head;
for (auto p = head -> next; p; p = p -> next) {
if (p -> val != cur -> val) cur = cur -> next = p;
}
cur -> next = nullptr;
return head;
}
};
用两个指针从头开始扫描,第一个指针每次走一步,第二个指针每次走两步。如果走到 null,说明不存在环;否则如果两个指针相遇,则说明存在环。
原因:
假设链表存在环,则当第一个指针走到环入口时,第二个指针已经走到环上的某个位置,距离环入口还差 x 步。由于第二个指针每次比第一个指针多走一步,所以第一个指针再走 x步,两个指针就相遇了。
复杂度分析,由于慢指针走的总步数小于链表总长度,复杂度为 O ( N ) O(N) O(N).
class Solution {
public:
bool hasCycle(ListNode *head) {
if (!head || !head -> next) return false;
ListNode* slow = head;
ListNode* fast = head -> next;
while (slow && fast) {
if (slow == fast) return true;
slow = slow -> next;
fast = fast -> next;
if (fast) fast = fast -> next;
}
return false;
}
};