首页 > 其他分享 >《渣男代码历险记》第四 双指针怪

《渣男代码历险记》第四 双指针怪

时间:2024-04-22 20:22:13浏览次数:20  
标签:历险记 slow 代码 fast 链表 数组 节点 指针

  1. 已知一个带有表头结点的单链表,结点结构为:

data next
假设该链表只给出了头指针 head。 在不改变链表的前提下,请设计一个尽可能高效的算法,查找链 表中倒数第k(k 为正整数)个位置上的结点。若查找成功,算法输出该结点的 data 值,并返回1;否则,
只返回0。
要求:
(1)描述算法的基本设计思想。
(2)描述算法的详细实现步骤。
(3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用 C 或 C++ 语言实现),关键之处
请给出简要注释。
渣男回答:

  1. 基本设计思想:
    我们可以使用双指针法来解决这个问题。首先,我们创建两个指针,一个快指针和一个慢指针。快指针先向前移动 k 个节点,然后快指针和慢指针同时向前移动,直到快指针到达链表的末尾。此时,慢指针所指向的节点就是倒数第 k 个节点。

(2) 详细实现步骤:

初始化两个指针,fast 和 slow,都指向头结点 head。
将 fast 指针向前移动 k 个节点。
同时移动 fast 和 slow 指针,直到 fast 指针到达链表的末尾。
如果 fast 指针到达链表末尾时,slow 指针指向的节点是倒数第 k 个节点,则返回该节点的 data 值;否则,返回 0。
(3) C++ 代码实现:

cpp
复制代码运行

include

using namespace std;

struct ListNode {
int data;
ListNode *next;
};

int findKthFromEnd(ListNode *head, int k, int &result) {
if (head == nullptr || k <= 0) {
return 0;
}

ListNode *fast = head;
ListNode *slow = head;

// 将 fast 指针向前移动 k 个节点
for (int i = 0; i < k; ++i) {
    if (fast == nullptr) {
        return 0;
    }
    fast = fast->next;
}

// 同时移动 fast 和 slow 指针,直到 fast 指针到达链表的末尾
while (fast != nullptr) {
    fast = fast->next;
    slow = slow->next;
}

// 如果 slow 指针指向的节点是倒数第 k 个节点,则返回该节点的 data 值
if (slow != nullptr) {
    result = slow->data;
    return 1;
}

return 0;

}

include <stdio.h>

include <stdlib.h>

typedef struct ListNode {
int data;
struct ListNode *next;
} ListNode;

int findKthFromEnd(ListNode *head, int k, int *result) {
if (head == NULL || k <= 0) {
return 0;
}

ListNode *fast = head;
ListNode *slow = head;

// 将 fast 指针向前移动 k 个节点
for (int i = 0; i < k; ++i) {
    if (fast == NULL) {
        return 0;
    }
    fast = fast->next;
}

// 同时移动 fast 和 slow 指针,直到 fast 指针到达链表的末尾
while (fast != NULL) {
    fast = fast->next;
    slow = slow->next;
}

// 如果 slow 指针指向的节点是倒数第 k 个节点,则返回该节点的 data 值
if (slow != NULL) {
    *result = slow->data;
    return 1;
}

return 0;

}
为什么要两个指针?
在链表问题中使用两个指针,通常被称为“快慢指针”技巧,是一种高效的算法设计技术。具体原因如下:

提高效率:通过使用快慢指针,可以在一次遍历中解决问题,而不需要对整个链表进行多次遍历。快指针先行k步,然后慢指针开始移动,当快指针到达链表末尾时,慢指针即位于倒数第k个节点。
节省空间:这种方法不需要额外的数据结构来存储链表的节点信息,因此可以节省空间。
简化逻辑:使用两个指针可以使逻辑更加直观和简单。快指针用于确定范围,慢指针用于跟踪目标节点。这种方法的逻辑比单指针要清晰得多。
避免修改原链表:使用两个指针可以在不修改链表结构的情况下找到倒数第k个节点,这对于不允许或不希望修改原始数据结构的情况非常有用。
解决特定问题:对于一些特定的链表问题,如寻找链表中的环或者寻找倒数第k个节点,双指针技术提供了一种有效的解决方案。
总之,双指针技术在链表操作中非常有用,尤其是在需要高效解决特定类型问题时。

这个是谁最先使用的

双指针法这一技术的具体起源并不明确,因为它是算法和数据结构领域中一个逐渐发展起来的概念

双指针法是一种常用的算法设计技巧,它在计算机科学的许多领域都有应用。这种方法可以通过两个移动的指针来遍历数据结构,如数组或链表,以解决特定类型的问题。例如,在数组中寻找两数之和等于特定值的问题,或者在链表中寻找倒数第k个节点的问题。双指针法可以提高效率,因为它通常只需要一次遍历即可找到解决方案。

