【双指针算法】——还不会双指针?看这里一篇就能让你明白其中的奥妙

1. 移动零 - Move Zeroes

https://leetcode.cn/problems/move-zeroes/
在这里插入图片描述

解题思路:使用双指针法,遍历数组时遇到非零元素就与另一个指针位置交换,然后非零指针往前移动一位。这样可以在一次遍历中将所有非零元素移动到前面,并将零元素移动到末尾。

详细解题思路:
使用双指针法来处理:j 指针用于指向非零元素应放置的位置,i 指针遍历数组。
初始时 j 指向数组起始位置。
遍历数组时,如果 nums[i] 不为零,将 nums[i] 与 nums[j] 交换位置,然后 j 向前移动一位。
这样,j指针始终指向下一个非零元素应放置的位置。
在遍历结束后,所有的非零元素会被移动到数组的前面,零元素会自动移到数组的末尾。

class Solution {
public:
    void moveZeroes(vector<int>& nums) 
    {
        // cur 指针表示当前遍历到的数组元素
        int cur = 0;
        // dest 指针表示非零元素要放置的位置,初始化为 -1
        int dest = -1;

        // 遍历整个数组
        while (cur < nums.size())
        {
            // 如果当前元素不为零,将其移动到 dest 指向的位置
            if (nums[cur] != 0)
            {
                dest++;
                // 交换当前位置 cur 和 dest 位置的元素
                swap(nums[cur], nums[dest]);
            }
            // 移动到下一个元素
            cur++;
        }
    }
};

2. 复写零 - Duplicate Zeros

https://leetcode.cn/problems/duplicate-zeros/
在这里插入图片描述

解题思路:先遍历数组,统计出需要复制的零的数量,从最后一个元素向前遍历。对于每个元素,计算出其应该放置的新位置,遇到零时将其复制到新的位置上,并减少零计数。

详细解题思路:
首先遍历数组,统计出需要复制的零的总数量,这样可以确定扩展后的数组长度。
从最后一个元素开始反向遍历,对于每个元素计算它应该被放置的位置。
如果当前位置的元素为零,且扩展后的数组未超出原数组范围,则复制零。 通过反向遍历确保我们不会丢失数据,并且保持数组的长度不变。
这样在一次遍历中可以完成零的复制和位置调整。

class Solution {
public:
    void duplicateZeros(vector<int>& arr) 
    {
        // 定义两个指针 cur 和 dest。cur 用于遍历数组,dest 用于表示最终位置。
        int cur = 0, dest = -1;

        // 找到可以“复写”的最后一个零的位置
        for (cur = 0, dest = -1; cur < arr.size(); cur++)
        {
            if (arr[cur] == 0) // 如果当前元素是零,dest 需要增加 2 来模拟复写
            {
                dest += 2;
            }
            else // 非零元素只需要 dest 增加 1
            {
                dest++;
            }
            
            // 当 dest 超过或到达数组边界时,跳出循环
            if (dest >= arr.size() - 1)
                break;
        }

        // 处理 dest 越界的情况
        if (dest == arr.size()) // 如果 dest 正好越过边界
        {
            arr[arr.size() - 1] = 0; // 将最后一个元素设置为零
            dest -= 2; // 回到上一个可以放置的位置
            cur--; // 复写指针 cur 向前移动一步
        }

        // 从后往前遍历,执行实际的复制操作
        for (cur, dest; cur >= 0; cur--)
        {
            if (arr[cur] == 0) // 如果当前元素是零,需要复制两次
            {
                arr[dest--] = arr[cur]; // 先复制一个零
                arr[dest--] = 0; // 再复制一个零
            }
            else // 如果不是零,正常复制
            {
                arr[dest--] = arr[cur];
            }
        }
    }
};

3.快乐数 - Happy Number

https://leetcode.cn/problems/happy-number/
在这里插入图片描述
解题思路:使用集合记录已出现过的平方和值,在不断迭代计算平方和的过程中检查是否出现循环。如果平方和为1则是快乐数,出现循环则不是。

