#c

C++双指针算法基础

高效解决数组与字符串问题的技巧

#c
@鞠大龙
魅客科创中心

CONTENTS

目录

1. 双指针概述

1.1 算法复杂度概念简介

  • 时间复杂度:衡量算法执行时间随输入规模增长的变化趋势
  • 常见时间复杂度
    • :常数时间,不随输入规模变化
    • :对数时间,随输入规模对数增长
    • :线性时间,与输入规模成正比
    • :平方时间,与输入规模的平方成正比
  • 双指针的优势:将许多需要嵌套循环的问题从优化到
  • 实际意义:当时,算法需要约10000次操作,而算法需要约1亿次操作
返回目录 信息学 C++双指针算法基础 魅客科创中心

1.2 指针的含义

  • 指针的概念:在双指针算法中,"指针"是指向数据结构中特定位置的索引或标记
  • 与C++指针的区别
    • 双指针算法中的指针通常是数组索引(整数),而非实际内存地址
    • 不涉及直接的内存操作,仅用于标记处理位置
  • 指针的作用
    • 标记当前处理的位置
    • 协调两个位置之间的操作
    • 控制算法流程和边界条件
  • 指针的特点:根据问题要求按特定规则移动,通过协同工作解决问题
返回目录 信息学 C++双指针算法基础 魅客科创中心

1.3 双指针概述

  • 双指针(Two Pointers) : 是一种使用两个指针在线性结构(数组、字符串等)上协同工作以解决问题的算法技巧
  • 核心思想:通过控制两个指针的移动,将时间复杂度从 降低到
  • 基本前提:数据结构通常是数组、链表或字符串等线性结构
  • 双指针的两种主要形式
    • 同向双指针:两个指针从同一侧出发,通常用于滑动窗口快慢指针
    • 相向双指针:两个指针从两端向中间移动,通常用于查找、排序等,也成为碰撞双指针
返回目录 信息学 C++双指针算法基础 魅客科创中心

1.4 双指针的应用场景

  • 同向双指针的应用
    • 滑动窗口问题
      • 子数组/子串的最大/最小值
      • 满足特定条件的子数组/子串
    • 快慢指针问题
      • 链表中环的检测
      • 寻找链表的中点
      • 删除链表的倒数第N个节点
    • 数组操作
      • 移除数组中的特定元素
      • 合并两个有序数组
  • 相向双指针的应用

    • 两数之和问题
      • 查找和为特定值的两个元素
      • 三数之和、四数之和等变种
    • 字符串操作
      • 回文串的判断
      • 反转字符串
    • 排序算法
      • 快速排序中的分区操作
      • 归并排序中的合并操作
    • 容器盛水问题
      • 接雨水
      • 盛最多水的容器
返回目录 信息学 C++双指针算法基础 魅客科创中心

1.5 双指针的基本思想

双指针的工作原理

  1. 初始化:根据问题设置两个指针的初始位置
  2. 移动规则:根据问题要求和当前状态决定如何移动指针
  3. 终止条件:确定算法何时结束
  4. 结果处理:根据最终指针位置或过程中的状态得出结果

降低时间复杂度的原理

  • 利用问题的特性(如有序性、单调性)
  • 避免不必要的重复计算
  • 通过指针的协同移动跳过无效状态
  • 维护局部最优解,逐步构建全局最优解

简单例子

  1. 有序数组中的两数之和

    • 传统方法:两层循环,
    • 双指针方法:左右指针,
  2. 移除数组中的重复元素

    • 传统方法:额外数组存储,空间
    • 双指针方法:原地操作,空间
  3. 判断回文串

    • 传统方法:反转字符串比较,空间
    • 双指针方法:左右指针比较,空间
返回目录 信息学 C++双指针算法基础 魅客科创中心

2. 双指针的特点

2.1 双指针的特点

  • 高效性:时间复杂度通常为,比暴力方法快得多
  • 空间效率:通常只需要的额外空间,可以原地操作
  • 适用范围:主要用于线性数据结构(数组、链表、字符串)
  • 实现简单:思路清晰,代码简洁,易于理解和实现
  • 灵活多变:可以根据问题特点调整指针的初始位置、移动规则和终止条件
  • 前提条件:某些应用场景(如相向双指针)可能需要数据有序或满足特定性质
返回目录 信息学 C++双指针算法基础 魅客科创中心

2.2 双指针的优势与局限性

