21 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
链接:https://leetcode-cn.com/problems/merge-two-sorted-lists
思路:
- 两个链表都存在时,遍历两个链表,并对比节点val,把较小的一个节点插到temp之后。
- 有一个链表不存在时跳出循环,然后把另一个链表的剩余部分全部插入到当前temp之后。
注意:开头创建了一个假的链表头,让temp指针指向他,就不需要再对首个链表节点做特殊处理,牺牲了空间,换取了代码简洁性。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
struct ListNode list_head = {0}, *list_temp = &list_head;
while(list1 && list2)
{
if(list1->val > list2->val)
{
list_temp->next = list2;
list2 = list2->next;
}
else
{
list_temp->next = list1;
list1 = list1->next;
}
list_temp = list_temp->next;
}
if(list1)
{
list_temp->next = list1;
}
else
{
list_temp->next = list2;
}
return list_head.next;
}
其实有一个优化项是:假设list1一直小于list2,不需要把list1的每个节点指针都赋值给list_temp,只需要赋值最后一个小于list2的节点即可。
234 回文链表
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
示例 1:
输入:head = [1,2,2,1] 输出:true
示例 2:
输入:head = [1,2] 输出:false
提示:链表中节点数目在范围[1, 105] 内 0 <= Node.val <= 9
进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题
链接:https://leetcode-cn.com/problems/palindrome-linked-list
直接奔着进阶难度做:
O(n)时间复杂度和 O(1)空间复杂度
难点:
- 因为时间复杂度限制为O(n),所以不管是遍历还是递归,都不允许在到达某个节点后再去遍历链表,需要一次完成。
- 只遍历一遍的情况下,如何在遍历到第1个节点时,获取到第n个节点,遍历到第2个节点时,获取到第n-1个节点
思路:
- 还是通过递归来处理,从链表头递归到尾,逐层传递深度参数,再从最底部将整个链表的长度返回,这样在每层的处理中都可以判断自己是否是链表的后1/2,不会出现重复对比的情况
- 在第n个节点,获取到第1个节点的方法:递归函数中增加形参node_head,从第一层开始将链表头传递进来,再在递归函数逐层返回的过程中,返回node_head->next,实现了一次递归中同时从头尾两端向中间操作链表。
- 因为1、2中需要返回链表总长度和链表指针,因此创建了一个结构体Palindrome_t,用来同时传递这两个参数。
时间复杂度和O(n)
空间复杂度O(1)
struct Palindrome_t
{
int number_all;
struct ListNode* node;
};
struct Palindrome_t list_recursion(struct ListNode* node_curr, struct ListNode* node_head, int number)
{
if(node_curr->next)
{
struct Palindrome_t palindrome_temp = list_recursion(node_curr->next, node_head, number+1);
if(palindrome_temp.node)
{
if(number > (palindrome_temp.number_all%2? palindrome_temp.number_all/2 : (palindrome_temp.number_all+1)/2) )
{
if(node_curr->val != palindrome_temp.node->val)
{
return (struct Palindrome_t){0,0};
}
else
{
return (struct Palindrome_t){palindrome_temp.number_all, palindrome_temp.node->next};
}
}
}
return palindrome_temp;
}
else
{
if(node_curr->val != node_head->val)
{
return (struct Palindrome_t){0,0};
}
else
{
return (struct Palindrome_t){number, node_head->next};
}
}
}
bool isPalindrome(struct ListNode* head)
{
int data_map[10] = {0};
if(list_recursion(head, head, 1).number_all)
{
return true;
}
return false;
}
141 环形链表
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
为了表示给定链表中的环,评测系统内部使用整数 pos来表示链表尾连接到链表中的位置(索引从 0 开始)。
注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。 如果链表中存在环 ,则返回 true 。 否则,返回 false 。示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
链表中节点的数目范围是 [0, 104]-105 <= Node.val <= 105 pos 为 -1 或者链表中的一个 有效索引 。 进阶:你能用 O(1)(即,常量)内存解决此问题吗?
链接:https://leetcode-cn.com/problems/linked-list-cycle
思路1:
快慢指针
- 快指针一次走两个节点,慢指针一次走一个节点
- 如果链表中不存在环,则快指针早晚能检测到next为null
- 如果链表中存在换,则快指针会绕过环追上快指针
bool hasCycle(struct ListNode *head)
{
char i = 0;
struct ListNode *fast_p = head, *slow_p = head;
while(fast_p && fast_p->next)
{
fast_p = fast_p->next->next;
slow_p = slow_p->next;
if(slow_p == fast_p)
{
return true;
}
}
return false;
}
思路2:
过河拆桥
- 因为题目中说明了val的范围:-10^5 <= Node.val <= 10^5,因此将所有遍历过的点的val修改为这个范围之外的一个值0x7fffffff。
- 检测到next为null,肯定没有环
- 检测到next->val==0x7fffffff,说明遍历到了之前遍历过的点,存在环。
#define NODE_MARK 0x7fffffff
bool hasCycle(struct ListNode *head)
{
if(!head)
{
return false;
}
while(head->next)
{
head->val = NODE_MARK;
if(head->next->val == NODE_MARK)
{
return true;
}
head = head->next;
}
return false;
}
总体上我更倾向于使用快慢指针的方法,虽然快慢指针法在存在环时,遍历个数是大于链表中总节点数的,但第二种方法破坏了原有数据,换取一点的执行效率的提高实在不值得。