双指针C++

本文详细介绍了如何利用双指针技巧解决多种算法问题,包括有序数组的平方、移动零、两数之和、反转字符串、旋转数组等。此外,还探讨了快慢指针在链表问题中的应用,如找到链表的中间节点和删除倒数第N个节点。文章提供了详细的题解和示例,展示了如何在O(n)的时间复杂度内解决这些问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

双指针夹逼定理

977. 有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例 1:

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:

输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

提示:

1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 已按 非递减顺序 排序

进阶:

请你设计时间复杂度为 O(n) 的算法解决本问题

题解

//原题本来就是排序的,只不过有负数;
//kk是从后往前的下标;
//从前往后插
class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
    vector<int> n1(nums.size());
    int rr=nums.size()-1;
    int ll=0; int kk=nums.size()-1;
while(ll<=rr)
{
if(abs(nums[ll])>abs(nums[rr])){n1[kk]=(nums[ll]*nums[ll]);ll++;kk--;}
if(abs(nums[ll])<=abs(nums[rr])){n1[kk]=(nums[rr]*nums[rr]);rr--;kk--;}
}
    return n1;
    }
};

283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:

必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

题解

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
int n=nums.size();
int a=0;//aa是从前往后的下标;
for(int i=0;i<n;i++)//先把非0元素移向前,用a 记录
    if(nums[i]!=0)
      nums[a++]=nums[i];
for(int i=a;i<n;i++)//后面都为0
    nums[i]=0;
}
};

167. 两数之和 II - 输入有序数组

给定一个已按照 非递减顺序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

示例 1:

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
示例 2:

输入:numbers = [2,3,4], target = 6
输出:[1,3]
示例 3:

输入:numbers = [-1,0], target = -1
输出:[1,2]

提示:

2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers 按 非递减顺序 排列
-1000 <= target <= 1000
仅存在一个有效答案

题解

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
      int n=numbers.size();
      int a=0,b=n-1;
      while(a<b)
      {
        if(numbers[a]+numbers[b]>target)        b--;
        else if(numbers[a]+numbers[b]==target) return vector<int>{a+1,b+1};
        else                                    a++;
      }
          return vector<int>{-1, -1};
    }
};

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:

输入:s = [“h”,“e”,“l”,“l”,“o”]
输出:[“o”,“l”,“l”,“e”,“h”]
示例 2:

输入:s = [“H”,“a”,“n”,“n”,“a”,“h”]
输出:[“h”,“a”,“n”,“n”,“a”,“H”]

提示:

1 <= s.length <= 105
s[i] 都是 ASCII 码表中的可打印字符

题解

class Solution {
public:
    void reverseString(vector<char>& s) {
        int n=s.size();
    for(int i=0;i<n/2;i++){
        swap(s[i],s[n-i-1]);
    }
    }
};

557. 反转字符串中的单词 III

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

示例:

输入:“Let’s take LeetCode contest”
输出:“s’teL ekat edoCteeL tsetnoc”

提示:

在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。

题解

class Solution {
public:
    string reverseWords(string s) {
        int n=s.size();int a=0,b=-1;
          for(int i=0;i<n;i++)
           {
               if(s[i]==' '||i==n-1)//寻找单个单词
               {
                  b=i-1;
                  if(i==n-1)  b=i;
                   while(a<b)//旋转单词内部
                   {
                       swap(s[a],s[b]);
                       a++;
                       b--;
                   }
                   a=i+1;
               }
           }
       return s;
    }
};

189. 旋转数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

进阶:

尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?

示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
示例 2:

输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]

提示:

1 <= nums.length <= 2 * 104
-231 <= nums[i] <= 231 - 1
0 <= k <= 105

题解

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n=nums.size();
        if(n==1)
        return;
        k=k%n;//经过总结规律。刚开大于n时,相当取n的余;
        int a=0,b=n-1;//翻转全部。
        while(a<b){ swap(nums[a],nums[b]);a++;b--;}
        a=0;b=k-1;//翻转前半部分。
        while(a<b){ swap(nums[a],nums[b]);a++;b--;}
        a=k;b=n-1;//翻转后半部分
        while(a<b){ swap(nums[a],nums[b]);a++;b--;}
         }   
};