双指针的优势

  • 时间效率:将的问题优化到
  • 空间效率:通常只需要的额外空间
  • 原地操作:可以直接在原数据结构上操作,无需额外空间
  • 代码简洁:实现通常简单明了,易于理解
  • 适用性广:可以解决多种类型的问题

双指针的局限性

  • 适用范围:主要限于线性数据结构
  • 问题特性:某些问题需要数据满足特定性质(如有序)
  • 复杂场景:对于复杂的问题,可能需要结合其他算法技巧
  • 多指针扩展:某些问题可能需要三指针或更多指针
返回目录 信息学 C++双指针算法基础 魅客科创中心

2.3 双指针的适用场景

  1. 查找问题

    • 在有序数组中查找和为特定值的两个元素
    • 查找满足特定条件的子数组或子串
    • 查找数组中的重复元素
  2. 原地操作问题

    • 原地删除数组中的特定元素
    • 原地移除重复元素
    • 原地合并两个有序数组
  1. 字符串处理

    • 判断回文串
    • 反转字符串
    • 字符串匹配
  2. 区间问题

    • 盛最多水的容器
    • 接雨水问题
    • 区间合并
返回目录 信息学 C++双指针算法基础 魅客科创中心

3. 双指针的模板代码

3.1 同向双指针基本模板

// 同向双指针基本模板
void twoPointersSameDirection(vector<int>& nums) {
    int n = nums.size();
    int left = 0;  // 左指针初始位置
    int right = 0; // 右指针初始位置(也可以是其他位置)
    
    while (right < n) {
        // 更新状态或进行计算
        // ...
        
        // 移动右指针
        right++;
        
        // 根据条件移动左指针
        while (left < right && /* 某些条件 */) {
            // 可能需要在移动前更新状态
            // ...
            left++;
        }
        
        // 处理当前窗口的结果
        // ...
    }
    
    // 返回最终结果
    // ...
}
返回目录 信息学 C++双指针算法基础 魅客科创中心

3.2 相向双指针基本模板

// 相向双指针基本模板
void twoPointersOppositeDirection(vector<int>& nums) {
    int n = nums.size();
    int left = 0;          // 左指针初始位置
    int right = n - 1;     // 右指针初始位置
    
    while (left < right) {
        // 计算或比较当前指针指向的元素
        // ...
        
        // 根据条件移动指针
        if (/* 某些条件 */) {
            left++;
        } else {
            right--;
        }
        
        // 可能需要跳过重复元素
        while (left < right && nums[left] == nums[left - 1]) left++;
        while (left < right && nums[right] == nums[right + 1]) right--;
    }
    
    // 返回最终结果
    // ...
}
返回目录 信息学 C++双指针算法基础 魅客科创中心

3.3 快慢指针模板

// 快慢指针模板(以链表为例)
ListNode* fastSlowPointers(ListNode* head) {
    if (!head || !head->next) return head;
    
    ListNode* slow = head;        // 慢指针
    ListNode* fast = head->next;  // 快指针
    
    // 快指针每次移动两步,慢指针每次移动一步
    while (fast && fast->next) {
        slow = slow->next;        // 慢指针移动一步
        fast = fast->next->next;  // 快指针移动两步
        
        // 根据问题处理特定情况
        // 例如:检测环
        if (slow == fast) {
            // 找到环
            // ...
            break;
        }
    }
    
    // 返回结果(如链表中点、环的入口等)
    // ...
    return slow;  // 或其他结果
}
返回目录 信息学 C++双指针算法基础 魅客科创中心

3.4 滑动窗口模板

// 滑动窗口模板
int slidingWindow(vector<int>& nums, int target) {
    int n = nums.size();
    int left = 0;
    int right = 0;
    int sum = 0;  // 或其他状态变量
    int result = 0;  // 或其他结果变量
    
    while (right < n) {
        // 扩展窗口
        sum += nums[right];  // 更新状态
        right++;
        
        // 根据条件收缩窗口
        while (left < right && /* 窗口需要收缩的条件 */) {
            sum -= nums[left];  // 更新状态
            left++;
        }
        
        // 更新结果
        // ...
    }
    
    return result;
}
返回目录 信息学 C++双指针算法基础 魅客科创中心

3.5 双指针在有序数组中的应用模板

