LeetCode学习笔记:双指针

双指针是用于有关数组的题目的一种算法,主要题型是滑动窗口。
题型一、滑动窗口
相关题目:3、76、209、424、438、567、713、763、845、881、904、978、992、1004、1040、1052
解题思路:这类题一般用于求一个满足条件的最长或最短区间。这个区间可以用一个窗口来表示,滑动窗口就是用一个左指针一个右指针来构成一个窗口,通过指针的移动来移动窗口并构成符合条件的区间,最后求得结果区间。
一个技巧:做题过程中,若数组为字符数组,往往需要统计窗口中各字符出现次数以判断该窗口是否为“可行”窗口。统计窗口中各字符出现次数可创建一个数组来统计。如字符串s中字符只包含26个小写字母,那么我们可以创建一个长度为26位的数组count,则count[s.charAt(i)-‘a’]即为该字符i出现的次数。遍历字符串s,每次循环count[s.charAt(i)-‘a’]++,即可得到字符串s中各字符出现次数。

做题过程中我发现了一位大佬写的不错的模板:
作者:fuxuemingzhu
链接:https://leetcode-cn.com/problems/max-consecutive-ones-iii/solution/fen-xiang-hua-dong-chuang-kou-mo-ban-mia-f76z/
来源:力扣(LeetCode)

def findSubArray(nums):
    N = len(nums) # 数组/字符串长度
    left, right = 0, 0 # 双指针,表示当前遍历的区间[left, right],闭区间
    sums = 0 # 用于统计 子数组/子区间 是否有效,根据题目可能会改成求和/计数
    res = 0 # 保存最大的满足题目要求的 子数组/子串 长度
    while right < N: # 当右边的指针没有搜索到 数组/字符串 的结尾
        sums += nums[right] # 增加当前右边指针的数字/字符的求和/计数
        while 区间[left, right]不符合题意:# 此时需要一直移动左指针,直至找到一个符合题意的区间
            sums -= nums[left] # 移动左指针前需要从counter中减少left位置字符的求和/计数
            left += 1 # 真正的移动左指针,注意不能跟上面一行代码写反
        # 到 while 结束时,我们找到了一个符合题意要求的 子数组/子串
        res = max(res, right - left + 1) # 需要更新结果
        right += 1 # 移动右指针,去探索新的区间
    return res

滑动窗口中用到了左右两个指针,它们移动的思路是:以右指针作为驱动,拖着左指针向前走。右指针每次只移动一步,而左指针在内部 while 循环中每次可能移动多步。右指针是主动前移,探索未知的新区域;左指针是被迫移动,负责寻找满足题意的区间。
模板的整体思想是:
定义两个指针 left 和 right 分别指向区间的开头和结尾,注意是闭区间;定义 sums 用来统计该区间内的各个字符出现次数;
第一重 while 循环是为了判断 right 指针的位置是否超出了数组边界;当 right 每次到了新位置,需要增加 right 指针的求和/计数;
第二重 while 循环是让 left 指针向右移动到 [left, right] 区间符合题意的位置;当 left 每次移动到了新位置,需要减少 left 指针的求和/计数;
在第二重 while 循环之后,成功找到了一个符合题意的 [left, right] 区间,题目要求最大的区间长度,因此更新 res 为 max(res, 当前区间的长度) 。
right 指针每次向右移动一步,开始探索新的区间。

例题:

//1052
//先统计确定满意的顾客人数,将老板冷静的时间段作为窗口
//然后将确定满意的顾客人数加上老板冷静窗口内额外的满意顾客人数
//不断移动窗口,得到最大值
class GrumpyBookstoreOwner {
    public int maxSatisfied(int[] customers, int[] grumpy, int minutes) {
    	//左指针
    	int left = 0;
    	//结果
    	int result = 0;
    	//确定满意的顾客人数
    	int certain = 0;
    	//统计确定满意的顾客人数
    	for(int i=0;i<customers.length;i++) {
    		if(grumpy[i] == 0) {
    			certain += customers[i];
    		}
    	}
    	//将老板冷静的时间段作为窗口
    	for(int right=minutes-1;right<customers.length;right++) {
    		//确定满意的顾客人数加上老板冷静窗口内额外的满意顾客人数
    		int temp = certain;
    		//将确定满意的顾客人数加上老板冷静窗口内额外的满意顾客人数
    		for(int i=left;i<=right;i++) {
    			if(grumpy[i] == 1) {
    				temp += customers[i];
    			}
    		}
    		left++;
    		result = Math.max(result, temp);
    	}
    	return result;
    }
}

每天一道LeetCode题,冲!!!

题型二:SUM问题集
相关题目:1、15、16、18、167、923、1074
解题思路:这类题一般是要求数组中两数或三数之和满足target值。我们可以先对数组进行排序,对于两数之和,我们可以用头指针指向头部,尾指针指向尾部,当两者之和大于target,则尾指针前移;当两者之和小于target,则头指针后移,当两者之和等于target,则保存结果。一直到头指针大于或等于尾指针。对于三数之和,我们可以先固定一个数x,然后使用双指针满足target-x,方法与两数之和相同。使用双指针解这类题,可以使时间复杂度下降一个幂。
例题:

	//163
	class TwoSumⅡ_Inputarrayissorted {
    public int[] twoSum(int[] numbers, int target) {
    	int i = 0;
    	int j = numbers.length-1;
    	int[] result = new int[2];
    	while(i < j) {
    		if(numbers[i]+numbers[j] == target) {
    			result[0] = i+1;
    			result[1] = j+1;
    			break;
    		}
    		else if(numbers[i]+numbers[j] > target) {
    			j--;
    		}
    		else {
    			i++;
    		}
    	}
    	return result;
    }
}

每天一道LeetCode题,冲!!!

题型三、快慢指针检测重复数(环)
相关题目:202、287
解题思路:在某些题目中,可能会出现不断重复的一组数据,如:1,2,3,4,5,6,4,5,6,4,5,6。我们可以把这样的数组看成是一个隐式的链表,隐式意味着没有实际的链表节点和指针,但数据仍然形成链表结构。起始数字是链表的头 “节点”,链中的所有其他数字都是节点。next 指针是通过调用 getNext(n) 函数获得。使用快慢指针可以检测这个链表是否有重复的数据,即有无“环”。
我们可以使用一个快指针一个慢指针,在算法的每一步中,慢指针在链表中前进 1 个节点(调用getNext(n)方法),快指针前进 2 个节点(对 getNext(n) 函数的嵌套调用)。若有“环”,则快指针和慢指针最终会相遇。
例题:

//202
class HappyNumber {
//计算各位数字平方和
public int getNext(int n) {
   int totalSum = 0;
   while (n > 0) {
       int d = n % 10;
       n = n / 10;
       totalSum += d * d;
   }
   return totalSum;
   }

   public boolean isHappy(int n) {
	   //慢指针
       int slowRunner = n;
       //快指针
       int fastRunner = getNext(n);
       //如果 n 是一个快乐数,即没有循环,那么快跑者最终会比慢跑者先到达数字 1。
       //若有环则两指针最后必然相遇
       while (fastRunner != 1 && slowRunner != fastRunner) {
    	   //慢指针一次移动一个节点
           slowRunner = getNext(slowRunner);
           //快指针一次移动两个结点
           fastRunner = getNext(getNext(fastRunner));
       }
       //若快指针最终结果为1则是快乐数,否则不是
       return fastRunner == 1;
   }
}

每天一道LeetCode题,冲!!!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值