一、两个链表的第一个公共子节点问题
1.第一步,定义链表
struct ListNode {
int val;
struct ListNode* next;
} ;
struct ListNode* createNode(int x) {
struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
newNode->val = x;
newNode->next = NULL;
return newNode;
}
具体题目: 输入两个链表,找出它们的第一个公共节点。
例如下面的两个链表:
两个链表的头结点都是已知的,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的结点数也是未知的,请设计算法找到两个链表的合并点。
一个屡试不爽的思路:将常用数据结构和常用算法思想都想一遍,看看哪些能解决问题。
1)用集合的方法来做
这里用 java 的代码实现
public ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
Set<ListNode> set = new HashSet<>(); // 创建一个空的Set对象set,用于存储链表节点
while (headA != null) { // 循环将链表headA的节点添加到set中
set.add(headA);
headA = headA.next; // 将headA指向下一个节点
}
while (headB != null) { // 循环遍历链表headB的节点
if (set.contains(headB)) // 如果set中含有当前节点
return headB; // 返回当前节点,即找到第一个公共节点
headB = headB.next; // 将headB指向下一个节点
}
return null; // 如果没有找到公共节点,则返回null
}
函数findFirstCommonNodeBySet
用于在两个链表中查找第一个公共节点。
代码中使用了Set
接口的实现类HashSet
来存储链表节点。
首先将链表headA的所有节点添加到set中,然后遍历链表headB的节点,如果set中包含当前节点,则说明找到了第一个公共节点,返回该节点。
如果遍历完整个链表headB仍未找到公共节点,则返回null。
二、判断链表是否为回文序列
LeetCode234,这也是一道简单,但是很经典的链表题,判断一个链表是否为回文链表。
示例1:
输入: 1->2->2->1
输出: true
进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
做这题,刚开始我就在想各种数据结构,得到的一种思路是这个链表元素,一份压入栈中,一份压入队中,分别出栈,出队,比较两者元素值。有一个不相等,就不是回文链表。
看了知识库里面的答案,其实不用压入队,遍历就行。
同时还有优化方法:
一边遍历一边压栈,得到链表的总长度。第二遍比较的时候,只比较一半。
(因为假设是回文链表)
public boolean isPalindrome(ListNode head) {
ListNode temp = head; // 创建一个指针temp,用于遍历链表
Stack<Integer> stack = new Stack(); // 创建一个空的栈对象stack,用于存储链表节点的值
// 将链表节点的值压入栈中
while (temp != null) { // 循环遍历链表
stack.push(temp.val); // 将节点的值压入栈中
temp = temp.next; // 将temp指向下一个节点
}
// 判断链表是否回文
while (head != null) { // 循环遍历链表
if (head.val != stack.pop()) { // 如果链表节点的值不等于栈顶元素
return false; // 说明该链表不是回文链表,返回false
}
head = head.next; // 将head指向下一个节点
}
return true; // 如果整个链表遍历完之后都没有发现不相等的情况,则说明该链表是回文链表,返回true
}
在代码中,我们使用stack.pop()
方法弹出栈顶的元素,并与链表节点值进行比较。栈顶元素会随着每次弹出而改变。
三、合并两个有序列表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = [] 输出:[]
示例 3:
输入:l1 = [], l2 = [0] 输出:[0]
话不多说,附上代码
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// 如果 list1 为空,则直接返回 list2
if (list1 == null){
return list2;
}
// 如果 list2 为空,则直接返回 list1
if (list2 == null){
return list1;
}
else if (list1.val < list2.val){
// 当 list1 的值小于 list2 的值时,
// 将 list1 的下一个节点与 list2 进行合并,并将结果作为 list1 的下一个节点
list1.next = mergeTwoLists(list1.next, list2);
// 返回合并后的 list1
return list1;
}
else {
// 当 list1 的值大于等于 list2 的值时,
// 将 list2 的下一个节点与 list1 进行合并,并将结果作为 list2 的下一个节点
list2.next = mergeTwoLists(list1, list2.next);
// 返回合并后的 list2
return list2;
}
}
}
这段代码实现了合并两个有序链表的功能。根据链表节点的值大小,递归地将两个链表中较小节点连接在一起,形成新的有序链表。
递归方法用的很巧妙,以我自己的理解是节点的迭代恰好与递归的特性相符,可惜现阶段对递归的理解的=还没有很透彻...
四、合并两个链表
给你两个链表 list1
和 list2
,它们包含的元素分别为 n
个和 m
个。
请你将 list1
中下标从 a
到 b
的全部节点都删除,并将list2
接在被删除节点的位置。
下图中蓝色边和节点展示了操作后的结果:
请你返回结果链表的头指针。
这道题的思路并不难,就是需要注意一些细节。
思路:可以把题目看成是链表的中间插入
细节:要通过遍历来找到插入的位置在哪,代码中用到了 pre1 和 post1 来找到需要插入的节点
list2 也 用到了 post2 来遍历链表
总的来说,思路并不难,需要你对链表的插入较为熟悉
代码如下:
class Solution {
public ListNode mergeInBetween(ListNode l1, int a, int b, ListNode l2) {
// 创建指针 pre1 指向链表 l1 的起始节点
ListNode pre1 = l1;
// 创建指针 post1 指向链表 l1 的起始节点
ListNode post1 = l1;
// 创建指针 post2 指向链表 l2 的起始节点
ListNode post2 = l2;
// 初始化变量 i 和 j 为 0,用于计数
int i = 0, j = 0;
// 遍历链表 l1,找到 pre1 指针的位置,停止条件是找到区间的前一个节点 a-1 或者遍历到链表末尾
while (pre1 != null && i < a - 1) {
pre1 = pre1.next; // 向后移动 pre1 指针
i++; // i 计数器加一
}
// 遍历链表 l1,找到 post1 指针的位置,停止条件是找到区间的最后一个节点 b 或者遍历到链表末尾
while (post1 != null && j < b) {
post1 = post1.next; // 向后移动 post1 指针
j++; // j 计数器加一
}
// 将 pre1 的下一个节点指向 l2,完成区间 [a, b] 的切断
pre1.next = l2;
// 遍历链表 l2,找到 post2 的末尾节点
while (post2.next != null) {
post2 = post2.next; // 向后移动 post2 指针
}
// 将 post2 的下一个节点指向 post1 的下一个节点,连接剩余部分
post2.next = post1.next;
// 返回合并后的链表 l1
return l1;
}
}