// 有序数组中的双指针模板(以两数之和为例)
vector<int> twoSum(vector<int>& nums, int target) {
    int n = nums.size();
    int left = 0;
    int right = n - 1;
    
    while (left < right) {
        int sum = nums[left] + nums[right];
        
        if (sum == target) {
            return {left, right};  // 找到目标
        } else if (sum < target) {
            left++;  // 和太小,增加左指针
        } else {
            right--;  // 和太大,减小右指针
        }
    }
    
    return {-1, -1};  // 未找到目标
}
返回目录 信息学 C++双指针算法基础 魅客科创中心

3.6 双指针在链表中的应用模板

// 链表中的双指针模板(以删除倒数第N个节点为例)
ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode* dummy = new ListNode(0);
    dummy->next = head;
    
    ListNode* first = dummy;
    ListNode* second = dummy;
    
    // 先让第一个指针前进n+1步
    for (int i = 0; i <= n; i++) {
        first = first->next;
    }
    
    // 两个指针同时前进,直到第一个指针到达末尾
    while (first) {
        first = first->next;
        second = second->next;
    }
    
    // 此时second指向要删除节点的前一个节点
    ListNode* temp = second->next;
    second->next = second->next->next;
    delete temp;
    
    ListNode* result = dummy->next;
    delete dummy;
    return result;
}
返回目录 信息学 C++双指针算法基础 魅客科创中心

3.7 双指针在字符串中的应用模板

// 字符串中的双指针模板(以判断回文串为例)
bool isPalindrome(string s) {
    int left = 0;
    int right = s.length() - 1;
    
    while (left < right) {
        // 跳过非字母数字字符
        while (left < right && !isalnum(s[left])) {
            left++;
        }
        while (left < right && !isalnum(s[right])) {
            right--;
        }
        
        // 比较字符(忽略大小写)
        if (tolower(s[left]) != tolower(s[right])) {
            return false;
        }
        
        left++;
        right--;
    }
    
    return true;
}
返回目录 信息学 C++双指针算法基础 魅客科创中心

3.8 双指针的常见错误与注意事项

双指针的常见错误与注意事项

  1. 边界条件处理:忘记检查数组为空或只有一个元素的情况。
  2. 指针越界:没有正确检查指针是否超出数组范围,导致访问无效内存。
  3. 指针移动规则:指针移动逻辑错误,导致无法覆盖所有情况或陷入死循环。
  4. 终止条件:设置不正确的循环终止条件,导致提前退出或无限循环。
  5. 重复元素处理:在需要去重的场景中,没有正确处理重复元素。
  6. 状态更新:在移动指针前没有正确更新状态变量或结果。
  7. 特殊情况:没有考虑问题中的特殊情况,如空字符串、负数等。
  8. 算法选择:使用双指针解决不适合的问题,导致算法复杂或低效。
返回目录 信息学 C++双指针算法基础 魅客科创中心

4. 实例分析

4.1 实例分析:两数之和(有序数组)

问题描述
给定一个已按照升序排列的整数数组numbers,请找出两个数,使得它们相加之和等于目标数target。函数应该返回这两个数的下标(从1开始计数),而且下标1必须小于下标2。

分析

  • 由于数组已经排序,可以使用相向双指针
  • 初始化左指针指向数组开头,右指针指向数组末尾
  • 计算两指针所指元素之和,与目标值比较
  • 如果和等于目标值,返回两个指针的位置
  • 如果和小于目标值,增加左指针(使和变大)
  • 如果和大于目标值,减小右指针(使和变小)
返回目录 信息学 C++双指针算法基础 魅客科创中心

4.1 实例分析:两数之和(有序数组)(代码示例)

// 两数之和(有序数组)
vector<int> twoSum(vector<int>& numbers, int target) {
    int left = 0;
    int right = numbers.size() - 1;
    
    while (left < right) {
        int sum = numbers[left] + numbers[right];
        
        if (sum == target) {
            // 题目要求返回的下标从1开始
            return {left + 1, right + 1};
        } else if (sum < target) {
            // 和太小,增加左指针
            left++;
        } else {
            // 和太大,减小右指针
            right--;
        }
    }
    
    // 题目保证有唯一解,所以不会到达这里
    return {-1, -1};
}

代码解析

  • 初始化左指针left为0,右指针right为数组末尾
  • 在循环中计算当前两指针所指元素之和
  • 根据和与目标值的比较,决定移动哪个指针
  • 找到目标值后,返回两个指针的位置(加1是因为题目要求从1开始计数)

复杂度分析

  • 时间复杂度:O(n),其中n是数组长度,最坏情况下需要遍历整个数组
  • 空间复杂度:O(1),只使用了常数额外空间