11. 盛最多水的容器

给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器。

示例 1:

输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

题解

class Solution {
public:
    int maxArea(vector<int>& height) {
 int a=0,b=height.size()-1;
 int max=0;
 while(a<b)
 {
     int min1=min(height[a],height[b]);
     if(max<min1*(b-a))
        max=min1*(b-a);
    if(height[a]<height[b]) a++;//经常使用if else if else 
    else b--;
 }
 return max;
    }
    //若向内 移动短板 ,水槽的短板 min(h[i], h[j])min(h[i],h[j]) 可能变大,因此下个水槽的面积 可能增大 。
    //若向内 移动长板 ,水槽的短板 min(h[i], h[j])min(h[i],h[j])​ 不变或变小,因此下个水槽的面积 一定变小 。

};

16. 最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

提示:

3 <= nums.length <= 10^3
-10^3 <= nums[i] <= 10^3
-10^4 <= target <= 10^4

题解

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
   int mini=10000,min1;
   int n=nums.size();
    sort(nums.begin(),nums.end());
   for(int i=0;i<n-2;i++)//一个一个来
   {
       int a=i+1;int b=n-1;
     while(a<b)//循环遍历后面
     {
          if(abs(nums[a]+nums[b]+nums[i]-target)<mini)
          {
            mini=abs(nums[a]+nums[b]+nums[i]-target);//寻找最接近的
            min1=nums[a]+nums[b]+nums[i];//最接近的
          } 
          if(nums[a]+nums[b]+nums[i]<target)a++;//常规三情况
          else if(nums[a]+nums[b]+nums[i]>target)b--;
          else return target;

     }
   }
   return min1;
    }
};

##面试题 01.05. 一次编辑
字符串有三种编辑操作:插入一个字符、删除一个字符或者替换一个字符。 给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。

示例 1:

输入:
first = “pale”
second = “ple”
输出: True

示例 2:

输入:
first = “pales”
second = “pal”
输出: False

题解

class Solution {
public:
    bool oneEditAway(string first, string second) {
    int m=first.size(),n=second.size();
    // if(first.empty()&&second.size()==1) return true;
    // if(second.empty()&&first.size()==1) return true;
    if(n>m)swap(first,second);
    if(first.size()-second.size()>1)//两字符串长度相差二
     return false;
    else if(first.size()==second.size())//两个字符串一样长
    {
        int sum=0;
     for(int i=0;i<n;i++)
       if(first[i]!=second[i])
       {  sum++;     if(sum>1)return false;}
     return true;
    }
else//两字符串长度相差一。
{
int a=0,b=n-1;
while(a<=b&&first[a]==second[a]) a++;//从前往后遍历,
while(a<=b&&first[b+1]==second[b]) b--;//从后往前便利,如果错误之处有两个,则A必小于B,如果只有一次错误,则A等于B加一。
return a>b;
}
  
    }
};

面试题 16.16. 部分排序

给定一个整数数组,编写一个函数,找出索引m和n,只要将索引区间[m,n]的元素排好序,整个数组就是有序的。注意:n-m尽量最小,也就是说,找出符合条件的最短序列。函数返回值为[m,n],若不存在这样的m和n(例如整个数组是有序的),请返回[-1,-1]。

示例:

输入: [1,2,4,7,10,11,7,12,6,7,16,18,19]
输出: [3,9]
提示:

0 <= len(array) <= 1000000

题解

