【数据结构与算法】之多指针算法经典问题

简介: 【数据结构与算法】之多指针算法经典问题

一、链表反转


链表反转公用代码:

public class ReverseLink {
    public static void main(String[] args) {
    }
    // 遍历的方法
    public static void print(Node<Integer> head) {
        Node<Integer> current = head;
        while (current != null) {
            System.out.println(current.t);
            current = current.next;
        }
    }
    private static class Node<T> {
        private T t;
        private Node<T> next = null;
        public Node(T t) {
            this.t = t;
        }
    }
    private static Node<Integer> buildLink() {
        Node<Integer> head = new Node<>(1);
        Node<Integer> node2 = new Node<>(2);
        Node<Integer> node3 = new Node<>(3);
        Node<Integer> node4 = new Node<>(4);
        Node<Integer> node5 = new Node<>(5);
        head.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        return head;
    }
}

1️⃣迭代反转链表


链表翻转最大的问题就是,对于单链表而言,引用一旦指向新的节点,就会于之前关联的节点失去联系,所以我们可以使用三个引用临时保存需要操作的节点:


741b376ebe1346e19224112562df19ae.png


第一个和第二个引用用来进行反转操作

第三个引用用来保存后边的节点,防止关联失效


c2eac7efeaca4fca8b5fe15b5cfbac65.png


以后每一步操作,只需要将三个临时节点统一向后移动即可,这就是一个遍历的过程


e766a3d2711b4aad87a6d89e6b301924.png

代码如下:

// 采用单个指针的方式,迭代进行逆转
public static Node<Integer> reverseLink(Node<Integer> head) {
    if (head == null || head.next == null) {
        return head;
    }
    // 定义三个引用,分别代表当前节点,以及他的前后节点
    Node<Integer> prevNode = null;
    Node<Integer> current = head;
    Node<Integer> nextNode = head.next;
    // 先进行一个翻转翻转
    current.next = prevNode;
    while (nextNode != null) {
        // 三个指针,统一移动
        prevNode = current;
        current = nextNode;
        nextNode = current.next;
        // 翻转
        current.next = prevNode;
        // 确定到达尾部,定义新的头结点
        if (nextNode == null) {
            head = current;
        }
    }
    return head;
}

2️⃣递归反转


  • 递归翻转的思路和之前的思路恰好相反,递归往往都是这个样子:

2d930bb2fa51443d8efa96e744c62a66.png

我们使用递归将后续的节点成功反转:


98fe23dd4f4d4e57b1d4d0d3e59da87e.png


接下来只需要将A和B反转即可。

代码如下:

// 递归遍历链表
public static Node<Integer> recursiveReverseLink(Node<Integer> head) {
    if (head == null || head.next == null) {
        return head;
    }
    // 上边的A和B的反转,node是反转后的头节点
    Node<Integer> node = recursiveReverseLink(head.next);
    head.next.next = head;
    head.next = null;
    return node;
}


3️⃣头插法反转


头插法的思路比较简单,就是从头开始遍历,每次摘下一个节点,然后使用头插法,拼接成一个新的链表:


b6c57f05ca2244fc9d610c46fde70db2.png


代码如下:

// 使用头插法
public static Node<Integer> headInsertReverseLink(Node<Integer> head) {
    if (head == null || head.next == null) {
        return head;
    }
    // newHead表示的是新建的链表的头结点
    Node<Integer> newHead = null, temp;
    while (head != null) {
        // 临时节点指向head
        temp = head;
        // head后移,相当于将头结点摘除了
        head = head.next;
        temp.next = newHead;
        newHead = temp;
    }
    return newHead;
}


二、双指针-快慢指针


1️⃣寻找单向无环链表的中点


2a0718eb1f7943f5a95b7e3e1a4af5ca.png


代码如下:

public static Node findMiddle(Node head){
    Node fast = head,slow = head;
    while (fast != null && fast.next != null){
        fast = fast.next.next;
        slow = slow.next;
    }
    return slow;
}


2️⃣判断单向链表是否有环及找环入口


题目: 给定一个单向的链表,判断该链表是否有换,如果不存在换返回null,如果存在,则返回链表开始入环的第一个节点。


说明: 不允许修改给定的链表。如下图:应该返回C这个节点。


fb2e1e93473a4295995bcfb5a9b686c3.png

判断有没有环思路: 我们同样使用快慢指针,fast 与slow,一旦fast追上slow就说明存在环。


