剑指offer数据结构与算法第四章链表(021-029)

/**
 * 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) {}
 * };
 */
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) {}
};

021 删除链表的倒数第n个结点

先给原链表增加设置一个头结点,可以省略原链表头节点被删除的情况,然后设置两个指针,一个j在后一个i在前,让他们的位置j-i=n,那么当靠后的指针到了链表末尾时,前面的指针的next就刚好指向了要被删除的结点。

// 654321
ListNode* removeNthFromEnd(ListNode* head, int n) {
	ListNode* newHead = new ListNode(0, head);
	ListNode* i = newHead, *j = newHead;
	while (n--)
		j = j->next;
	while (j->next) {
		j = j->next;
		i = i->next;
	}
	i->next = i->next->next;
	return newHead->next;
}

022 链表中环的入口结点

方法1:哈希表,表内存每个结点的索引,若某个结点的next已经存在哈希表中,这个next即为入口
方法2:双指针(快慢)利用环的特点,这里的速度设置的很巧妙,设置两个指针i,j,i移动一位,j移动两位,当他们相遇时,

// 方法1
ListNode* detectCycle(ListNode* head) {
	map< ListNode*, int> sum;
	ListNode* s = head;
	while (s) {
		sum[s] = 0;
		s = s->next;
		if(!s || (s&&sum.count(s))) //next为空说明是尾结点或者不为空但sum中有了,都是跳出。
			break;
	}
	return s;
}
// 方法2
ListNode* detectCycle2(ListNode* head) {
	if (!head || !head->next || !head->next->next) return NULL;
	ListNode* i = head->next, * j = head->next->next;
    ListNode* ptr = head;
	while (i != j){
		if (!j->next || !j->next->next) return NULL;
		i = i->next;
		j = j->next->next;
	}
	while (ptr != i) {
		i = i->next;
		ptr = ptr->next;
	}
	return ptr;
}

023 两个链表的第一个重合结点

// 方法1 双指针
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
    if (headA == NULL || headB == NULL)
        return NULL;
    ListNode *a = headA, *b = headB;
    while (a != b) {
        a = a == NULL ? headA : a->next;
        b = b == NULL ? headB : b->next;
    }
    return a;
}
// 方法2 哈希表
ListNode* getIntersectionNode2(ListNode* headA, ListNode* headB) {
    if (headA == NULL || headB == NULL)
        return NULL;
	map<ListNode*, int > hash;
    ListNode* a = headA, * b = headB;
    while (a != NULL) {
		hash[a];
		a = a->next;
    }
	while (hash.count(b) == 0 && b!=NULL) 
		b = b->next;
	return b;
}

024 反转链表

  • 方法1 迭代法
    用两个指针存储链表中一前一后的结点,改变它们的指向
ListNode* reverseList(ListNode* head) {
	ListNode* p = head, * q = NULL,*s;
	//用p遍历链表,s指向反转的当前第一个,w指向p
	while (p != NULL) {
		s = p->next;  //先保存p的next
		p->next = q;  //改变p的next指向,使链表反转
		q = p;  //移动反转好的链表头
		p = s;  //处理原链表的下一个结点
	}
	return q;
}
  • 方法2 递归法
    理解:reverseList函数用来将head之后的结点都反转
    一篇非常详细的 参考文章
ListNode* reverseList(ListNode* head) {
	if (head == NULL || head->next == NULL)
		return head;
	ListNode* last = reverseList(head->next);
	head->next->next = head;
	head->next = NULL;
	return last;
}

递归反转链表扩展

反转链表中部分结点

  • 同样思路是用函数将后面的链都反转,最后处理第一个结点,把最后一个结点指向要反转的链表的后一个结点,实际上在反转时,要找到反转的具体位置,这是把起始位置开始的链表当作一个新的链表,头节点就为n
  • 先做出来反转1到n,再调用这个函数实现n到m的反转
