Leetcode刷题Task03:双指针(C++)

一、双指针的概念

在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。

双指针法可以充分使用了数组有序的特征,从而在某些情况下能够简化一些运算,减少时间消耗。

二、两类双指针方法

  • 快慢指针
  • 对撞指针(左右指针)

【注】前者主要解决链表中的问题,比如典型的判定链表中是否包含环、链表中间元素等;后者主要解决数组(或者字符串) 中的问题,比如二分查找。

三、对撞指针

3.1 思想

对撞指针是指在有序数组中,将指向最左侧的索引定义为左指针(left),最右侧的定义为右指针(right),然后从两头向中间进行数组遍历。当题目不是有序数组时,可以通过sort或haep方式得到有序数组,方便进一步处理。

3.2 对撞指针的C++实现模板

//这里 以int型数组为例)(已排序)
void crash_Ptr(vector<int>& nums, param1,param2,...){
	int left=0,right=nums.size()-1;
	while(left<right){
		//data proc: 这里一般是遍历数组,对每个元素进行处理
		if( condition 1){ //条件1:左指针右移
			...
			left++;
		}
		else if( condition 2){ //条件2:右指针左移
			...
			right--;
		}
	}//通过while循环,用left和right记录所需的两个元素。
	... //后处理相关问题
}

3.3 LeetCode相关例题

3.3.1 两数之和

题目描述:https://leetcode-cn.com/problems/two-sum
解题思路
1、
Q:如何使用对撞指针?
A:可以对原数组排序,定义left和right索引记录nums两个元素,这样就可以使用sum=nums[left]+nums[right]记录两数之和,所以遍历数组时就会有三种情况:
  (1)sum == target:可以直接返回left和right就是和为target的数组索引,
  (2)sum < target:表明两数取值较小,由于数组有序,将left索引右移,可以提高sum值向target靠近
  (3)sum > target:表明两数取值较大,由于数组有序,将right索引左移,可以减小sum值向target靠近

Q:若直接对原数组进行排序,会破坏原索引值,如何解决?
A:用临时数组Temp拷贝nums,对temp进行排序和对撞处理,当得到left和right索引时,break掉,在原数组找到和temp[left]、temp[right]相等的元素就得到了原数组对应值索引。

Q:是否需要边界处理?
A:可以的,若nums.size()为空,直接返回空数组!

C++代码实现:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target){
        vector<int> res;
        vector<int> temp=nums;  //拷贝一份原数组
        int n=nums.size();
        int left=0,right=nums.size()-1;
        sort(temp.begin(),temp.end());//排序
        while(left<right){ //移动left和right指针找到目标值
            if(nums[left]+nums[right] == target){
                break;
            }
            else if(nums[left]+nums[right]<target){
                left++;
            }
            else{
                right--;
            }
        }
        
        for(int k=0;k<n;k++){
            if(left<n && nums[k]==temp[left]){ //找到left在原数组对应位置
                res.push_back(k);
                left=n;
            }
            else if(right<n && nums[k]==temp[right]){ //找到right在原数组对应位置
                res.push_back(k);
                right=n;
            }
            if(left==n && right==n) //若两则相等则返回
                return res;
        }
        return res;
    }
};

这题也可以通过创建map<int,int>使用Hash的方式解决,而且更为方便,可以参考官方解题

3.3.2 三数之和

LeetCode15

3.3.3 四数之和

LeetCode18

四、快慢指针

4.1 思想

快慢指针一般都初始化指向链表的头结点 head,前进时快指针 fast 在前,慢指针 slow 在后,巧妙解决一些链表中的问题。如判断链表是否有环、寻找链表中点等特殊问题。

4.2 快慢指针的C++实现模板

struct ListNode{
	int val;          //数据域
	ListNode* next;   //指针域
	ListNode(int _x):val(_x),next(NULL){}; //初始化
};
ListNode* fast_slow_Ptr(ListNode* head){
	ListNode *fast, *slow;
	fast = slow = head; //初始化为头指针
	while(fast != NULL && fast->next != NULL){
		slow = slow->next; //slow走一步
		fast = fast->next;
		fast = fast->next; //fast走两步
		if(fast == slow){
			//链表有环状结构
			.... //break,或者对该结点处理
		}
	}
	.... 
}

4.3 LeetCode相关例题

4.3.1 环形链表

题目描述:https://leetcode-cn.com/problems/linked-list-cycle/
解题思路
分析一下,由于链表中含有环,那么通过 head=head->next 遍历环形数组就会陷入死循环,因为没有 NULL指针作为尾结点。
这时就可以使用双指针,一个slow指针每次走一步,一个fast指针每次走两步,那么无论环多大,都会在

C++代码实现

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(!head || !head->next)  //边界值:链表为空
        	return nullptr;
        ListNode *fast, *slow;
		fast = slow = head; //初始化为头指针
        while(fast != NULL && fast->next != NULL){
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow) 
            	return true;
        }
        return false;
    }
};
4.3.1 环形链表 II

题目描述:https://leetcode-cn.com/problems/linked-list-cycle-ii/
解题思路:和上题一样,但是这题需要返回环的第一个节点,所以可以利用一些数学知识,作图表示一下
在这里插入图片描述
假设:(1)绿色段为a,(2)蓝色段为b,(3)橙色段为c
由于fast路程是slow的二倍,所以得出
2 ∗ ( a + b ) = a + b + c + b 2*(a+b)= a+b+c+b 2a+b=a+b+c+b
即: a = c a=c a=c
所以可知,从相遇点meet和head节点同时出发,相遇点即为环的起点!

C++代码实现

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(!head || !head->next)  //边界值:链表为空
        	return nullptr;
        ListNode *fast, *slow;
        ListNode* meet=NULL;
		fast = slow = head; //初始化为头指针
        while(fast != NULL && fast->next != NULL){
        	fast = fast->next->next;
            slow = slow->next;
        	if(fast == slow){
        		meet=fast;
        		break;
        	} 
        }
        if(meet == NULL)
        	return nullptr; //无环,没有相遇
        while(head && meet){
			if(head == meet) //如果相遇,说明到了环的起点
				return head; 
			head=head->next;
			meet=meet->next;
		}
		return nullptr;
    }
};

这题也可以使用集合实现 h e a d = h e a d − > n e x t head=head->next head=head>next遍历链表数组,同时使用set<ListNode*>记录所经过的元素,找到第一个 s e t . f i n d ( h e a d ) ! = s e t . e n d ( ) set.find(head) != set.end() set.find(head)!=set.end()元素,即为环的初始结点,这个方便代码更方便一点!

参考Blog:
1、算法一招鲜——双指针问题
2、双指针技巧总结(这是算法大神labuladong的个人总结,感觉受益良多,读者可以进他主页查看!)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值