class Solution {
public:
    vector<int> subSort(vector<int>& array) {
       int n=array.size();
       if(n==1||n==0)//刚刚只有一个或没有元素的时候,我们直接返回{-1,-1}说明他就不需要再排序
       return {-1,-1};
        int i=1,j=n-2;//相当于我们先找出开始混乱的地方。但没有混乱的地方不一定排不排。初始第一个元素和最后一个元素是有序的。逐渐加入游戏的队伍。
        while(i<n){if(array[i]<array[i-1])break;i++;}i--;if(i==n-1)return {-1,-1};//从左往右找出开始混乱的地方,但如果他没有混乱那就直接返回
        while(j>=0){if(array[j]>array[j+1])break;j--;}j++;//从右往左咋说开始混乱的地方
        int max1=array[0];int min1=array[n-1];//找出混乱的最大值和最小
        for(int k=i;k<=j;k++)
        {
            if(array[k]<min1)min1=array[k];
            if(array[k]>max1)max1=array[k];
        }
        int m=i+1,m2=j-1;//首先给个默认值
        for(int k=0;k<=i;k++)//确定左端开始排序的下标
        {
           if(array[k]>min1)
            {
                m=k;
                break;
            }
        }
        for(int k=n-1;k>=j;k--)//确定右端开始排序的下标
        {
           if(array[k]<max1)
            {
               m2=k;
                break;
            }
        }
return {m,m2};
    }
};

面试题 10.01. 合并排序的数组

给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。

初始化 A 和 B 的元素数量分别为 m 和 n。

示例:

输入:
A = [1,2,3,0,0,0], m = 3
B = [2,5,6], n = 3

输出: [1,2,2,3,5,6]
说明:

A.length == n + m

题解

class Solution {
public:
    void merge(vector<int>& A, int m, vector<int>& B, int n) {
    int i=m-1,j=n-1;int max1=m+n-1;
    int l1=0;if(m==0)while(l1<n){A[l1]=B[l1];l1++;}//当然A为空时,把B直接给A;
    while(j>=0&&i>=0)
    {
        if(A[i]>=B[j]){ A[max1]=A[i];  max1--;  i--;   }//逆向插入,把最大的插入向前依次插入
        else { A[max1]=B[j];  max1--;   j--;} 
    }
    if(j>=0)while(j>=0){A[max1]=B[j]; max1--;  j--;  }//当一个已经插完了,如果是A剩下了那么A就不需要再插了
                                                 //如果是B剩下的,就应该全部把剩下的给全部剩下的A;
    }
};

快慢指针

876. 链表的中间结点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:

输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。

提示:

给定链表的结点数介于 1 和 100 之间。

题解

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode *a=head;
        ListNode *b=head; 
        while(b!=NULL&&b->next!=NULL)
        {
            a=a->next;
            b=b->next->next;
        }
        return a;
    }
};

19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

进阶:你能尝试使用一趟扫描实现吗?

示例 1:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:

输入:head = [1], n = 1
输出:[]
示例 3:

输入:head = [1,2], n = 1
输出:[1]

提示:

链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz

题解

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
      ListNode* qwe=new  ListNode(0);
      qwe->next=head;
      ListNode *p=qwe;                                   
      ListNode *q=qwe;
      for(int i=0;i<n+1;i++)
      {
          p=p->next;
      }
      while(p!=NULL)
      {
          q=q->next;
          p=p->next;
      }
        ListNode* delNode = q->next;
        q->next = delNode->next;
        delete delNode;
        ListNode* retNode = qwe->next;
        delete qwe;
        return retNode;

    }
};

面试题 02.02. 返回倒数第 k 个节点

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

注意:本题相对原题稍作改动

示例:

输入: 1->2->3->4->5 和 k = 2
输出: 4
说明:

给定的 k 保证是有效的。

题解

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    int kthToLast(ListNode* head, int k) {
        ListNode * qwe = new ListNode(0);
        qwe->next=head;
        ListNode * q=qwe;
        ListNode * w=qwe;
      for(int i=0;i<k+1;i++)
      {
          w=w->next;
      }
      while(w)
      {
          w=w->next;
          q=q->next;
      }
      return q->next->val;


    }
};

面试题 02.04. 分割链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你不需要 保留 每个分区中各节点的初始相对位置。

示例 1:

输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]

题解

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        ListNode* small=new ListNode(0);
         ListNode* large=new ListNode(0);
         ListNode* smallhead=small;
         ListNode* largehead=large;
         while(head)
         {
             if(head->val>=x)
                 {large->next=head; large=large->next;}
             if(head->val<x)
                 {small->next=head; small=small->next;}
                 head=head->next;
         }
         large->next = nullptr;
         small->next = largehead->next;
         return smallhead->next;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值