我是靠谱客的博主 温暖板凳,最近开发中收集的这篇文章主要介绍双指针法双指针法,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

双指针法

所谓双指针,指的是在遍历对象的过程中,使用两个相同方向或者相反方向的指针进行扫描,从而达到相应的目的。

例题一:盛最多水的容器「左右指针」

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

输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水的最大值为 49
解法:这种方法背后的思路在于,两线段之间形成的区域总是会受到其中较短那条长度的限制。此外,两线段距离越远,得到的面积就越大。

我们在由线段长度构成的数组中使用两个指针,一个放在开始,一个置于末尾。 此外,我们会使用变量 max 来持续存储到目前为止所获得的最大面积。 在每一步中,我们会找出指针所指向的两条线段形成的区域,更新 max,并将指向较短线段的指针向较长线段那端移动一步。

	public int maxArea(int[] height) {
        if(height.length==0) return 0;
		int max=(height.length-1)*Math.min(height[0],height[height.length-1]);	//最大面积初始化
		for(int l=0,r=height.length-1;l<r;) {
			if (height[l]<height[r]) {	// 左高度  < 右高度
				int temp=height[l];
				while(height[l]<=temp&&r>l) l++;//左指针右移(如果下个左指针<=当前指针,一直向下走)
			} else {					// 左高度 >= 右高度
				int temp=height[r];
				while(height[r]<=temp&&r>l) r--;//右指针左移(如果下个右指针<=当前指针,一直向下走)
			}
			max=Math.max(max,(r-l)*Math.min(height[l],height[r]));	//取得最大面积
		}
		return max;
	}

例题二:三数之和「左右指针」

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

示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],

	满足要求的三元组集合为:
	[
		 [-1, 0, 1],
		 [-1, -1, 2]
	]

固定三个指针中最左(最小)数字的指针 i,双指针 j,k 分设在数组索引 (i+1, nums.length-1) 两端,通过双指针交替向中间移动,记录 nums[i] + nums[j] + nums[k] == 0 时的nums组合:

	public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> List = new ArrayList<List<Integer>>();
        if(nums.length < 3) return List;
        Arrays.sort(nums); // 进行排序
        for (int i = 0; i < nums.length ; i++) {
            if(nums[i] > 0) break; // nums[i]大于0,则三数之和大于0,所以结束跳出
            if(i > 0 && nums[i] == nums[i-1]) continue; // 去掉重复
            for(int j=i+1,k=nums.length-1;j < k;){
                int He = nums[i] + nums[j] + nums[k];
                if(He == 0){
                	List.add(Arrays.asList(nums[i],nums[j],nums[k]));
                    while (j<k && nums[j] == nums[j+1]) j++; // 去掉左重复
                    while (j<k && nums[k] == nums[k-1]) k--; // 去掉右重复
                    j++; k--;
                }
                else if (He < 0) j++;
                else if (He > 0) k--;
            }
        }
        return List;
    }

例题三:最接近的三数之和「左右指针」

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

示例:
给定数组 nums = [-1,2,1,-4], 和 target = 1.

	满足要求的三元组集合为:
	与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2)

与三数之和类似。三数之和要求找到三个元素之和为0的所有不重复情况,这里找到三个元素之和最接近目标值的情况(唯一)。首先对原数组排序,然后维护一个绝对差值变量和对应的结果变量。遍历数组,使用双指针j和k,根据三数之和与目标值的大小关系来移动指针,注意跳过重复情况,并记录与目标值的最小绝对差和对应的三数之和。

	public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
     	int He,min;
     	if(nums.length<3)return target;
     	min=nums[0]+nums[1]+nums[2];
     	for(int i=0;i<nums.length-2;i++) {
     		if(i!=0) while (nums[i]==nums[i-1]&&i<nums.length-2) i++;
     		for(int j=i+1,k=nums.length-1;j<k;) {
     			He=nums[i]+nums[j]+nums[k];
     			if(He<target) {
     				if(target-He<Math.abs(min-target))min=He;
     				j++;
     			}
     			else if(He>target) {
     				if(He-target<Math.abs(min-target))min=He;
     				k--;
     			}
     			else return target;
     		}
     	}
     	return min;
    }

例题四:四数之和「左右指针」

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为:
	[
		 [-1,  0, 0, 1],
		 [-2, -1, 1, 2],
		 [-2,  0, 0, 2]
  	]

和3数之和类似,先将数组排序固定两个元素在用两个指针,一个指向头,一个指向尾,看四数之和为多少,太大了右指针左移,太小了左指针右移。

	public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> List = new ArrayList<List<Integer>>();
     	Arrays.sort(nums);
     	int He;
     	for(int i=0;i<nums.length-3;i++) {
     		if(i!=0&&nums[i-1]==nums[i]) continue;//防止重复
	     	for(int j=i+1;i<j&&j<nums.length-2;j++) {
	     		if(j!=i+1&&nums[j-1]==nums[j]) continue;//防止重复
	     		if(nums[j]>0&&nums[i]+nums[j]>target) break;
	     		for(int k=j+1,l=nums.length-1;k<l;) {
	     			He=nums[i]+nums[j]+nums[k]+nums[l];
	     			if(He<target)k++;
	     			else if(He>target)l--;
	     			else {
	     				List.add(Arrays.asList(nums[i],nums[j],nums[k],nums[l]));
	             		while(nums[k]==nums[k+1]) {
	             			k++;
	             			if(k>=l)break;
	             		}
	              		while(nums[l]==nums[l-1]) {
	               			l--;
	               			if(k>=l)break;
	               		}
	               		k++;
	               		l--;
	     			}
	     		}
	     	}
     	}
     	return List;
    }