详细解题思路:
对于给定的数字 n,计算其每个位上的数字平方和,将其作为下一个数字 n。
使用集合记录出现过的数字,如果平方和重复出现,则说明陷入循环,不是快乐数。
如果最终平方和为1,则是快乐数。
通过集合实现环检测,当一个数字再次出现时,表示开始循环,这样可以避免无限循环。

class Solution 
{
    // 定义一个辅助函数 bitsum,用于计算数字每位数字的平方和
    int bitsum(int n)
    {
        int sum = 0;
        // 通过循环获取每位数字,并计算它们的平方和
        while (n)
        {
            int tmp = n % 10; // 获取最后一位数字
            sum += tmp * tmp; // 计算该位的平方并加入到 sum 中
            n /= 10; // 去掉最后一位数字
        }
        return sum; // 返回最终的平方和
    }

public:
    // 定义主函数 isHappy,用于判断一个数是否为快乐数
    bool isHappy(int n) 
    {
        // 使用快慢指针法来检测循环
        int fast = bitsum(n); // 快指针,每次走两步
        int slow = n; // 慢指针,每次走一步

        // 当快指针和慢指针相遇时,如果等于1则是快乐数,否则存在循环
        while (fast != slow)
        {
            slow = bitsum(slow); // 慢指针每次计算一个 bitsum
            fast = bitsum(bitsum(fast)); // 快指针每次计算两次 bitsum
        }
        
        // 如果最后 slow == 1,则说明是快乐数
        return slow == 1;
    }
};

4. 盛最多水的容器 - Container With Most Water

https://leetcode.cn/problems/container-with-most-water/
在这里插入图片描述
解题思路:利用双指针法,分别指向数组的两端,根据短板效应移动较小的一端指针,以尝试找到更高的边界。更新最大容积值,直到两个指针相遇。

详细解题思路:
使用双指针分别指向数组的两端。
计算当前左右指针对应的高度和两指针间的距离形成的容积,并记录最大容积。
每次比较左右指针高度,将较短的指针向内移动,以尝试找到更大的容积。
根据“木桶效应”,容积由较小的高度决定,因此移动较小的一端可以更有效地找到可能的最大容积。
重复上述步骤直到两指针相遇,即遍历了所有可能的容积。

class Solution 
{
public:
    int maxArea(vector<int>& height) 
    {
        // 定义两个指针,分别指向容器的左右边界
        int left = 0;
        int right = height.size() - 1;
        int ret = 0; // 用于存储最大面积的变量

        // 通过双指针的方法来寻找最大容积
        while (left < right)
        {
            // 使用两个边界的较小值作为容器的高度,乘以两指针之间的宽度得到面积
            // 容器的高度由最短的边决定,宽度为 right - left
            int area = min(height[left], height[right]) * (right - left);
            // 更新最大面积
            ret = max(ret, area);
            
            // 移动指针以尝试找到更大的容器
            // 哪一边的高度更小,就移动哪一边的指针
            // 因为移动较高的一边不会增加面积,只能通过增加宽度或者找到更高的边来增大面积
            if (height[left] < height[right]) 
                left++;  // 左边高度较小时,移动左指针
            else 
                right--; // 右边高度较小时,移动右指针
        }

        return ret; // 返回找到的最大面积
    }
};

5. 有效三角形的个数 - Valid Triangle Number

https://leetcode.cn/problems/valid-triangle-number/
在这里插入图片描述

解题思路:先对数组排序,然后利用双指针法检查每个满足条件的三元组。固定最长边,使用双指针从左向右收缩,判断两侧边长之和是否大于最长边。

详细解题思路:
首先对数组排序,从而可以在接下来的步骤中使用双指针。
从右向左遍历数组,每次固定当前元素作为三角形的最大边 nums[k]。
使用两个指针i 和 j 从数组左侧开始,分别指向 k 左侧的两个位置。
如果 nums[i] + nums[j] > nums[k],则说明从 i 到 j 的所有元素都可以与 nums[j] 和 nums[k] 组成有效三角形,因此将 count 加上 j - i 的数量,然后将 j 左移。
如果和不满足条件,则将 i 右移,直到满足条件或 i 与 j 相遇。
通过这种方式可以在排序后的数组中快速找到所有满足条件的三角形组合。

