leecode-双指针问题

双指针问题

算法解释

  • 双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。也可以延伸到多个数组的多个指针。

  • 若两个指针指向同一数组,遍历方向相同且不会相交,则也称为滑动窗口(两个指针包围的区域即为当前的窗口),经常用于区间搜索。

  • 若两个指针指向同一数组,但是遍历方向相反,则可以用来进行搜索,待搜索的数组往往是排好序的。

两数之和相关问题

167.Sum II - Input array is sorted (Easy)

的整数数组里找到两个数,使它们的和为给定值。已知有且只有一对解

Input: numbers = [2,7,11,15], target = 9
Output: [1,2]

已经知道数组为一个有序数组,分别在首尾处设置指针Lo、hi,不断的计算lo与hi处数值的和,判断是否相等

/**
 * 在一个增序的整数数组里找到两个数,使它们的和为给定值。已知有且只有一对解。
 * @anthor shkstart
 * @create 2020-08-20 20:17
 */
public class Two_Sum {
	@Test
	    public void test1() {
		int[] S = {2,3,4};
		int target = 6;
		System.out.println(Arrays.toString(twoSum1(S,target)));
	}
	public int[] twoSum(int[] numbers, int target) {
		int lo = 0;
		int hi = numbers.length - 1;
		int sum = 0;
		while (lo < hi){
			sum = numbers[lo] + numbers[hi];
			if (sum == target) break;
			if (sum > target){
				hi--;
			} else{
				lo++;
			}
		}
		return new int[]{lo+1,hi+1};
	}
	/**
     * 二分查找
     */
	public int[] twoSum1(int[] numbers, int target) {
		for (int i = 0; i < numbers.length - 1; ++i) {
			int lo = i + 1;
			int hi = numbers.length;
			int j = find(numbers,target - numbers[i],lo,hi);
			if (j != (-1)){
				return new int[]{i+1,j+1};
			}
		}
		return new int[]{-1, -1};
	}
	public int find(int[] S,int e,int lo,int hi){
		while (1 < hi - lo){
			int mi = (lo + hi) >>1;
			//取得两者的中点
			if (e < S[mi]){
				//mi处值大于e
				hi = mi;
				//令hi = mi
			} else {
				//小于e
				lo = mi;
				//令lo = mi
			}
			//这里没有考虑相等的情况,把相等放在了右侧区间
		}
		if(S[lo] == e){
			return lo;
		} else {
			return -1;
		}
	}
	public int find2(int[] S,int e,int lo,int hi){
		while (lo < hi){
			int n = 0;
			while (hi - lo > fib(n) - 1){
				//计算合适为恰好的fib(n) - 1 >=
				n++;
			}
			int mi = lo + fib(n-1) - 1;
			//前后的子向量的长度为fib(n-1) - 1和fib(n-2) -1
			if (e < S[mi]){
				hi = mi;
			} else if (S[mi] < e){
				lo = mi + 1;
			} else {
				return mi;
			}
		}
		return -1;
	}
	public int fib(int n){
		int f = 0;
		//从0开始,
		int g = 1;
		//斐波那契数列
		while(0 < n--){
			g = g + f;
			//滚动常数
			f = g - f;
			//先计算后一个数,前一个用相减的方式得到
		}
		return g;
	}
	/**
     * 指针加上二分查找
     */
	public int[] twoSum2(int[] numbers, int target) {
		int lo = 0;
		int hi = find(numbers,target-numbers[0],0,numbers.length);
		int sum = 0;
		while (lo < hi){
			sum = numbers[lo] + numbers[hi];
			if (sum == target) break;
			if (sum > target){
				hi--;
			} else{
				lo++;
			}
		}
		return new int[]{lo+1,hi+1};
	}
}

平方数之和(简单)167.

题目:给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c。
一、双指针方法
满足a2+b2 = d2 = c,这是一个中心在原点的圆;在取得a、b时其实关于Y = X是对称的,因此循环的范围可以缩减至根号2分之一;分别在首尾设置指针,判断是否满足条件

/**
     * 与之前两数之和类似,不过改成平方
     * 时间复杂度O(n)空间复杂度O(1)
     * @param c
     * @return
     */