寻找换的入口,是一个比较麻烦的事情,我们有基本的数学推导如下,这里有个一直条件,fast一旦追上slow说明fast比slow正好快了一圈。


如下图所示,设链表中环外部分的长度为 a。slow 指针进入环后,又走了 b 的距离与 fast 相遇。此时,fast 指针已经走完了环的 n 圈,因此它走过的总距离为 :


a + ( b + c ) + b = a + 2 b + c


image.png

根据题意,任意时刻,fast 指针走过的距离都为 slow 指针的 2 倍。因此,我们有:


a + 2 b + c = 2 ( a + b ) ⟹ a = c


有了这个等量关系,我们会发现:在我们的题目中,从相遇点到入环点的距离,恰好等于从链表头部到入环点的距离。


因此,当发现 slow 与 fast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和slow 每次向后移动一个位置。最终,它们会在入环点相遇。


代码如下:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode slow = head, fast = head;
        while (fast != null) {
            slow = slow.next;
            if (fast.next != null) {
                fast = fast.next.next;
            } else {
                return null;
            }
            if (fast == slow) {
                ListNode ptr = head;
                while (ptr != slow) {
                    ptr = ptr.next;
                    slow = slow.next;
                }
                return ptr;
            }
        }
        return null;
    }
}

三、双指针-左右指针


1️⃣两数之和


输入一个【有序数组】和一个目标值,找到数组中的两个数相加等于目标值,输出两个数字的下标:


image.png

代码如下:

public int[] twoSum(int[] nums,int target){
    int left = 0,right = nums.length -1;
    while (left < right){
        int sum = nums[left] + nums[right];
        if(sum == target){
            return new int[]{left+1,right+1};
        } else if(sum < target){
            left++;
        } else if (sum > target){
            right--;
        }
    }
    return new int[]{-1,-1};
}

2️⃣二分查找


给定一个有序数组,和一个目标值,找出目标值出现的位置,返回下标,找不到则返回-1:

image.png


代码如下:

public static int binarySearch(int[] nums,int target){
    int left = 0,right = nums.length -1;
    while (left <= right){
        int middle = (left + right)/2;
        if(nums[middle] == target){
            return middle;
        } else if(nums[middle] < target){
            left = middle+1;
        } else if (nums[middle] > target){
            right = middle -1;
        }
    }
    return -1;
}


相关文章
|
13天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
23 1
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
90 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
14天前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
13天前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
21天前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
82 23
|
21天前
|
算法
数据结构之蜜蜂算法
蜜蜂算法是一种受蜜蜂觅食行为启发的优化算法,通过模拟蜜蜂的群体智能来解决优化问题。本文介绍了蜜蜂算法的基本原理、数据结构设计、核心代码实现及算法优缺点。算法通过迭代更新蜜蜂位置,逐步优化适应度,最终找到问题的最优解。代码实现了单链表结构,用于管理蜜蜂节点,并通过适应度计算、节点移动等操作实现算法的核心功能。蜜蜂算法具有全局寻优能力强、参数设置简单等优点,但也存在对初始化参数敏感、计算复杂度高等缺点。
56 20
|
21天前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
43 4
|
21天前
|
机器学习/深度学习 算法 C++
数据结构之鲸鱼算法
鲸鱼算法(Whale Optimization Algorithm,WOA)是由伊朗研究员Seyedali Mirjalili于2016年提出的一种基于群体智能的全局优化算法,灵感源自鲸鱼捕食时的群体协作行为。该算法通过模拟鲸鱼的围捕猎物和喷出气泡网的行为,结合全局搜索和局部搜索策略,有效解决了复杂问题的优化需求。其应用广泛,涵盖函数优化、机器学习、图像处理等领域。鲸鱼算法以其简单直观的特点,成为初学者友好型的优化工具,但同时也存在参数敏感、可能陷入局部最优等问题。提供的C++代码示例展示了算法的基本实现和运行过程。
43 0
|
2月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
38 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
21天前
|
算法 vr&ar 计算机视觉
数据结构之洪水填充算法(DFS)
洪水填充算法是一种基于深度优先搜索(DFS)的图像处理技术,主要用于区域填充和图像分割。通过递归或栈的方式探索图像中的连通区域并进行颜色替换。本文介绍了算法的基本原理、数据结构设计(如链表和栈)、核心代码实现及应用实例,展示了算法在图像编辑等领域的高效性和灵活性。同时,文中也讨论了算法的优缺点,如实现简单但可能存在堆栈溢出的风险等。
35 0