在编程竞赛和算法教育中,双指针法经常被提及和教授,LeetCode等在线算法平台也有许多使用双指针法的示例和问题。然而,由于这是一种基础的算法技巧,很多早期的算法文献和教材都可能没有特别强调其起源,而是作为解决特定问题的常用方法来介绍。

总的来说,双指针法是一个在算法发展中自然形成的概念,它并没有一个明确的发明者,而是众多计算机科学家和工程师在解决问题时共同贡献的结果。
双指针法的最高级形态体现在其应用的多样性和复杂性上,能够解决各种复杂的算法问题。以下是一些高级应用:

  1. 滑动窗口算法:在数组问题中,双指针可以用来维护一个动态的窗口,例如在解决“盛最多水的容器”问题时,两个指针分别表示容器的左右边界,通过移动这两个指针来找到能装最多水的容器。
  2. 有序数组合并:当涉及到有序数组的合并时,双指针可以用来追踪下一个待选值可能出现的位置,从而高效地完成合并操作。
  3. 链表问题处理:在链表中,双指针可以用来处理断链问题,其中一个指针用于维护断链后链表的头部,另一个指针指向当前节点以完成特定操作。
  4. 对撞指针:在有序数组中,双指针可以从两端向中间遍历,这种方法适用于需要在有序数组中寻找特定元素或条件的问题。左指针从最左侧开始,右指针从最右侧开始,向中间移动,直到找到满足条件的解或者两指针相遇。
  5. 快慢指针:在链表问题中,快慢指针技巧可以用来检测链表中的环或者寻找倒数第k个节点。快指针先行k步,然后慢指针开始移动,当快指针到达链表末尾时,慢指针即位于倒数第k个节点。

总的来说,双指针法的最高级形态体现在其能够灵活应用于多种数据结构和算法问题中,通过精心设计的两个指针的移动策略,可以高效地解决问题。这些应用不仅提高了算法的效率,也展现了双指针法的强大能力和灵活性。

对撞指针是一种高效的算法技巧,用于在有序数组中查找特定元素或满足特定条件的元素对

对撞指针的工作原理是使用两个指针,一个从数组的开始位置(左指针)出发,另一个从数组的结束位置(右指针)出发。这两个指针向数组中间移动,直到它们相遇或者找到所需的元素。这种技术适用于有序数组,因为元素的有序性允许通过比较两个指针所指向的元素来确定继续搜索的方向。具体来说,对撞指针的应用可以分为以下几个方面:

  • 查找元素:当需要找到一个特定的值时,可以通过比较左右指针所指向的值与目标值的大小关系来决定是向左移动左指针还是向右移动右指针。这样可以逐步缩小搜索范围,直到找到目标值或者指针相遇。
  • 查找元素对:在某些问题中,比如需要找到两个数的和等于特定值的情况,也可以通过对撞指针来减少搜索时间。如果当前两指针之和小于目标值,则右指针向右移动;如果之和大于目标值,则左指针向左移动。这样不断迭代,直到找到满足条件的数对或者指针相遇。
  • 效率提升:由于每次迭代都在有序数组中排除了一半的搜索空间,对撞指针的效率非常高,时间复杂度通常为O(log n),其中n是数组的长度。

此外,对撞指针不仅提高了解决问题的效率,还简化了代码逻辑,使得算法更加简洁易懂。在实际应用中,对撞指针可以解决各种类型的问题,包括但不限于数组中的元素查找、有序数组的最大值和最小值查找、以及一些复杂的双元素问题。

总的来说,对撞指针是双指针法的一种高级应用,它利用了数组的有序特性,通过两个指针从两端向中间迭代,高效地解决了有序数组中的查找问题。

当然,下面是一些使用对撞指针的代码示例:

  1. 查找有序数组中的目标值
def binary_search(arr, target):
    left = 0
    right = len(arr) - 1
    
    while left <= right:
        mid = (left + right) // 2
        
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    
    return -1
  1. 查找有序数组中两个数之和等于特定值的元素对
def two_sum_sorted(arr, target):
    left = 0
    right = len(arr) - 1
    
    while left < right:
        current_sum = arr[left] + arr[right]
        
        if current_sum == target:
            return [left, right]
        elif current_sum < target:
            left += 1
        else:
            right -= 1
    
    return []
  1. 查找有序数组中第一个大于等于目标值的元素
