初级算法题(下)

前接初级算法题(上)

链表

链表问题相对容易掌握。 不要忘记 “双指针解法” ,它不仅适用于数组问题,而且还适用于链表问题。

另一种大大简化链接列表问题的方法是 “Dummy node” 节点技巧 ,所谓 Dummy Node 其实就是带头节点的指针。

我们推荐以下题目:反转链表,合并两个有序链表和链接环。

更有额外的挑战,你可以尝试运用 递归 来解决这些问题:反转链表,回文链表和合并两个有序链表。

/*
* 链表部分习题,统一采用不带头结点的单链表结构
* 结构体定义如下
*/
//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) {
   }
};

3.01 删除链表的结点

请编写一个函数,用于 删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点 。

题目数据保证需要删除的节点 不是末尾节点 。

示例 1:

输入:head = [4, 5, 1, 9], node = 5
输出:[4, 1, 9]
解释:指定链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9

示例 2:

输入:head = [4, 5, 1, 9], node = 1
输出:[4, 5, 9]
解释:指定链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9

// 替换法,测试通过
void deleteNode(ListNode* node) {
	node->val = node->next->val;
	node->next = node->next->next;
}

3.02 删除链表的倒数第N个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:

输入:head = [1, 2, 3, 4, 5], n = 2
输出:[1, 2, 3, 5]

示例 2:

输入:head = [1], n = 1
输出:[]

示例 3:

输入:head = [1, 2], n = 1
输出:[1]

// 方法一快慢指针,测试通过
// 首先建立一个头结点,方便后序编程
ListNode* removeNthFromEnd(ListNode* head, int n) {
   
	ListNode* dummy = new ListNode(0, head);
	ListNode* slow = dummy;
	ListNode* fast = head;
	for (int i = 0; i < n; ++i)
	{
   
		fast = fast->next;
	}
	while (fast)
	{
   
		fast = fast->next;
		slow = slow->next;
	}
	slow->next = slow->next->next;
	ListNode* ans = dummy->next;
	delete dummy;
	return ans;
}
// 方法二两次遍历法,测试通过
// 第一遍得到链表长度,第二遍删除结点
int getlength(ListNode* head)
{
   
	int count = 0;
	while (head)
	{
   
		++count;
		head = head->next;
	}
	return count;
}
ListNode* removeNthFromEnd(ListNode* head, int n) {
   
	ListNode* dummy = new ListNode(0, head);
	int length = getlength(head);
	ListNode* ptr = dummy;
	for (int i = 1; i < length - n + 1; ++i)
	{
   
		ptr = ptr->next;
	}
	ptr->next = ptr->next->next;
	ListNode* ans = dummy->next;
	delete dummy;
	return ans;
}
// 方法三堆栈法,测试通过
// 遍历链表,所有结点入栈,然后根据n值出栈
ListNode* removeNthFromEnd(ListNode* head, int n) {
   
	ListNode* dummy = new ListNode(0, head);
	stack<ListNode*> ms;
	ListNode* cur = dummy;
	while (cur)
	{
   
		ms.push(cur);
		cur = cur->next;
	}
	for (int i = 0; i < n; ++i)
	{
   
		ms.pop();
	}
	ListNode* prev = ms.top();
	prev->next = prev->next->next;
	ListNode* ans = dummy->next;
	delete dummy;
	return ans;
}
// 方法四快慢指针,测试通过
// 不建立头结点,使用了额外的一个指针指向待删除结点的前驱
ListNode* removeNthFromEnd(ListNode* head, int n) {
   
	ListNode* fast = head, * slow = head, * prev = head;
	for (int i = 0; i < n - 1; ++i)
	{
   
		if (fast->next != NULL)
		{
   
			fast = fast->next;
		}
	}
	while (fast->next != NULL)
	{
   
		prev = slow;
		slow = slow->next;
		fast = fast->next;
	}
	if (slow == head) head = slow->next;
	else prev->next = slow->next;
	return head;
}

3.03 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

输入:head = [1, 2, 3, 4, 5]
输出:[5, 4, 3, 2, 1]

示例 2:

输入:head = [1, 2]
输出:[2, 1]

示例 3:

输入:head = []
输出:[]

// 方法一迭代法,测试通过
// 这是没有使用头结点的方法,使用了三个指针完成
ListNode* reverseList(ListNode* head) {
   
	if (head == nullptr) return nullptr;
	ListNode* first = head;
	if (first->next == nullptr) return head;
	ListNode* second = first->next;
	ListNode* third;
	if (second->next != nullptr) third = second->next;
	first->next = nullptr;
	while (third != nullptr)
	{
   
		second->next = first;
		first = second;
		second = third;
		third = third->next;
	}
	second->next = first;
	first = second;
	return first;
}
// 方法二迭代法,测试通过
// 方法一的改进版本,使用了更少的指针,减少了代码量
ListNode* reverseList(ListNode* head) {
   
	if (head == nullptr || head->next == nullptr) return head;
	ListNode* p = head;
	head = nullptr;
	while (p != nullptr)
	{
   
		ListNode* s = p;
		p = p->next;
		s->next = head;
		head = s;
	}
	return head;
}
// 方法三迭代法,测试通过
// 首先构造了头结点,然后按照有头结点的单链表进行逆置
ListNode* reverseList(ListNode* head) {
   
	ListNode* truehead = new ListNode(-1, head);
	ListNode* p = truehead->next;
	truehead->next = nullptr;
	while (p != nullptr)
	{
   
		ListNode* s = p;
		p = p->next;
		s->next = truehead->next;
		truehead->next = s;
	}
	return truehead->next;
}
// 方法四递归法,测试通过
// 基本思路是递归进入最后一个结点,然后再回归的时候逆接所有结点。较难理解,不建议采用
ListNode* reverse(ListNode* pre, ListNode* p)
{
   
	ListNode* tail = nullptr;
	if (p != nullptr)
	{
   
		tail = reverse(p, p->next);
		p->next = pre;
	}
	else
	{
   
		tail = pre;
	}
	return tail;
}
ListNode* reverseList(ListNode* head) {
   
	if (head == nullptr || head->next == nullptr) return head;
	return reverse(nullptr, head);
}