ListNode* reverseListN(ListNode* head, int n) {
	ListNode* nextN;
	if (n == 1) {
		nextN = head->next;
		return head;
	}
	ListNode*  last = reverseListN(head->next, n - 1);
	head->next->next = head;
	head->next = nextN;
	return last;
}
ListNode* reverseListNM(ListNode* head, int n, int m) {
	if (n == 1)
		return reverseListN(head, m);
	head->next = reverseListNM(head->next, n - 1, m - 1);
}

025 链表中的数字相加

要注意这道题是把链表看作一个整数,相加时要对应的位置相加,并且要考虑进位问题,所以我们应该把链表反转然后求和,最后将结果再次反转即可
写代码的时候要注意生成新链表的过程,为了最后的反转,首先要生成一个结点,保存链表的头结点,并且后面让链表的尾指针指向最后一个结点,而不是最后一个结点的next结点,这样会导致反转出错。

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
	int num = 0, flag = 0; //存和and进位
	int num1, num2;
	// 先反转两个链表 
	l1 = reverseList(l1);
	l2 = reverseList(l2);
	ListNode* q = new ListNode(0), * p = q;
	while (l1 != NULL || l2 != NULL || flag != 0) {
		if (l1 != NULL) {
			num1 = l1->val;
			l1 = l1->next;
		}
		else
			num1 = 0;
		if (l2 != NULL) {
			num2 = l2->val;
			l2 = l2->next;
		}
		else
			num2 = 0;
		num = (num1 + num2 + flag) % 10;
		flag = (num1 + num2 + flag) / 10;
		p->next = new ListNode(num);
		p = p->next;
	}
	return reverseList(q->next);
}

026 重排链表

  • 方法1:线性表的话实现简单点,把每个结点存到线性表中,这样就可以按照索引获得每个结点,然后根据索引修改每个结点的next,将最后一个结点的next修改为null,空间复杂度为O(n),时间复杂度为O(n)
  • 方法2:还可以用反转链表的方法,找到链表的后半部分,然后给它反转,对前半部分和后半部分同时修改它们的next指向,这个空间复杂度为O(1),时间复杂度为O(n)
void reorderList(ListNode* head){
	vector< ListNode*> list;
	int sum, i;
	if (head == NULL)
		return;
	while (head != NULL) {
		list.emplace_back(head);
		head = head->next;
	}
	sum = list.size();
	for (i = 0; i < sum/2; i++) {
		list[i]->next = list[sum - i - 1];
		list[sum - 1 - i]->next = list[i + 1];
	}
	list[i]->next = NULL;
}
// 反转链表法,要注意改变指针指向时的顺序
void reorderList(ListNode* head) {
	ListNode* slow = head, * fast = head;
	if (head == NULL || head->next==NULL||head->next->next==NULL)
		return;
	while (fast->next != NULL && fast->next->next != NULL) {
		slow = slow->next;
		fast = fast->next->next;
	}
	fast = reverseList(slow->next);
	slow->next = NULL;
	slow = head;
	ListNode* FFast, * SSlow;
	while (fast != NULL) {
		FFast = fast->next;
		SSlow = slow->next;
		fast->next = slow->next;  //注意指针改变的顺序
		slow->next = fast;
		slow = SSlow;
		fast = FFast;
	}
}

027 回文链表

同样也有两种方法,一种是用O(n)的空间保存链表使之反转或者哈希表存,
另一种方法是反转链表后半段,即使长度为奇数,只要保证相同长度段全部相等即可,这种方法空间复杂度为O(1)。

// 反转链表法
bool isPalindrome(ListNode* head) {
	ListNode* slow = head, *fast = head;
	// 一个点没有
	if (head == NULL)
		return false;
	// 有一个结点
	if (head->next == NULL)
		return true;
	// 只有俩结点
	if (head->next->next == NULL) {
		if (head->val == head->next->val)
			return true;
		else
			return false;
	}
	// 三个结点及以上
	while (fast->next!=NULL &&fast->next->next!=NULL) {
		slow = slow->next;
		fast = fast->next->next;
	}
	fast = reverseList(slow->next);
	slow->next = NULL;
	slow = head;
	while (fast != NULL && slow != NULL && fast->val == slow->val) {
		slow = slow->next;
		fast = fast->next;
	}
	if (fast == NULL || slow == NULL)
		return true;
	else
		return false;
}