例题五:删除链表的倒数第N个节点「快慢指针」

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

示例:

  	给定一个链表: 1->2->3->4->5, 和 n = 2.
  	
当删除了倒数第二个节点后,链表变为 1->2->3->5.

我们可以使用两个指针而不是一个指针。第一个指针从列表的开头向前移动 n+1 步,而第二个指针将从列表的开头出发。现在,这两个指针被 n 个结点分开。我们通过同时移动两个指针向前来保持这个恒定的间隔,直到第一个指针到达最后一个结点。此时第二个指针将指向从最后一个结点数起的第 n 个结点。我们重新链接第二个指针所引用的结点的 next 指针指向该结点的下下个结点。

	/**
 	* 链表数据结构
 	* class ListNode {
 	*     int val;
 	*     ListNode next;
	*     ListNode(int x) { val = x; }
 	* }
	*/
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode i=head,j=head;
    	for(;n>0;n--,j=j.next);	//后指针向后转移n次
    	if(j==null)return head.next;
    	for(;j.next!=null;) {	//前指针,后指针同时后指,直到后指针为null
    		i=i.next;
    		j=j.next;
    	}
    	i.next=i.next.next;//删除第n个结点
    	return head;
    }

例题六:环形链表「快慢指针」

给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例:

  	输入:head = [3,2,0,-4], pos = 1
	输出:true
	解释:链表中有一个环,其尾部连接到第二个节点。

通过使用具有 不同速度 的快、慢两个指针遍历链表,空间复杂度可以被降低至 O(1)。慢指针每次移动一步,而快指针每次移动两步。

如果列表中不存在环,最终快指针将会最先到达尾部,此时我们可以返回 false。

现在考虑一个环形链表,把慢指针和快指针想象成两个在环形赛道上跑步的运动员(分别称之为慢跑者与快跑者)。而快跑者最终一定会追上慢跑者。这是为什么呢?考虑下面这种情况(记作情况 A)- 假如快跑者只落后慢跑者一步,在下一次迭代中,它们就会分别跑了一步或两步并相遇。

	/**
 	* 链表数据结构
 	* class ListNode {
 	*     int val;
 	*     ListNode next;
	*     ListNode(int x) { val = x; }
 	* }
	*/
    public boolean hasCycle(ListNode head) {
        if(head!=null&&head.next!=null) {
    		ListNode  m=head,k=head.next.next;
    		while(m!=null&&k!=null) {
    			if(m==k)return true;
    			m=m.next;
    			if(k.next!=null)k=k.next.next;
    			else return false;
    		}
    	}
    	return false;
    }

例题七:环形链表II「快慢指针」

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例:

  	输入:head = [3,2,0,-4], pos = 1
	输出:链表中有一个环,其尾部连接到第二个节点。

通过使用具有 不同速度 的快、慢两个指针遍历链表,空间复杂度可以被降低至 O(1)。慢指针每次移动一步,而快指针每次移动两步。

第一次相遇时,假设慢指针 m 走了 t 步,那么快指针 k一定走了 2t 步,也就是说比 m 多走了 t 步(也就是环的长度)。
设相遇点距环的起点的距离为 l,那么环的起点距头结点 head 的距离为 t - l,也就是说如果从 head 前进 t - l 步就能到达环起点。
巧的是,如果从相遇点继续前进 t - l 步,也恰好到达环起点。

	/**
 	* 链表数据结构
 	* class ListNode {
 	*     int val;
 	*     ListNode next;
	*     ListNode(int x) { val = x; }
 	* }
	*/
    public ListNode detectCycle(ListNode head) {
        if (head == null) return null;
        ListNode m = head,k = head;
        while (k!=null&&k.next!=null) {
            m=m.next;
            k=k.next.next;
            if (m==k) break;
        }
        if(k==null) return null;
        else if(k.next==null) return null;
        while (head!=k) {
            head=head.next;
            k=k.next;
        }
        return head;
    }

例题八:快乐数「快慢指针」

编写一个算法来判断一个数是不是“快乐数”。
一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。

示例:

			输入: 19
			输出: true
			解释: 
			12 + 92 = 82
			82 + 22 = 68
			62 + 82 = 100
			12 + 02 + 02 = 1

使用“快慢指针”思想找出循环:“快指针”每次走两步,“慢指针”每次走一步,当二者相等时,即为一个循环周期。此时,判断是不是因为1引起的循环,是的话就是快乐数,否则不是快乐数。

	public boolean isHappy(int n) {
        int m=n,k=bitSquareSum(n);
        while(m!=k){
            m=bitSquareSum(m);
            k=bitSquareSum(bitSquareSum(k));
        }
        return m==1;
    }
    public int bitSquareSum(int n) {
        int sum = 0;
        for(;n>0;n=n/10){
            int t=n%10;
            sum+=t*t;
        }
        return sum;
    }

最后

以上就是温暖板凳为你收集整理的双指针法双指针法的全部内容,希望文章能够帮你解决双指针法双指针法所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(69)

评论列表共有 0 条评论

立即
投稿
返回
顶部