优势

  • 相比哈希表方法,不需要额外空间
  • 充分利用了数组的有序性质
返回目录 信息学 C++双指针算法基础 魅客科创中心

4.2 实例分析:移除元素

问题描述
给定一个数组nums和一个值val,你需要原地移除所有数值等于val的元素,并返回移除后数组的新长度。不要使用额外的数组空间,必须在原地修改输入数组并使用O(1)额外空间的条件下完成。

分析

  • 使用快慢指针的思想
  • 慢指针slow指向当前需要填入新元素的位置
  • 快指针fast用于遍历数组
  • fast指向的元素不等于val时,将其复制到slow位置,并增加slow
  • 最终slow的值就是新数组的长度
返回目录 信息学 C++双指针算法基础 魅客科创中心

4.2 实例分析:移除元素(代码示例)

// 移除元素
int removeElement(vector<int>& nums, int val) {
    int n = nums.size();
    int slow = 0;  // 慢指针,指向当前需要填入新元素的位置
    
    for (int fast = 0; fast < n; fast++) {
        // 如果当前元素不等于val,则保留
        if (nums[fast] != val) {
            nums[slow] = nums[fast];
            slow++;
        }
        // 如果当前元素等于val,则跳过(fast增加,slow不变)
    }
    
    // slow指向的位置就是新数组的长度
    return slow;
}

代码解析

  • 使用slow指针表示新数组的当前位置
  • 使用fast指针遍历原数组的每个元素
  • fast指向的元素不等于要移除的值时,将其复制到slow位置
  • 每次复制后,slow向前移动一步
  • fast指向的元素等于要移除的值时,只移动fast,不复制元素

复杂度分析

  • 时间复杂度:O(n),其中n是数组长度,只需遍历一次数组
  • 空间复杂度:O(1),只使用了常数额外空间,实现了原地操作

优势

  • 不需要额外的数组空间
  • 只需一次遍历即可完成
  • 保持了剩余元素的相对顺序
返回目录 信息学 C++双指针算法基础 魅客科创中心

4.3 实例分析:盛最多水的容器

问题描述
给定一个长度为n的整数数组height,有n条垂线,第i条线的两个端点是(i, 0)和(i, height[i])。找出其中的两条线,使得它们与x轴共同构成的容器可以容纳最多的水。

分析

  • 使用相向双指针,初始时指向数组的两端
  • 容器的面积由两个因素决定:两条线的距离和两条线中较短的那条
  • 面积计算公式:min(height[left], height[right]) * (right - left)
  • 为了找到可能的最大面积,每次移动较短的那条线对应的指针
  • 因为如果移动较长的线,容器的高度不会增加,而宽度会减小,面积一定会减小
返回目录 信息学 C++双指针算法基础 魅客科创中心

4.3 实例分析:盛最多水的容器(代码示例)

// 盛最多水的容器
int maxArea(vector<int>& height) {
    int n = height.size();
    int left = 0;
    int right = n - 1;
    int maxWater = 0;
    
    while (left < right) {
        // 计算当前容器的面积
        int h = min(height[left], height[right]);
        int w = right - left;
        int area = h * w;
        
        // 更新最大面积
        maxWater = max(maxWater, area);
        
        // 移动较短的那条线对应的指针
        if (height[left] < height[right]) {
            left++;
        } else {
            right--;
        }
    }
    
    return maxWater;
}

代码解析

  • 初始化左右指针分别指向数组的两端
  • 在循环中计算当前容器的面积
  • 更新记录到的最大面积
  • 移动较短的那条线对应的指针
    • 如果左边较短,移动左指针
    • 如果右边较短或两边相等,移动右指针

复杂度分析

  • 时间复杂度:O(n),其中n是数组长度,只需遍历一次数组
  • 空间复杂度:O(1),只使用了常数额外空间

贪心策略

  • 每次都移动较短的那条线,因为这样才有可能得到更大的面积
  • 如果移动较长的线,容器的高度不会增加,而宽度会减小,面积一定会减小
返回目录 信息学 C++双指针算法基础 魅客科创中心

4.4 实例分析:三数之和

问题描述
给定一个包含n个整数的数组nums,判断nums中是否存在三个元素a, b, c,使得a + b + c = 0?找出所有满足条件且不重复的三元组。

