刷了几天leetcode ,发现以前的很多解法都很冗余,代码不够精简,重新整理了一波,准备做个集合。
每一题解法争取用最少的行数解决问题
以下是链表相关问题,持续更新中…
反转链表
- 反转过程中需要保存当前节点 curr 的下一个节点 next,当 next 空时,反转结束,返回 curr
- 全程只需要关注 curr 是否为空即可。
- 每一轮反转结束,需要更新 prev,curr 的值,而 next 是在最开始就更新的。
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* prev = NULL;
struct ListNode* curr = head;
struct ListNode* next = NULL;
while(curr)
{
next = curr->next; //save next
curr->next = prev; //reverse
if (next==NULL){ //last node of list
break;
}
prev = curr; //move forword
curr = next; //move forword
}
return curr;
}
k个一组反转链表
- 这题用递归解法会比较简单
- 找到翻转点很重要,代码里 kTail 是当前k个一组节点的尾节点,为了找到 kTail ,需要从头节点开始走 k-1 步。
- 通过 kTail 是否空来判断是否需要翻转,如果 kTail 为空,说明剩下的节点数小于 K个了。
- 同时需要保存下一组 k 个节点的头节点,也就是 nextHead
- 只关注当前一组节点的翻转,翻转代码与上述翻转单链表一样,唯一不同的是,break 的地方变成了 next==nextHead
- 当前组的K个节点翻转完毕之后,需要将尾节点(oldHead)的next 指向下一组节点的头节点。
struct ListNode* reverseKGroup(struct ListNode* head, int k) {
struct ListNode* kTail = head; //k个节点的尾节点
struct ListNode* nextHead = NULL;//下一组k个节点的头节点
int n = k-1;
while(n-- && kTail){
kTail = kTail->next;
}
if (kTail){
nextHead = kTail->next;
}
if (kTail){ //满足k个节点即可反转
struct ListNode* oldHead = head; //保存下旧的头部
struct ListNode* prev = NULL;
struct ListNode* curr = head;
struct ListNode* next = NULL;
while(curr){
next = curr->next;
curr->next = prev;
if(next == nextHead){ //翻转到下一组的头节点就截止
break;
}
prev = curr;
curr = next;
}
oldHead->next = reverseKGroup(next, k); //旧的头节点(即翻转后的尾节点)指向下一组的头节点
return curr; //返回翻转后新的head
}
return head; //不满足k个节点则直接返回 head
}
合并有序链表
- 合并之前需要判两个链表是否有空链表
- 合并时需要一个头指针和一个尾指针,尾指针不断更新,最后返回的是头指针
- 合并完之后需要再次判断两个链表剩余的部分
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
if (l1 == NULL || l2 == NULL)
return l1 != NULL? l1 : l2;
struct ListNode head = {0, NULL}; //virtual head node
struct ListNode *tail = &head;
while(l1 && l2){
if (l1->val < l2->val){
tail->next = l1;
l1=l1->next;
}else{
tail->next = l2;
l2=l2->next;
}
tail=tail->next;
}
tail->next = l1==NULL ? l2:l1;
return head.next;
}
合并k个有序链表
//找到K个链表中最小的头节点
struct ListNode* findMinNode(struct ListNode** lists, int listsSize, int*index)
{
int init=0;
struct ListNode* min = NULL;
for (int i=0;i<listsSize;i++){
if (lists[i]==NULL)
continue;
if (init==0){ //初始化第一个最小节点
min=lists[i];
*index = i;
init=1;
} else {
if (lists[i]->val < min->val){
*index = i;
min=lists[i];
}
}
}
return min;
}
struct ListNode* mergeKLists(struct ListNode** lists, int listsSize) {
struct ListNode head = {0, NULL};
struct ListNode* tail = &head;
struct ListNode* minNode = NULL;
int i = 0;
while(minNode = findMinNode(lists, listsSize, &i)){
tail->next = minNode;
tail = tail->next;
lists[i] = lists[i]->next;
}
return head.next;
}
环形链表
- 快慢指针找到相遇点
bool hasCycle(struct ListNode *head) {
struct ListNode *slow = head;
struct ListNode *fast = head;
while(slow && fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if (fast == slow)
return true;
}
return false;
}
环形链表入口
- 先快慢指针找到相遇点
- 再2个指针分别从起点和相遇点开始,直到再次相遇则为入口
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode *slow = head;
struct ListNode *fast = head;
struct ListNode *meet = NULL;
//find meet node
while(slow && fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if (slow==fast){
meet = slow;
break;
}
}
slow = head;
while(slow && meet){
if (meet==slow){ //find entry point
return meet;
}
slow = slow->next;
meet = meet->next;
}
return NULL;
}
删除倒数第N个节点
- 快指针先向前走n步,注意是n步,不是n-1步
- 快慢指针同时走,直到 fast 或 fast->next 为空
- 如果 fast 为空,表示要删除的是第一个节点,此时 slow 指向的是 head
- 如果 fast !=NULL && fast->next==NULL,表示要删除的是head节点之后的节点,此时slow 指向的是被删节点的前置节点。
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
struct ListNode* slow = head;
struct ListNode* fast = head;
while(fast && n--){
fast=fast->next;
}
while(slow && fast && fast->next){
slow = slow->next;
fast = fast->next;
}
if (fast==NULL){ //删除的是第一个节点
head = head->next;
}else if (slow && slow->next){ //删除的不是第一个节点
slow->next = slow->next->next;
}
return head;
}
相交链表的交点
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if (headA==NULL || headB==NULL){
return NULL;
}
//先找到其中一个链表的尾节点
struct ListNode *tailA = headA;
while(tailA->next){
tailA = tailA->next;
}
//将尾节点和头结点连起来形成环
tailA->next = headA;
//问题转化为求环的入口
struct ListNode *slow = headB;
struct ListNode *fast = headB;
struct ListNode *meet = NULL;
while(slow && fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if (slow==fast){
meet = slow;
break;
}
}
slow = headB;
while(slow && meet){
if (slow==meet){
break;
}
slow = slow->next;
meet = meet->next;
}
将尾节点和头结点解开
tailA->next = NULL;
return meet;
}
移除链表指定元素
struct ListNode* removeElements(struct ListNode* head, int val) {
//移除最开始相等的节点,直到第一个不相等的
while(head && head->val == val){
head = head->next;
}
//head 一定是不相等的节点,此时如果下一个节点为空可以直接返回了
if (head==NULL || head->next==NULL)
return head;
//2个指针一前一后
struct ListNode* slow = head;
struct ListNode* fast = head->next;
while(slow && fast){
if (fast->val==val){ //快指针相等的话,慢指针直接删除该节点
slow->next = fast->next;
}else{
slow = slow->next;
}
fast = fast->next;
}
return head;
}
回文链表
以下面2个链表为例
list1: 1->2->3->2->1
list2: 1->2->3->2
- 首先需要处理特殊情况,即链表长度为0,1,2 的时候。
- 接下来用快慢指针,直到 fast 或 fast->next 为空
- 如果fast 为空则表示链表长度为偶数,此时 slow 指向链表第 n/2 +1 个节点,也就是list1的第3 个节点
- 如果fast 不为空则 fast->next 为空,链表长度为奇数,此时 slow 指向链表中间节点,也就是 list2 的第3个节点
- 针对偶数链表做翻转,需要反转 1->2 的部分,其下一个节点正是 slow
- 针对奇数链表做反转,也只需要反转 1->2 的部分,其下一个节点正是 slow
- 所以当 next==slow 的时候反转停止,返回反转后的头节点,也就是 slow 的上一个节点
- 接下来同时反向遍历比较,这里针对奇偶有区别,奇数链表的右半部分从 slow->next 开始,偶数链表右半部分从 slow 开始。
struct ListNode* reverse(struct ListNode* head, struct ListNode* givenNode){
struct ListNode* prev = NULL;
struct ListNode* curr = head;
struct ListNode* next = NULL;
while(curr){
next = curr->next;
curr->next = prev;
if (next == givenNode){
break;
}
prev = curr;
curr = next;
}
return curr;
}
bool isPalindrome(struct ListNode* head) {
//1个节点
if (head && head->next==NULL){
return true;
}
//2个节点
if (head && head->next && head->next->next==NULL){
if (head->val == head->next->val){
return true;
}else{
return false;
}
}
struct ListNode* slow = head;
struct ListNode* fast = head;
while (slow!=NULL && fast!=NULL && fast->next!=NULL) {
slow = slow->next;
fast = fast->next->next;
}
struct ListNode* left = reverse(head, slow);
struct ListNode* right = NULL;
if (fast){ //奇数链表
right = slow->next;
}else{ //偶数链表,此时slow指向第len/2+1个节点
right = slow;
}
while(left && right){
if (left->val != right->val){
return false;
}else{
left = left->next;
right = right->next;
}
}
return true;
}
奇偶链表
- 用4个指针分别指向奇数链表的首尾节点和偶数链表的首尾节点
- 最后再把偶数链表和奇数链表连起来
struct ListNode* oddEvenList(struct ListNode* head) {
if (head==NULL || head->next==NULL)
return head;
struct ListNode* oddHead = head; //奇数节点的头
struct ListNode* oddTail = oddHead; //奇数节点的尾
struct ListNode* eventHead = head->next; //偶数节点头
struct ListNode* eventTail = eventHead; //偶数节点尾
bool isOdd = true;
head = head->next->next; //从第3个节点开始
while(head){
printf("%d\n", head->val);
if (isOdd){
oddTail->next = head;
oddTail = oddTail->next;
isOdd = false;
}else{
eventTail->next = head;
eventTail = eventTail->next;
isOdd = true;
}
head = head->next;
}
if (!isOdd) //一定要将偶数节点的最后一个节点置为空
eventTail->next = NULL;
oddTail->next = eventHead;
return oddHead;
}
两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
- 和合并有序链表比较相似,只是多了一个进位的操作。同时要考虑其中一个链表更长的情况。
struct ListNode*newNode(int val)
{
struct ListNode* node = malloc(sizeof(struct ListNode));
node->val = val;
node->next = NULL;
return node;
}
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {
struct ListNode head = {0,NULL};
struct ListNode* tail = &head;
struct ListNode* p1 = l1;
struct ListNode* p2 = l2;
int carry = 0; //进位
while(p1 && p2){
int sum = p1->val + p2->val + carry;
carry = sum/10;
tail->next = newNode(sum%10);
tail = tail->next;
p1 = p1->next;
p2 = p2->next;
}
if (carry != 0){
while(p1){
int sum = p1->val + carry;
carry = sum/10;
tail->next = newNode(sum%10);
tail = tail->next;
p1 = p1->next;
}
while(p2){
int sum = p2->val + carry;
carry = sum/10;
tail->next = newNode(sum%10);
tail = tail->next;
p2 = p2->next;
}
if (carry!=0){
tail->next = newNode(carry);
tail = tail->next;
}
}else{
tail->next = p1==NULL?p2:p1;
}
return head.next;
}
旋转链表
- 2个指针分别找到链表解除链接的点,要注意是否是原地旋转
struct ListNode* rotateRight(struct ListNode* head, int k) {
if (head==NULL || k<=0)
return head;
struct ListNode* slow = head;
struct ListNode* fast = head;
while(k--){
fast = fast->next;
if(fast == NULL){ //k>len 的时候从头开始
fast = head;
}
}
if (slow==fast){ // 相等表示 k%len==0
return head;
}
while(slow && fast && fast->next){
fast = fast->next;
slow = slow->next;
}
struct ListNode* newHead = slow->next;
slow->next = NULL;
fast->next = head;
return newHead;
}