public Boolean judgeSquareSum(int c) {
	int d = (int) ((Math.sqrt(c)) * Math.sqrt(0.5));
	int lo = 0;
	int hi = d;
	int sum = 0;
	while (lo < hi){
		sum = lo*lo + hi*hi;
		if (sum == c) return true;
		if (sum > c){
			hi--;
		} else{
			lo++;
		}
	}
	return false;
}
/**
     * 可以尝试二分查找
     * i从1到i*i < sum遍历,对每个n =(sum-i*i)进行分析
     * 分析方法(a,b,n)
     * 每次取中间值,判断其平方是否为n
     * 若大于n,则b = 中间值 - 1
     * 若小于n,则a = 中间值 + 1
     * ==n,则输出true
     * 终止于s>e
     * 结果时超出了时间限制
     */
public Boolean judgeSquareSum1(int c) {
	for (int i = 0;i*i <= c;i++){
		int d = c - i*i;
		if (find(i,d,d)){
			return true;
		}
	}
	return false;
}
public Boolean find(long lo,long hi,int e){
	while (1 < hi - lo){
		long mi = (lo + hi) >>1;
		//取得两者的中点
		if (e < mi*mi){
			//mi处值大于e
			hi = mi;
			//令hi = mi
		} else {
			//小于e
			lo = mi;
			//令lo = mi
		}
		//这里没有考虑相等的情况,把相等放在了右侧区间
	}
	if(lo*lo == e || hi*hi == e){
		return true;
	} else {
		return false;
	}
}
/**
     * 直接判断存不存在
     */
public Boolean judgeSquareSum2(int c) {
	for (long a = 0; a * a <= c; a++) {
		double b = Math.sqrt(c - a * a);
		if (b == (int) b)
		                return true;
	}
	return false;
}

二、费马定理
费马定理:一个非负整数 cc 能够表示为两个整数的平方和,当且仅当 cc 的所有形如4k+34k+3 的质因子的幂次均为偶数;先对数进行因式分解,得到转换为a(n1)+a(n2) + .....然后判断是否含有形为4k + 3 且幂次为奇数的因子

/**
     * 费马定理:一个非负整数 cc 能够表示为两个整数的平方和,当且仅当 cc 的所有形如 4k+34k+3 的质因子的幂次均为偶数
     */
public Boolean judgeSquareSum3(int c) {
	for (int i = 2; i * i <= c; i++) {
		int count = 0;
		if (c % i == 0) {
			//简单的因式分解
			while (c % i == 0) {
				count++;
				c /= i;
			}
			/** 到此 c被拆成当前i的n次方 乘 某个无法被当前i整除的数(但是该数进过不断循环总归会变成别的质数的乘积) */
			/** 费马平方和定理 */
			/** 其他形式的无所谓 只有形为4k + 3 且幂次为奇数的过不了 */
			if (i % 4 == 3 && count % 2 != 0)
			                    return false;
		}
	}
	return c % 4 != 3;
}

平方数之和(简单)680.

题目:给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串
一、双指针法
可以把这个题化为数学上的两个蚂蚁在一条轴线上相向运动,在蚂蚁相遇前的见到的每个数字都相等即是。一个回文字符
一旦数字不相等,让两只蚂蚁分别忘掉这个数字,继续前行,当忘掉的次数大于1时,说明这个字符串无法成为一个回文字符串

public Boolean validPalindrome(String s) {
	int low = 0, high = s.length() - 1;
	while (low < high) {
		char c1 = s.charAt(low), c2 = s.charAt(high);
		if (c1 == c2) {
			low++;
			high--;
		} else {
			Boolean flag1 = true, flag2 = true;
			for (int i = low, j = high - 1; i < j; i++, j--) {
				char c3 = s.charAt(i), c4 = s.charAt(j);
				if (c3 != c4) {
					flag1 = false;
					break;
				}
			}
			for (int i = low + 1, j = high; i < j; i++, j--) {
				char c3 = s.charAt(i), c4 = s.charAt(j);
				if (c3 != c4) {
					flag2 = false;
					break;
				}
			}
			return flag1 || flag2;
		}
	}
	return true;
}

归并两个数组相关问题