class Solution 
{
public:
    int triangleNumber(vector<int>& nums) 
    {
        // 首先对数组进行升序排序
        sort(nums.begin(), nums.end());

        // 定义 max 为最大的下标值(从后向前遍历)
        int max = nums.size() - 1;
        int n = 0; // 用于存储能够构成三角形的三元组数量

        // 从最大元素开始,尝试找到可以组成三角形的组合
        for (max = nums.size() - 1; max >= 2; max--)
        {
            // 初始化左右指针,right 指向 max 左边的元素,left 指向最左边
            int right = max - 1;
            int left = 0;

            // 使用双指针法查找满足条件的组合
            while (left < right)
            {
                // 如果左指针和右指针的和大于最大边(满足三角形不等式)
                if (nums[left] + nums[right] > nums[max])
                {
                    // 那么从 left 到 right 之间的所有组合都能满足条件
                    n += right - left;
                    // 右指针左移,继续查找其他可能的组合
                    right--;
                }
                else
                {
                    // 如果不满足条件,说明 left 太小,左指针右移
                    left++;
                }
            }
        }

        // 返回可以组成三角形的三元组数量
        return n;
    }
};

6. 和为s的两个数字 - Two Sum (LCOF)

https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/
在这里插入图片描述
解题思路:由于数组是递增排序的,可以使用双指针法。一个指针从数组头开始,另一个指针从数组尾开始,检查当前和是否等于目标值。根据和的大小调整指针位置,直到找到符合条件的数字。

详细解题思路:
利用数组的有序性,使用双指针 left 和 right,分别指向数组的首尾。
计算当前两数之和 nums[left] +
nums[right],如果和等于目标值 s,即找到所需的两个数。
如果和小于 s,则将 left 向右移动以增大和;如果和大于 s,则将
right 向左移动以减小和。 这样可以确保遍历整个数组的时间复杂度为 O(n)。

class Solution 
{
public:
    vector<int> FindNumbersWithSum(vector<int> array, int sum) 
    {
        // 初始化两个指针,一个指向数组的开头,另一个指向数组的末尾
        int left = 0;
        int right = array.size() - 1;
        
        // 只要左指针在右指针的左边,就继续搜索
        while (left < right)
        {
            // 如果左指针和右指针指向的数的和大于目标和
            if (array[left] + array[right] > sum)
            {
                // 右指针左移以减小和
                right--;
            }
            // 如果左指针和右指针指向的数的和小于目标和
            else if (array[left] + array[right] < sum)
            {
                // 左指针右移以增加和
                left++;
            }
            // 如果找到了和等于目标和的两个数
            else 
            {
                // 返回这两个数
                return {array[left], array[right]};
            }
        }
        // 如果未找到符合条件的数对,返回空向量
        return { };
    }
};

7. 三数之和 - 3Sum

https://leetcode.cn/problems/3sum/
在这里插入图片描述

解题思路:先对数组排序,固定一个数,接着使用双指针法寻找另外两个数。确保跳过重复元素,以避免重复的三元组组合。调整指针位置直到找到所有符合条件的组合。

详细解题思路:
首先对数组进行排序,从而方便后续使用双指针法。
遍历数组,每次固定一个数 nums[i],将问题简化为寻找两个数,使得这两个数的和为 -nums[i]。
之后对剩余数组使用双指针法,初始化 left 和 right指针,分别从当前数右边的起始位置和数组末尾开始。
如果三数之和等于零,将三元组加入结果集中,同时移动指针并跳过重复元素。
如果和小于零,将 left 向右移动;如果和大于零,将 right 向左移动。
通过这种方法,可以有效避免重复解并且时间复杂度较低。