028 展平多级双向链表

我写的

  • 按照题目的意思,遍历链表,遇到有子链表的结点,就要逐个访问其子链表结点并且找到末尾的结点连接到父节点的后一个结点,这样重复且相同的操作可以考虑递归
  • 递归函数用来找到有孩子结点的父结点的最后一个孩子,并修改指针指向, 对每个结点,寻找它的next结点,若next的孩子不为空,递归处理next,否则继续寻找下一个next, 直至next的next为空且其child也为空,找到最后一个孩子结点
  • 修改找到的最后一个孩子以及其他相关结点的指针指向。
void lastChild(Node* parent) {
	Node* first = parent->child, * nextNode = parent->next;
	// 先看该结点有没有孩子,有则先处理孩子
	if (first->child != NULL)
		lastChild(first);
	// 寻找结点的末尾,同样要看结点有无孩子
	while (first->next != NULL) {
		first = first->next;
		if (first->child != NULL)
			lastChild(first);
	}
	// 父节点有next结点时才可以改变next的前驱指针指向
	if (nextNode != NULL) {
		nextNode->prev = first;
		first->next = nextNode;
	}
	parent->next = parent->child;
	parent->child->prev = parent;
	parent->child = NULL;
}
Node* flatten(Node* head) {
	Node* p = head;
	while (p != NULL) {
		if (p->child != NULL) {
			Node* s = p->next;
			lastChild(p);
			p = s;
		}
		else
			p = p->next;
	}
	return head;
}

大佬写的,学习

摘自官方题解评论区
我理解的大佬写的函数思路是,处理一个结点时,要让它的next以及孩子都是平铺的,也即是要先递归处理完结点的next以及所有孩子,让他们平铺,再处理当前结点,看他有无child,有,则找到最后一个孩子,然后进行一系列指针修改,结束。
代码简洁,思路清晰,太牛啦!!

 Node* flatten(Node* head) {
        if (!head)
            return nullptr;
        auto next = flatten(head->next);   //处理完next
        auto child = flatten(head->child);  //处理完孩子
        // 如果有 child 节点,则该节点的 next 应该设置为 child 节点,child 链表的尾节点应该与当前节点的 next 节点连接
        // 最后记得 child 节点置为 nullptr。
        if (child) {
            head->next = child;
            child->prev = head;
            while(child->next) child = child->next;
            if (next) {
                child->next = next;
                next->prev = child;
            }
            head->child = nullptr;
        }
        return head;
    }

029 排序的循环链表

用一前一后两个指针比较val的值即可,
需要注意的是当插入的值为当前最大值或最小值时,指针找不到合适的位置,会循环一圈,只要判断两个指针是否回到原点即可,这时应该把结点插入链表的最大值和最小值之间,所以还需要一个指针来存最大值的位置(最大值的next即为最小值),
这种方法还需要考虑链表为空或者只有一个结点

Node* insert(Node* head, int insertVal) {
	Node* node = new Node(insertVal);
	Node* slow, * fast, * maxNode;
	// 链表空
	if (head == NULL) {
		head = node;
		head->next = head;
		return head;
	}
	// 1个结点
	if (head->next == NULL) {
		head->next == node;
		node->next == head;
		return head;
	}
	// 两个及以上
	slow = head;
	fast = head->next;
	maxNode = slow->val > fast->val ? slow : fast;
	while (slow->val > node->val || fast->val < node->val) {
		slow = slow->next;
		fast = fast->next;
		if (slow == head)
			break;
		if (fast->val >= maxNode->val)
			maxNode = fast;
	}
	if (slow->val <= node->val && fast->val >= node->val) {
		slow->next = node;
		node->next = fast;
	}
	else {
		node->next = maxNode->next;
		maxNode->next = node;
	}
	return head;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值