分析

  • 首先对数组进行排序,这样可以使用双指针技巧
  • 固定第一个数,然后使用双指针寻找剩下两个数
  • 为了避免重复解,需要跳过重复的元素
  • 当三数之和等于0时,记录结果并移动双指针
  • 当三数之和小于0时,增加左指针
  • 当三数之和大于0时,减小右指针
返回目录 信息学 C++双指针算法基础 魅客科创中心

4.4 实例分析:三数之和(代码示例)

// 三数之和
vector<vector<int>> threeSum(vector<int>& nums) {
    int n = nums.size();
    vector<vector<int>> result;
    
    // 排序
    sort(nums.begin(), nums.end());
    
    for (int i = 0; i < n - 2; i++) {
        // 跳过重复的第一个数
        if (i > 0 && nums[i] == nums[i - 1]) {
            continue;
        }
        
        int target = -nums[i];
        int left = i + 1;
        int right = n - 1;
        
        while (left < right) {
            int sum = nums[left] + nums[right];
            
            if (sum == target) {
                // 找到一组解
                result.push_back({nums[i], nums[left], nums[right]});
                
                // 跳过重复元素
                while (left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                while (left < right && nums[right] == nums[right - 1]) {
                    right--;
                }
                
                left++;
                right--;
            } else if (sum < target) {
                left++;
            } else {
                right--;
            }
        }
    }
    
    return result;
}

代码解析

  • 首先对数组进行排序,使相同的元素相邻
  • 使用三重循环:外层循环固定第一个数,内层使用双指针寻找另外两个数
  • 为避免重复解:
    • 跳过重复的第一个数
    • 找到解后,跳过重复的第二个和第三个数
  • 根据三数之和与0的比较,决定如何移动双指针

复杂度分析

  • 时间复杂度:O(n²),其中n是数组长度
    • 排序需要O(n log n)
    • 双指针部分需要O(n²)
  • 空间复杂度:O(log n)到O(n),取决于排序算法的空间复杂度

优化

  • 当nums[i] > 0时可以提前退出,因为三个正数之和不可能为0
  • 当nums[i] + nums[i+1] + nums[i+2] > 0时可以提前退出
  • 当nums[i] + nums[n-2] + nums[n-1] < 0时可以跳过当前i
返回目录 信息学 C++双指针算法基础 魅客科创中心

4.5 实例分析:链表中环的检测

问题描述
给定一个链表,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环。

分析

  • 使用快慢指针(Floyd's Tortoise and Hare算法)
  • 慢指针每次移动一步,快指针每次移动两步
  • 如果存在环,两个指针最终会在环中相遇
  • 如果不存在环,快指针会先到达链表末尾

进阶:找到环的入口点

  • 当快慢指针相遇时,将其中一个指针重置到链表头部
  • 然后两个指针都每次移动一步
  • 它们会在环的入口点相遇
返回目录 信息学 C++双指针算法基础 魅客科创中心

4.5 实例分析:链表中环的检测(代码示例)

// 检测链表中的环
bool hasCycle(ListNode *head) {
    if (!head || !head->next) {
        return false;
    }
    
    ListNode *slow = head;
    ListNode *fast = head;
    
    while (fast && fast->next) {
        slow = slow->next;       // 慢指针移动一步
        fast = fast->next->next; // 快指针移动两步
        
        if (slow == fast) {
            return true;  // 指针相遇,存在环
        }
    }
    
    return false;  // 快指针到达末尾,不存在环
}

代码解析

  • 初始化慢指针slow和快指针fast都指向链表头
  • 在循环中,慢指针每次移动一步,快指针每次移动两步
  • 如果两个指针相遇,说明存在环
  • 如果快指针到达链表末尾(即fast为NULL或fast->next为NULL),说明不存在环

复杂度分析

  • 时间复杂度:O(n),其中n是链表长度
  • 空间复杂度:O(1),只使用了两个指针

Floyd's算法原理

  • 如果存在环,快指针每次比慢指针多走一步
  • 可以看作快指针在追赶慢指针,每次缩小距离1
  • 因此,如果存在环,快指针一定会追上慢指针
返回目录 信息学 C++双指针算法基础 魅客科创中心

4.6 实例分析:滑动窗口最大值

问题描述
给定一个数组nums和滑动窗口的大小k,请找出所有滑动窗口里的最大值。

分析

  • 使用双端队列来维护一个单调递减的队列
  • 队列中存储的是元素的索引,而不是元素本身
  • 队首元素始终是当前窗口中的最大值
  • 当新元素进入窗口时:
    • 移除队列中所有小于新元素的值(保持单调性)
    • 将新元素的索引加入队列尾部
  • 当元素离开窗口时,如果它在队首,则将其移除
返回目录 信息学 C++双指针算法基础 魅客科创中心

4.6 实例分析:滑动窗口最大值(代码示例)

// 滑动窗口最大值
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    int n = nums.size();
    vector<int> result;
    deque<int> dq;  // 存储索引的双端队列
    
    for (int i = 0; i < n; i++) {
        // 移除离开窗口的元素
        if (!dq.empty() && dq.front() == i - k) {
            dq.pop_front();
        }
        
        // 移除队列中所有小于当前元素的值
        while (!dq.empty() && nums[dq.back()] < nums[i]) {
            dq.pop_back();
        }
        
        // 将当前元素的索引加入队列
        dq.push_back(i);
        
        // 当窗口形成后,记录最大值
        if (i >= k - 1) {
            result.push_back(nums[dq.front()]);
        }
    }
    
    return result;
}

代码解析

  • 使用双端队列dq存储元素的索引
  • 队列中的索引对应的元素保持单调递减
  • 对于每个新元素:
    • 检查队首元素是否已离开窗口
    • 移除队列中所有小于当前元素的值
    • 将当前元素的索引加入队列
    • 当窗口形成后,队首元素就是当前窗口的最大值

复杂度分析

  • 时间复杂度:O(n),其中n是数组长度
    • 每个元素最多入队和出队各一次
  • 空间复杂度:O(k),队列的大小最多为k

优势

  • 每个窗口的最大值可以在O(1)时间内获取
  • 避免了暴力方法的O(nk)时间复杂度
返回目录 信息学 C++双指针算法基础 魅客科创中心

5. 练习题推荐

5.1 基础练习题

  1. [POJ 2739] Sum of Consecutive Prime Numbers

    • 难度:★★☆☆☆
    • 描述:找出所有连续质数的和等于给定数的方案数
    • 知识点:同向双指针,滑动窗口
  2. [HDU 1392] Surround the Trees

    • 难度:★★☆☆☆
    • 描述:求包围所有点的最小周长凸多边形
    • 知识点:相向双指针,凸包算法
  3. [CSP-J 2019] 数列

    • 难度:★★☆☆☆
    • 描述:在有序数组中查找满足特定条件的元素
    • 知识点:相向双指针,二分查找
返回目录 信息学 C++双指针算法基础 魅客科创中心

5.2 中级练习题

  1. [USACO 2016 Feb Silver] Load Balancing

    • 难度:★★★☆☆
    • 描述:通过划分平面使四个区域中的点数尽可能平衡
    • 知识点:双指针扫描,二分思想
  2. [NOIP 2015] 子串

    • 难度:★★★☆☆
    • 描述:求两个字符串的最长公共子串
    • 知识点:滑动窗口,字符串匹配
  3. [CSES 1640] Sum of Two Values

    • 难度:★★★☆☆
    • 描述:在数组中找到和为给定值的两个元素
    • 知识点:相向双指针,哈希表
返回目录 信息学 C++双指针算法基础 魅客科创中心

5.3 高级练习题

  1. [CSP-S 2019] 树的重心

    • 难度:★★★★☆
    • 描述:求树的重心,使得删除该节点后的最大连通块最小
    • 知识点:树上双指针,深度优先搜索
  2. [POJ 3061] Subsequence

    • 难度:★★★★☆
    • 描述:求和不小于给定值的最短连续子数组
    • 知识点:滑动窗口,前缀和
  3. [HDU 3549] Flow Problem

    • 难度:★★★★☆
    • 描述:求网络最大流
    • 知识点:双指针在图论中的应用,最大流算法
返回目录 信息学 C++双指针算法基础 魅客科创中心

5.4 综合应用题

  1. [NOIP 2016] 换教室

    • 难度:★★★★★
    • 描述:安排课程使得换教室的次数最少
    • 知识点:双指针与动态规划结合
  2. [USACO 2019 Jan Gold] Sleepy Cow Sorting

    • 难度:★★★★★
    • 描述:求将数组排序所需的最少操作次数
    • 知识点:双指针与贪心算法结合
  3. [IOI 2015] Teams

    • 难度:★★★★★
    • 描述:将学生分成若干组,使得每组的能力值在给定范围内
    • 知识点:双指针与二分查找结合
返回目录 信息学 C++双指针算法基础 魅客科创中心

6. 总结

6.1 总结

双指针的核心要点

  • 基本思想

    • 使用两个指针协同工作
    • 避免不必要的重复计算
    • 将时间复杂度从O(n²)降低到O(n)
  • 主要类型

    • 同向双指针(滑动窗口、快慢指针)
    • 相向双指针(两端向中间)
  • 常见应用

    • 数组和字符串处理
    • 链表操作
    • 查找和排序
    • 区间问题

实践建议

  1. 编码前

    • 确认问题是否适合用双指针
    • 明确指针的初始位置和移动规则
    • 设计好终止条件
  2. 编码时

    • 选择合适的双指针类型
    • 注意边界条件
    • 处理好特殊情况
  3. 调试时

    • 检查指针移动逻辑
    • 验证终止条件
    • 测试特殊输入
    • 确认结果正确性
返回目录 信息学 C++双指针算法基础 魅客科创中心

6.2 进阶方向

双指针的扩展

  1. 多指针技术

    • 三指针、四指针等
    • 用于更复杂的问题
  2. 双指针与其他算法结合

    • 双指针 + 二分查找
    • 双指针 + 动态规划
    • 双指针 + 贪心算法
    • 双指针 + 数据结构(如堆、单调队列)
  3. 高维双指针

    • 在二维数组中应用双指针
    • 在图结构中应用双指针
  4. 并行双指针

    • 多线程环境下的优化
    • 分布式系统中的应用

实际应用中的注意事项

  1. 在使用双指针之前,先考虑:

    • 问题是否具有线性特性
    • 是否可以通过指针移动降低复杂度
    • 是否有更简单的解法
  2. 优化建议:

    • 预处理数据(如排序)
    • 结合其他数据结构
    • 考虑问题的特殊性质
    • 注意代码可读性
  3. 记住:

    • 简单问题用简单方法
    • 正确性优先于效率
    • 可维护性很重要
返回目录 信息学 C++双指针算法基础 魅客科创中心
C++双指针算法基础

#c

这个讲义使用了Awesome Marp主题的蓝色风格。 演讲者备注不会在演示模式中显示给观众,仅供演讲者参考。 每个关键页面都添加了备注,帮助演讲者更好地讲解内容。

封面页: - 欢迎学生,简要介绍本次课程的主题和目标 - 强调双指针是算法设计中的基础技巧,也是竞赛和面试中的常见题型 - 可以简单提及本课程将从基础概念到实际应用,全面讲解双指针的思想和技巧

目录页: - 简要介绍本次讲座的结构和内容安排 - 可以提及讲座的逻辑流程:从基础概念到具体应用,再到实践建议 - 强调我们将通过多个例子来深入理解双指针的应用 - 提醒学生关注每个部分之间的联系,以及如何将理论应用到实践中

转场页: - 这是一个简单的转场页,用于引入双指针概述部分 - 可以简单提及双指针是算法设计中的重要策略,有着广泛的应用

算法复杂度概念简介: - 介绍时间复杂度的基本概念 - 解释为什么需要优化算法复杂度 - 说明双指针算法如何优化时间复杂度

指针的含义: - 解释双指针算法中"指针"的概念 - 说明这里的指针与C++中实际内存指针的区别 - 介绍指针在算法中的作用

双指针概述: - 介绍双指针的基本概念和核心思想 - 强调双指针的高效性和应用场景 - 简要提及双指针的历史和发展

双指针的应用场景: - 介绍双指针的主要应用场景 - 讨论双指针在不同问题中的应用 - 强调双指针的重要性和普遍性

双指针的基本思想: - 详细解释双指针的工作原理 - 讨论如何通过双指针降低时间复杂度 - 提供一些简单的例子来说明双指针的思想

转场页: - 引入双指针的特点部分 - 可以简单提及理解这些特点有助于更好地应用双指针

双指针的特点: - 详细介绍双指针的主要特点 - 强调这些特点如何帮助识别和解决双指针问题 - 解释双指针的优势和局限性

双指针的优势与局限性: - 分析双指针的优势和局限性 - 讨论何时应该选择双指针 - 提供一些优化双指针的思路

双指针的适用场景: - 详细介绍双指针适用的各种场景 - 提供一些实际应用的例子 - 讨论如何识别可以使用双指针的问题

转场页: - 引入双指针的模板代码部分 - 可以简单提及掌握这些模板有助于快速实现双指针算法

同向双指针基本模板: - 提供同向双指针的基本代码模板 - 解释模板中的关键部分 - 强调指针移动规则的重要性

相向双指针基本模板: - 提供相向双指针的基本代码模板 - 解释与同向双指针的区别 - 强调初始位置和终止条件的设置

快慢指针模板: - 提供快慢指针的代码模板 - 解释快慢指针的特点和应用 - 强调快慢指针在链表操作中的重要性

滑动窗口模板: - 提供滑动窗口的代码模板 - 解释滑动窗口与同向双指针的关系 - 强调窗口大小调整的策略

双指针在有序数组中的应用模板: - 提供在有序数组中使用双指针的代码模板 - 解释有序性如何帮助优化算法 - 强调这种模板在查找问题中的应用

双指针在链表中的应用模板: - 提供在链表中使用双指针的代码模板 - 解释链表特性如何影响双指针的使用 - 强调这种模板在链表操作中的应用

双指针在字符串中的应用模板: - 提供在字符串中使用双指针的代码模板 - 解释字符串特性如何影响双指针的使用 - 强调这种模板在字符串处理中的应用

双指针的常见错误与注意事项: - 列举双指针实现中的常见错误 - 提供避免这些错误的方法 - 强调细节的重要性

转场页: - 引入实例分析部分 - 可以简单提及我们将通过几个经典例子来深入理解双指针

两数之和问题: - 详细介绍两数之和问题的描述和分析 - 解释如何使用双指针解决这个问题 - 强调有序性在问题解决中的重要性

两数之和问题的代码实现: - 提供完整的代码实现 - 解释代码中的关键部分 - 分析算法的时间和空间复杂度

移除元素问题: - 详细介绍移除元素问题的描述和分析 - 解释如何使用双指针解决这个问题 - 强调原地操作的实现方法

移除元素问题的代码实现: - 提供完整的代码实现 - 解释代码中的关键部分 - 分析算法的时间和空间复杂度

盛最多水的容器问题: - 详细介绍盛最多水的容器问题的描述和分析 - 解释如何使用双指针解决这个问题 - 强调贪心思想在问题解决中的应用

盛最多水的容器问题的代码实现: - 提供完整的代码实现 - 解释代码中的关键部分 - 分析算法的时间和空间复杂度

三数之和问题: - 详细介绍三数之和问题的描述和分析 - 解释如何使用双指针解决这个问题 - 强调排序和去重的重要性

三数之和问题的代码实现: - 提供完整的代码实现 - 解释代码中的关键部分 - 分析算法的时间和空间复杂度

链表中环的检测问题: - 详细介绍链表中环的检测问题的描述和分析 - 解释如何使用快慢指针解决这个问题 - 强调Floyd's Tortoise and Hare算法的原理

链表中环的检测问题的代码实现: - 提供完整的代码实现 - 解释代码中的关键部分 - 分析算法的时间和空间复杂度

滑动窗口最大值问题: - 详细介绍滑动窗口最大值问题的描述和分析 - 解释如何使用滑动窗口和双指针解决这个问题 - 强调数据结构选择的重要性

滑动窗口最大值问题的代码实现: - 提供完整的代码实现 - 解释代码中的关键部分 - 分析算法的时间和空间复杂度

转场页: - 引入练习题推荐部分 - 可以简单提及练习是掌握双指针的关键

基础练习题: - 提供一系列适合初学者的双指针练习题 - 简要介绍每道题的内容和难度 - 提供题目链接或参考资料

中级练习题: - 提供一系列适合有一定基础的学习者的双指针练习题 - 简要介绍每道题的内容和难度 - 提供题目链接或参考资料

高级练习题: - 提供一系列适合有较强基础的学习者的双指针练习题 - 简要介绍每道题的内容和难度 - 提供题目链接或参考资料

综合应用题: - 提供一系列需要综合运用多种算法技巧的双指针练习题 - 简要介绍每道题的内容和难度 - 提供题目链接或参考资料

转场页: - 引入总结部分 - 可以简单提及总结将回顾本次讲座的主要内容

总结: - 回顾双指针的核心概念和特点 - 强调双指针的应用场景和解题思路 - 提供一些学习和实践的建议

进阶方向: - 介绍双指针的进阶内容和优化技巧 - 提供一些实践建议和学习资源 - 鼓励学生继续深入学习和实践

封底页: - 感谢学生的参与 - 提供联系方式或其他资源 - 鼓励学生继续学习和实践