合并两个有序数组88.

题目:给定两个有序数组,把两个数组合并为一个。

Input: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
Output: nums1 = [1,2,2,3,5,6]

一、双指针法
类似于两列蚂蚁排队进洞,其中每一列都按高低排好了,先在要求两队按从低到高依次进洞
两个指针分别指向蚂蚁的两队,比较后将较小的先入洞(即指针向后移动一位),当某一队全部进去后,剩下的蚂蚁高低是有序的直接入洞即可

public int[] merge2(int[] nums1, int m, int[] nums2, int n) {
	int t = m-- + n-- - 1;
	while (m >= 0 && n >= 0){
		nums1[t--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];
	}
	while (n >= 0){
		nums1[t--] = nums2[n--];
	}
	return nums1;
}

524.通过删除字母匹配到字典里最长单词524.

给定一个字符串和一个字符串字典,找到字典里面最长的字符串,该字符串可以通过删除给定字符串的某些字符来得到。如果答案不止一个,返回长度最长且字典顺序最小的字符串。如果答案不存在,则返回空字符串。
一、双指针法
首先应该将字典中的字符串进行排序,排序的比较方案是:长度长的排前面,当长度相等时字典顺序小的排前面
然后从字典中第一个开始,类似两列蚂蚁,指针分别指向列首,如果相等就向后移动一位,直至任意队列的末尾
不断判断短的一列是否到末尾,若到达就输出true。没有就进行下一个字符串的比较。

public String findLongestWord(String s, List<String> d) {
	Collections.sort(d, new Comparator < String > () {
		public int compare(String s1, String s2) {
			return s2.length() != s1.length() ? s2.length() - s1.length() : s1.compareTo(s2);
		}
		//这个要多看几遍,还是分不清楚怎么弄
	}
	);
	for (int n = 0;n < d.size();n++) {
		String _str = d.get(n);
		for (int i = 0,j = 0;i < s.length() && j < _str.length();i++){
			if (s.charAt(i) == _str.charAt(j)) j++;
			if (j == _str.length()){
				return _str;
			}
		}
	}
	return "";
}

二、双指针不排序
每次判断时先判断包含与否,再比较长度,时间上可能有点损耗,但空间上不需要额外空间

public Boolean isSubsequence(String x, String y) {
	int j = 0;
	for (int i = 0; i < y.length() && j < x.length(); i++)
	            if (x.charAt(j) == y.charAt(i))
	                j++;
	return j == x.length();
}
public String findLongestWord(String s, List < String > d) {
	String max_str = "";
	for (String str: d) {
		if (isSubsequence(str, s)) {
			if (str.length() > max_str.length() || (str.length() == max_str.length() && str.compareTo(max_str) < 0))
			                    max_str = str;
		}
	}
	return max_str;
}

快慢指针相关问题

142.环形链表二142.

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

Input: S = "ADOBECODEBANC", T = "ABC"
Output: "BANC"

一、快慢指针法
在数学上,两个点在同一个线路上比赛,如果在线路中有环路的话,跑的快的点最终会追上跑的慢的点,这里便可以判断是否有环路
如果有环路,在两点相遇时,将跑的快的点放回起点,然后两个点以相同的速度运动,再次相遇时的位置就是环路的起点(这里快速每次移动两个单位,慢速每次移动一个单位)
数学上,设直线长度为x,环路长度为y,第一次相遇时,有x+n1y+d = 2(x+d)(1)
将其放回原点后再次移动,相遇时,有x = y - d+n2y(2)
通过化简可知上面两个等式说等价的,即有公式一可得到公式二

public ListNode detectCycle1(ListNode head) {
	ListNode slow = head;
	ListNode fast = head;
	do{
		if (fast == null || fast.next == null) return null;
		fast = fast.next.next;
		slow = slow.next;
	}
	while (fast != slow);
	fast = head;
	while (fast != slow){
		fast = fast.next;
		slow = slow.next;
	}
	return fast;
}