class Solution 
{
public:
    vector<vector<int>> threeSum(vector<int>& nums) 
    {
        vector<vector<int>> ret; // 定义用于存储结果的二维数组
        sort(nums.begin(), nums.end()); // 对数组进行升序排序

        // 遍历数组,使用三指针法找到所有符合条件的三元组
        for (int i = 0; i < nums.size();)
        {
            int left = i + 1; // 左指针指向 i 之后的元素
            int right = nums.size() - 1; // 右指针指向数组的最后一个元素

            // 使用双指针查找从 i 开始能够满足条件的三元组
            while (left < right)
            {
                if (nums[left] + nums[right] > -nums[i]) // 当前和大于目标值,右指针左移
                {
                    right--;
                }
                else if (nums[left] + nums[right] < -nums[i]) // 当前和小于目标值,左指针右移
                {
                    left++;
                }
                else if (nums[left] + nums[right] == -nums[i]) // 找到符合条件的三元组
                {
                    ret.push_back({nums[left], nums[right], nums[i]}); // 将符合条件的三元组添加到结果中
                    left++;
                    right--;

                    // 去重,跳过重复的左指针值
                    while (left < right && nums[left] == nums[left - 1]) 
                        left++;
                    
                    // 去重,跳过重复的右指针值
                    while (left < right && nums[right] == nums[right + 1]) 
                        right--;
                }
            }

            // 去重 i,确保跳过与前一个元素相同的值
            i++;
            while (i < nums.size() && nums[i] == nums[i - 1]) 
                i++;
        }    

        return ret; // 返回所有符合条件的三元组
    }
};

8. 四数之和 - 4Sum

https://leetcode.cn/problems/3sum/
在这里插入图片描述

解题思路:与“三数之和”类似,先排序,固定两个数,然后在剩下的数组中使用双指针法寻找另外两个数,确保结果去重。整体时间复杂度为 O(n^3)。

详细解题思路:
和“三数之和”类似,先对数组进行排序以便于使用双指针。
使用四重循环,其中前两层循环固定前两个数字,将问题转换为寻找两个数的和为固定值。
使用双指针法在剩余的数组中寻找符合条件的另外两个数。
每次找到一组符合条件的四元组后,将其加入结果集中,同时移动指针以避免重复解。
为了减少不必要的计算,可以在循环中加入剪枝操作,例如在固定的两个数过大或过小时直接跳过当前循环,优化运行时间。

class Solution 
{
public:
    vector<vector<int>> threeSum(vector<int>& nums) 
    {
        vector<vector<int>> ret; // 用于存储结果的二维数组
        sort(nums.begin(), nums.end()); // 对数组进行升序排序

        // 遍历数组,尝试找到所有符合条件的三元组
        for (int i = 0; i < nums.size();)
        {
            int left = i + 1; // 左指针,初始化为 i 之后的元素
            int right = nums.size() - 1; // 右指针,初始化为数组最后一个元素

            // 使用双指针法查找满足条件的组合
            while (left < right)
            {
                // 计算三元组的和,比较它是否等于目标值(0)
                if (nums[left] + nums[right] > -nums[i])
                {
                    // 如果左指针和右指针的和大于 -nums[i],说明需要减小和,右指针左移
                    right--;
                }
                else if (nums[left] + nums[right] < -nums[i])
                {
                    // 如果左指针和右指针的和小于 -nums[i],说明需要增大和,左指针右移
                    left++;
                }
                else
                {
                    // 找到一个满足条件的三元组,将其加入结果中
                    ret.push_back({nums[left], nums[right], nums[i]});
                    left++;
                    right--;

                    // 去重,跳过与之前相同的左指针值,确保不加入重复组合
                    while (left < right && nums[left] == nums[left - 1]) 
                        left++;

                    // 去重,跳过与之前相同的右指针值,确保不加入重复组合
                    while (left < right && nums[right] == nums[right + 1]) 
                        right--;
                }
            }

            // 去重,确保跳过与前一个相同的元素,避免重复计算
            i++;
            while (i < nums.size() && nums[i] == nums[i - 1]) 
                i++;
        }    
        // 返回所有符合条件的三元组
        return ret;
    }
};
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值