def lower_bound(arr, target):
    left = 0
    right = len(arr) - 1
    
    while left <= right:
        mid = (left + right) // 2
        
        if arr[mid] >= target:
            right = mid - 1
        else:
            left = mid + 1
    
    return left

这些示例展示了对撞指针在有序数组中的不同应用。通过比较左右指针所指向的值与目标值的大小关系,可以逐步缩小搜索范围,直到找到目标值或者指针相遇。这种技巧在解决有序数组问题时非常有效,并且具有高效性和简洁性。

标签:历险记,slow,代码,fast,链表,数组,节点,指针
From: https://www.cnblogs.com/jjjkkklll/p/18151439

相关文章

  • Mac搭建appium环境及python运行代码示例
    Appium主要是通过调用安卓提供的接口来执行命令的,所以需要安装Java和安卓SDK。1.安装Appium服务端appium的服务端是基于node的,直接使用npm(node包管理器)安装即可,比较简单。npminstall-gappium2.安装Python客户端pipinstallAppium-Python-Client同样直接使用pip安装......
  • 《渣男代码历险记》第三章:天谴之子——光明与黑暗的对决
    “醒醒!醒醒!”“我这是在哪?”二。“你昏睡了34天了。”舅舅。“发生了什么?”舅舅哽咽了,半天说不出话来。“你。。。。。。是天谴之子。”“你为什么这样说?”“你只有放弃人类的未来,才能获得自身的幸福。现在全人类都被代码灭亡了。你答对一个计算机题目,就可以救若干个人类。......
  • 刚交付了智慧园区统一管理平台,前端零代码+后端零代码+物联网平台,完美搭配!(一)
    先看下交付效果,2人3个月完成,涉及120个页面,80来个接口,30多个二级模块。其中负责前端的实施工程师,无需懂任何代码编程。其中界面和功能,基本是严格按照墨刀的产品原型实现的: 记录下这个有意思的过程。我是后端工程师,前端开发懂一点点但也不多,平时开发底层为主,CURD项目业务......
  • 蓝牙打印开发代码示例(日拱一卒,有点进步,UUID仍报错)
     packagecom.lingrui.btprint;importandroid.Manifest;importandroid.app.AlertDialog;importandroid.bluetooth.BluetoothAdapter;importandroid.bluetooth.BluetoothDevice;importandroid.bluetooth.BluetoothSocket;importandroid.content.DialogInterface......
  • Python实现批量下载文件的示例代码
    前言下载文件是我们在日常工作中常常要做的一件事情。当我们需要从互联网上批量下载大量文件时,手动一个一个去下载显然不够高效。为了解决这个问题,我们可以使用Python编写一个批量下载文件的脚本,让它自动帮我们下载文件。同时,为了避免下载过程中被网站禁止访问,我们也可以运用代理......
  • DDD领域驱动设计总结和C#代码示例
    DDD(领域驱动设计)是一种软件设计方法,它强调以业务领域为核心来驱动软件的设计和开发。DDD的设计初衷是为了解决复杂业务领域的设计和开发问题,它提供了一套丰富的概念和模式,帮助开发者更好地理解和建模业务领域,从而提高软件的质量和可维护性。一、DDD主要组成DDD的主要模式包......
  • SpringBoot模块集成mqtt代码实现
    1//引入pom2<!--mqtt-->3<dependency>4<groupId>org.springframework.boot</groupId>5<artifactId>spring-boot-starter-integration</artifactId>6</dependency>......
  • 智能编码助手都在卷什么?投喂你的代码库,让Comate帮你解读
    越来越普遍的AI绘画、视频、小说等AIGC内容渗入生活的方方面面,超高的生成速度引发大量从业者为自己的饭碗而感到“压力山大”。在办公越来越讲究提效的当下,为了让自己的编码提速、提质,要么“鸡娃不如鸡自己”,硬生生练就键盘上的“佛山无影手”;要么“开点科技”,比如使用各路智能编......
  • 34天【代码随想录算法训练营34期】第八章 贪心算法 part03 (● 1005.K次取反后最大化
    1005.K次取反后最大化的数组和classSolution:deflargestSumAfterKNegations(self,nums:List[int],k:int)->int:nums.sort(key=lambdax:abs(x),reverse=True)foriinrange(len(nums)):ifnums[i]<0andk>0:......
  • 5种搭建LLM服务的方法和代码示例
    在不断发展的大型语言模型(LLMs)领域中,用于支持这些模型的工具和技术正以与模型本身一样快的速度进步。在这篇文章中,我们将总结5种搭建开源大语言模型服务的方法,每种都附带详细的操作步骤,以及各自的优缺点。 https://avoid.overfit.cn/post/efad539d0969474987a7ff652f632d8e......