二、hashset方法
对于数据储存的结构我们知道有collection和map两种体系
collection中的list接口储存有序、可重复的数据,包括Arraylist、linkedlist、vector;set接口,储存无序的不可重复的数据,包括hashset、linkedhashset、treeset。
Map接口用来储存一对(Key-Value)对的数据,有Hashmap、LinkedHashMap、TreeMap、Hashtable、Properties
这里我们选用不可重复的无序的数据储存类型,HashSet,点从原点处开始移动,判断Hashset中是否有相同的数据,没有的话将该点的数据储存进入Hashset中,有的话输出这个节点

public ListNode detectCycle(ListNode head) {
	HashSet<ListNode> hashSet = new HashSet<>();
	ListNode node = head;
	while (node != null){
		if (hashSet.contains(node)){
			return node;
		}
		hashSet.add(node);
		node = node.next;
	}
	return null;
}

340.需要会员还没看

滑动窗口相关问题

76覆盖串

给你一个字符串 S、一个字符串 T 。请你设计一种算法,可以在 O(n) 的时间复杂度内,从字符串 S 里面找出:包含 T 所有字符的最小子串。

输入:S = "ADOBECODEBANC", T = "ABC"
输出:"BANC"

一、滑动窗口方法
数学上,要在有序集合S中找到集合T中的元素,最好对T中的几个元素进行重点标记,以显示出与众不同,这里选择用向量的方式作标记,因为会有符合要求的多种情况,不要对集合T本身做改变
类似于在一列蚂蚁中找到比较特殊的几种蚂蚁,并要求蚂蚁的距离最短。在队列的头部设置两个指针lo和hi,找到三个元素前hi向后移动,找到三种蚂蚁后,判断并记录距离,然后进行Lo的移动。类似一个滑动的窗口,直到到达队列的尾部为止。

public String minWindow3(String s, String t) {
	int[] chars = new int[128];
	Boolean[] flag = new Boolean[128];
	for (int i = 0;i < t.length();i++){
		flag[t.charAt(i)] = true;
		++chars[t.charAt(i)];
	}
	int l = 0;
	int cnt = 0;
	int _l = 0;
	int len = s.length()+1;
	//这里+1是灵魂,以免出现a与aa的情况
	for (int r = 0;r < s.length();++r) {
		if (flag[s.charAt(r)]) {
			if (--chars[s.charAt(r)] >= 0) {
				++cnt;
			}
			while (cnt == t.length()) {
				if (r - l + 1 < len) {
					_l = l;
					len = r - l + 1;
				}
				if (flag[s.charAt(l)] && ++chars[s.charAt(l)] > 0) {
					--cnt;
				}
				++l;
			}
		}
	}
	return len > s.length() ? "" : (String) s.substring(_l,_l+len);
}

HashMap的方法

Map<Character, Integer> ori = new HashMap<Character, Integer>();
Map<Character, Integer> cnt = new HashMap<Character, Integer>();
public String minWindow1(String s, String t) {
	int tLen = t.length();
	for (int i = 0; i < tLen; i++) {
		char c = t.charAt(i);
		ori.put(c, ori.getOrDefault(c, 0) + 1);
	}
	int l = 0, r = -1;
	int len = Integer.MAX_VALUE, ansL = -1, ansR = -1;
	int sLen = s.length();
	while (r < sLen) {
		++r;
		if (r < sLen && ori.containsKey(s.charAt(r))) {
			cnt.put(s.charAt(r), cnt.getOrDefault(s.charAt(r), 0) + 1);
		}
		while (check() && l <= r) {
			if (r - l + 1 < len) {
				len = r - l + 1;
				ansL = l;
				ansR = l + len;
			}
			if (ori.containsKey(s.charAt(l))) {
				cnt.put(s.charAt(l), cnt.getOrDefault(s.charAt(l), 0) - 1);
			}
			++l;
		}
	}
	return ansL == -1 ? "" : s.substring(ansL, ansR);
}
public Boolean check() {
	Iterator iter = ori.entrySet().iterator();
	while (iter.hasNext()) {
		Map.Entry entry = (Map.Entry) iter.next();
		Character key = (Character) entry.getKey();
		Integer val = (Integer) entry.getValue();
		if (cnt.getOrDefault(key, 0) < val) {
			return false;
		}
	}
	return true;
}
原文地址:https://www.cnblogs.com/suit000001/p/13582365.html