3.04 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例 1:

输入:l1 = [1, 2, 4], l2 = [1, 3, 4]
输出:[1, 1, 2, 3, 4, 4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

// 方法一迭代法,测试通过
// 没有使用额外结点,直接在原链表进行操作,缺点是使用指针较多,代码不简洁
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
   
	if (list1 == nullptr || list2 == nullptr)
	{
   
		if (list1 == nullptr) return list2;
		else return list1;
	}
	ListNode* p1 = list1;
	ListNode* p2 = list2;
	ListNode* list3 = p1->val < p2->val ? p1 : p2;
	ListNode* p3 = list3;
	if (list3 == p1) p1 = p1->next;
	else p2 = p2->next;
	while (p1 != nullptr && p2 != nullptr)
	{
   
		if (p1->val < p2->val)
		{
   
			p3->next = p1;
			p3 = p1;
			p1 = p1->next;
		}
		else
		{
   
			p3->next = p2;
			p3 = p2;
			p2 = p2->next;
		}
	}
	if (p1 == nullptr)
	{
   
		p3->next = p2;
	}
	else
	{
   
		p3->next = p1;
	}
	return list3;
}
// 方法二迭代法,测试通过
// 继方法一的优化版本,使用了一个额外结点作为开始头结点,然后思路同方法一
// 代码简洁同时易于理解,建议采用
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
   
	ListNode* mergelist = new ListNode(-1);
	ListNode* prev = mergelist;
	while (list1 != nullptr && list2 != nullptr)
	{
   
		if (list1->val < list2->val)
		{
   
			prev->next = list1;
			list1 = list1->next;
		}
		else
		{
   
			prev->next = list2;
			list2 = list2->next;
		}
		prev = prev->next;
	}
	prev->next = list1 == nullptr ? list2 : list1;
	return mergelist->next;
}
// 方法三递归法,测试通过
// 基本思想如下:
// list1[0] + merge(list1[1:], list2)--list1[0] < list2[0]
// list2[0] + merge(list1, list2[1:])--otherwise
// 也就是说,两个链表头部值较小的一个节点与剩下元素的 merge 操作结果合并。
// 在此思想的基础上加入边界条件判断,及空结点的情况
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;
	}
}

3.05 回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

示例 1:

输入:head = [1, 2, 2, 1]
输出:true

示例 2:

输入:head = [1, 2]
输出:false

// 方法一部分反转法,测试通过
// 通过快慢指针找到链表的中点,然后将中点之后的链表反转,前半部分和后半部分进行比较看是否相等
// 注意:调用了之前反转链表的方法
bool isPalindrome(ListNode* head) {
   
	ListNode* fast = head;
	ListNode* slow = head;
	while (fast != nullptr && fast->next != nullptr)
	{
   
		fast = fast->next->next;
		slow = slow->next;
	}
	slow = reverseList(slow);// 调用 反转链表 方法
	while (slow != nullptr)
	{
   
		if (slow->val != head->val) return false;
		slow = slow->next;
		head = head->next;
	}
	return true;
}
// 方法二堆栈法,测试通过
// 使用栈后进先出的特性,首先将链表中的元素入栈,然后从链表头部开始依次和栈顶元素进行比较,
// 不需要将链表所有元素再比较一遍,只需要比较前一半元素即可
bool isPalindrome(ListNode* head) {
   
	stack<ListNode*> stk;
	ListNode* p = head;
	int len = 0;
	while (p != nullptr)
	{
   
		stk.push(p);
		p = p->next;
		len++;
	}
	len >>= 1;
	for (int i = 0; i < len; ++i)
	{
   
		if (head->val == stk.top()->val)
		{
   
			stk.pop();
			head = head->next;
		}
		else return false;
	}
	return true;
}
// 方法三递归法,测试通过
// 基本思路是使用两个指针,一个从前往后走,一个从后往前走,依次比较元素是否相等
// 由于单链表无法直接从后向前遍历,但是利用递归一定会进行回归的特性可以在逻辑上
// 做到从后向前的递归。同时注意,前向指针需要进行外部定义,不受到函数调用栈的影响
ListNode* frontnode;// 外部定义指针
bool recursive(ListNode* currentNode) {
   
	if (currentNode != nullptr)
	{
   
		if (!recursive(currentNode->next)) return false;
		if (frontnode->val != currentNode->val) return false;
		frontnode = frontnode->next;
	}
	return true;
}
bool isPalindrome(ListNode* head) {
   
	frontnode = head;
	return recursive(head);
}

3.06 环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。如果链表中存在环 ,则返回 true 。 否则,返回 false 。

示例 1:

输入:head = [3, 2, 0, -

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值