zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    买二手房的税费详细版本
    实时推荐部分代码
    卡片推荐部分代码
    详解一下网络广告cpc、cpm、cpl、cpa、cps、cpr的计费方法是什么
    吴军硅谷来信《三板斧破四困境》
    JS实现的在线推荐逻辑
    mongo数据库时间存储的问题
    crontab 定时的陷阱
    【剑指offer】栈的压入、弹出序列
    【剑指offer】包含min函数的栈
  • 原文地址:https://www.cnblogs.com/suit000001/p/13582365.html
Copyright © 2011-2022 走看看