2. 链表
-
相交链表: https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。 如果两个链表不存在相交节点,返回 null 。
方法一:哈希集合 思路和算法 判断两个链表是否相交,可以使用哈希集合存储链表节点。 首先遍历链表headA,并将链表headA 中的每个节点加入哈希集合中。 然后遍历链表headB,对于遍历到的每个节点,判断该节点是否在哈希集合中: 如果当前节点不在哈希集合中,则继续遍历下一个节点; 如果当前节点在哈希集合中,则后面的节点都在哈希集合中,即从当前节点开始的所有节点都在两个链表的相交部分, 因此在链表 headB 中遍历到的第一个在哈希集合中的节点就是两个链表相交的节点,返回该节点。 如果链表headB 中的所有节点都不在哈希集合中,则两个链表不相交,返回null。 class Solution { public: ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { unordered_set<ListNode *> visited; //创建一个哈希表 ListNode *temp = headA; cout<< temp << endl ; //输出的是headA的地址 while (temp != nullptr) { visited.insert(temp); //将链表A插入到哈希表中 temp = temp->next; //指向下一节点 } temp = headB; while (temp != nullptr) { if (visited.count(temp)) { //count计数 return temp; } temp = temp->next; } return nullptr; } };
-
反转链表: https://leetcode-cn.com/problems/reverse-linked-list/
给你单链表的头节点head ,请你反转链表,并返回反转后的链表。
方法一:迭代 假设链表为 1→2→3→∅,我们想要把它改成 3∅←1←2←3。 在遍历链表时,将当前节点的 next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。 class Solution { public: ListNode* reverseList(ListNode* head) { ListNode* prev = nullptr; ListNode* curr = head; cout<<curr<<endl; //输出地址 while (curr) { ListNode* next = curr->next; curr->next = prev; prev = curr; curr = next; } return prev; } }; 复杂度分析 时间复杂度:O(n),其中 n 是链表的长度。需要遍历链表一次。 空间复杂度:O(1)。
-
合并两个有序链表: https://leetcode-cn.com/problems/merge-two-sorted-lists/
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
class Solution { public: ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { if (list1 == nullptr) { return list2; } else if (list2 == nullptr) { return list1; } else if (list1->val < list2->val) { list1->next = mergeTwoLists(list1->next, list2); return list1; } else { list2->next = mergeTwoLists(list1, list2->next); return list2; } } }; 复杂度分析 时间复杂度:O(n+m),其中 n 和 m 分别为两个链表的长度。因为每次调用递归都会去掉 l1 或者 l2 的头节点(直到至少有一个链表为空),函数 mergeTwoList 至多只会递归调用每个节点一次。因此,时间复杂度取决于合并后的链表长度,即O(n+m)。 空间复杂度:O(n+m),其中 n 和 m 分别为两个链表的长度。递归调用 mergeTwoLists 函数时需要消耗栈空间,栈空间的大小取决于递归调用的深度。结束递归调用时 mergeTwoLists 函数最多调用 n+m 次,因此空间复杂度为 O(n+m)。
-
删除排序链表中的重复元素: https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。 返回同样按升序排列的结果链表。
class Solution { public: ListNode* deleteDuplicates(ListNode* head) { if (!head) { return head; } ListNode* cur = head; while (cur->next) { //遍历链表 if (cur->val == cur->next->val) { //判断连续两个元素是否相同 cur->next = cur->next->next; //删除连续两个相同的元素 } else { cur = cur->next; } } return head; } }; 复杂度分析 时间复杂度:O(n),其中 nn 是链表的长度。 空间复杂度:O(1)。
-
删除链表的倒数第 N 个结点(???): https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
class Solution { public: int getLength(ListNode* head) { int length = 0; while (head) { ++length; head = head->next; } return length; } ListNode* removeNthFromEnd(ListNode* head, int n) { ListNode* dummy = new ListNode(0, head); int length = getLength(head); ListNode* cur = dummy; for (int i = 1; i < length - n + 1; ++i) { cur = cur->next; } cur->next = cur->next->next; ListNode* ans = dummy->next; // ??? delete dummy; return ans; } }; 复杂度分析 时间复杂度:O(L),其中 L 是链表的长度。 空间复杂度:O(1)。
new的使用: https://www.runoob.com/cplusplus/cpp-dynamic-memory.html
-
两两交换链表中的节点: https://leetcode-cn.com/problems/swap-nodes-in-pairs/
class Solution { public: ListNode* swapPairs(ListNode* head) { //递归的终止条件 if (head == nullptr || head->next == nullptr) { return head; } //假设链表是 1->2->3->4 //这句就先保存节点2 ListNode* newHead = head->next; //继续递归,处理节点3->4 //当递归结束返回后,就变成了4->3 //于是head节点就指向了4,变成1->4->3 head->next = swapPairs(newHead->next); //将2节点指向1 newHead->next = head; return newHead; } }; 复杂度分析 时间复杂度:O(n),其中 nn 是链表的节点数量。需要对每个节点进行更新指针的操作。 空间复杂度:O(n),其中 nn 是链表的节点数量。空间复杂度主要取决于递归调用的栈空间。
-
两数相加 II: https://leetcode-cn.com/problems/add-two-numbers-ii/
/** * 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) { stack<int> s1, s2; // stack库 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() or !s2.empty() or carry != 0) { int a = s1.empty() ? 0 : s1.top(); // ?并不是一个运算符,?:才是,这是C++种唯一一个三目运算符。 int b = s2.empty() ? 0 : s2.top(); //<表达式1>?<表达式2>:<表达式3> if (!s1.empty()) s1.pop(); //它的意思是,如果表达式1成立,则输出表达式2的值,否则输出表达式3的值。 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; } }; 复杂度分析 时间复杂度:O(max(m,n)),其中 m 和 n 分别为两个链表的长度。我们需要遍历两个链表的全部位置,而处理每个位置只需要 O(1) 的 时间。 空间复杂度:O(m+n),其中 m 和 n 分别为两个链表的长度。空间复杂度主要取决于我们把链表内容放入栈中所用的空间。
-
回文链表: https://leetcode-cn.com/problems/palindrome-linked-list/
/** * 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: bool isPalindrome(ListNode* head) { vector<int> vals; while (head != nullptr) //emplace_back函数的作用是减少对象拷贝和构造次数,用于对临时对象的赋值。 // 在使用push_back函数往容器中增加新元素时,必须要有一个该对象的实例才行,而 // emplace_back可以不用,它可以直接传入对象的构造函数参数直接进行构造,减少一次拷贝和赋值操作。 { vals.emplace_back(head->val); head = head->next; } for (int i = 0, j = (int)vals.size() - 1; i < j; ++i, --j) { if (vals[i] != vals[j]) { return false; } } return true; } }; 复杂度分析 时间复杂度:O(n),其中 n 指的是链表的元素个数。 第一步: 遍历链表并将值复制到数组中,O(n)。 第二步:双指针判断是否为回文,执行了 O(n/2) 次的判断,即O(n)。 总的时间复杂度:O(2n) =O(n)。 空间复杂度:O(n),其中 n 指的是链表的元素个数,我们使用了一个数组列表存放链表的元素值。
-
分隔链表: https://leetcode-cn.com/problems/split-linked-list-in-parts/
class Solution { public: vector<ListNode*> splitListToParts(ListNode* head, int k) { int n = 0; ListNode *temp = head; while (temp != nullptr) { n++; temp = temp->next; } int quotient = n / k, remainder = n % k; vector<ListNode*> parts(k,nullptr); ListNode *curr = head; for (int i = 0; i < k && curr != nullptr; i++) { parts[i] = curr; int partSize = quotient + (i < remainder ? 1 : 0); for (int j = 1; j < partSize; j++) { curr = curr->next; } ListNode *next = curr->next; curr->next = nullptr; curr = next; } return parts; } }; 复杂度分析 时间复杂度:O(n),其中 nn 是链表的长度。需要遍历链表两次,得到链表的长度和分隔链表。 空间复杂度:O(1)。只使用了常量的额外空间,注意返回值不计入空间复杂度
-
奇偶链表: https://leetcode-cn.com/problems/odd-even-linked-list/
/** * 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* oddEvenList(ListNode* head) { if (head == nullptr) { return head; } ListNode* evenHead = head->next; ListNode* odd = head; ListNode* even = evenHead; while (even != nullptr && even->next != nullptr) { odd->next = even->next; odd = odd->next; even->next = odd->next; even = even->next; } odd->next = evenHead; return head; } }; 复杂度分析 时间复杂度:O(n),其中 n 是链表的节点数。需要遍历链表中的每个节点,并更新指针。 空间复杂度:O(1)。只需要维护有限的指针。