将ListNode基本算法喜欢出现的一点tips总结一下
类型1.需要反转的类型
ListNode* ReverseListNode(ListNode* head)
{
if (head == nullptr)
{
return head;
}
ListNode* pre = nullptr;//反转后的尾指针的设计,同时反转后充当头指针
ListNode* cur = head;//cur作为游标
while (cur != nullptr)//只需要考虑cur的边界问题
{ //反转操作的设计
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
//反转后的设计
cur = next;
}
return pre;
}
类型2.需要哨兵的生成新的数组的
使用哨兵节点:主要是为了创建一个哨兵节点作为新链表的头节点,以简化边界条件处理。
比如:任务需要从头结点就要删除或者插入的操作,明显像上面一样,我们需要对头结点操作,但是肯定不能为nullptr,不然灭了头结点,无法赋值,也操作不了next的节点。
ListNode* deleteDuplicates(ListNode* head) {
if (!head) return nullptr;
ListNode* sentinel = new ListNode(0);//head节点随时可能是重复的,因此可能消失,所以必须要有一个哨兵来记录,所以要初始化一个ListNode
sentinel->next = head;
ListNode* prev = sentinel;//头结点必须要有游标来记录问题
ListNode* current = head;//头结点必须要有游标来记录问题
while (current && current->next) {//找到边界问题只有两个才具有比较的意义
if (current->val == current->next->val) {//真正的代码逻辑的条件判断
// 跳过所有重复节点
while (current->next && current->val == current->next->val) {//执行逻辑的解决
current = current->next;
}
prev->next = current->next;//删除重复ListNode的逻辑
}
else {
prev = prev->next;
}
current = current->next;
}
return sentinel->next;
}
类型3.需要快慢指针的
应用场景:用来找差距的都可以用快慢指针,比如找倍数等
bool isCircleList(ListNode* head)
{
if (head == nullptr)
return false;
ListNode* fast = head;
ListNode* slow = head;
while (fast && slow)
{
if(fast==slow)
{
return true;
}
fast = fast->next;
slow = slow->next->next;
}
return false;
}
分治与递归:
分治是一种算法设计范式,通过将问题分解为更小的子问题来解决,递归则是一种实现这一范式的常用技术。这里有一些具体的例子和解释,说明为什么分治和递归经常一起使用,但也指出它们可以独立存在的情况。
为什么分治经常与递归一起使用
-
自然的分治思想:许多问题天然地适合分治策略,例如归并排序(Merge Sort)、快速排序(Quick Sort)、二分查找(Binary Search)等。在这些算法中,问题被分解为规模更小的子问题,这些子问题可以递归地解决,然后合并结果。
-
代码简洁性:递归能够使代码更加简洁和易读。例如,在归并排序中,通过递归地排序左右子数组,然后合并它们,代码可以非常清晰地表达算法的核心思想。
-
子问题的相似性:在很多分治算法中,子问题的结构与原问题相似。递归正好能有效处理这种自相似性,因为递归函数可以调用自身来解决子问题。