算法通关村第二关——双指针思想及应用
双指针思想
思路:指在使用两个变量来进行相互配合,相互操作
例子:删除重复元素[1,2,2,2,3,3,3,5,5,7,8],重复元素只保留一个
图示:
删除元素专题
原地移除所有数值等于val的元素
题目:LeetCode:27
思路:可以使用三种双指针的思路:
-
快慢双指针,slow在相等val处等待,等待fast遍历到非val值,然后进行替换
public int removeElement(int[] nums, int val) { int slow = 0; for (int fast = 0; fast < nums.length; fast++) { if (nums[fast]!=val){ nums[slow]= nums[fast]; slow++; } } return slow; }
-
对撞双指针:思路为从右侧找到不是val的值来替换左侧是val的值
public int removeElement(int[] nums, int val) { //1.对撞双指针 int left = 0; int right = nums.length-1; for (; left <= right;) { if (nums[left]==val&&nums[right]!=val){ int tmp = nums[right]; nums[right]= nums[left]; nums[left]= tmp; } if (nums[left]!=val){ left++; } if (nums[right]==val){ right--; } } return left; }
-
两种结合,在第二种的基础上不做交换,而是直接做覆盖,所以不必判断右边是否为val,因为会慢慢替换
public int removeElement(int[] nums, int val) { //1.对撞双指针+覆盖法 int left = 0; int right = nums.length-1; for (; left <= right;) { if (nums[left]==val){ nums[left]= nums[right]; right--; } if (nums[left]!=val){ left++; } } return left; }
删除有序数组中的重复项
题目:leetCode 26
思路:双指针,一个指针负责数组遍历,一个指向有效数组的最后一个位置,注意:可以另slow=1,以减少不必要的操作,并且比较对象变为nums[slow-1]
解法:
-
双指针
/** * 利用双指针解决,注意点:可以令slow=1,并且比较的对象换做nums[slow-1],这样来比较换玩完之后位置的元素 * @param nums * @return */ public int removeDuplicates(int[] nums) { if (nums.length==0){ return 0; } //开始循环便利 int slow = 1; for (int fast = 0; fast < nums.length; fast++) { if (nums[fast]!=nums[slow-1]){ nums[slow]=nums[fast]; slow++; } } return slow; }
-
扩展(保留2个有效字符)
/** * 利用双指针解决,注意点:可以令slow=2,并且比较的对象换做nums[slow-2],这样来比较换完之后位置的元素 * @param nums * @return */ public int removeDuplicates(int[] nums) { if (nums.length<=2){ return nums.length; } //开始循环便利 int slow = 2; for (int fast = 2; fast < nums.length; fast++) { if (nums[fast]!=nums[slow-2]){ nums[slow]=nums[fast]; slow++; } } return slow; }
元素奇偶移动专题
题目:LeetCode 905
思路:最简单的思路是 搞一个临时数组,然后利用双指针我们可以搞一个对撞双指针,通过比较是否奇偶来进行对换
解法:
-
对撞双指针
/** * 利用对撞双指针实现,两天碰到奇偶的时候进行交换 * @param nums * @return */ public int[] sortArrayByParity(int[] nums) { if (nums.length<=1){ return nums; } int left = 0; int right = nums.length-1; while (left<=right){ while (left<nums.length&&nums[left]%2==0){ left++; } while (right>=0&&nums[right]%2==1){ right--; } if (left>=right){ return nums; } int tmp = nums[right]; nums[right] = nums[left]; nums[left] = tmp; } return nums; }
优化版本
/** * 利用对撞双指针实现,两天碰到奇偶的时候进行交换() * @param nums * @return */ public int[] sortArrayByParity(int[] nums) { if (nums.length<=1){ return nums; } int left = 0; int right = nums.length-1; while (left<=right){ if (nums[left]%2>nums[right]%2){ int tmp = nums[right]; nums[right] = nums[left]; nums[left] = tmp; } if(nums[left]%2==0)left++; if(nums[right]%2==1)right--; } return nums; }
数组轮换问题
题目:leetCode:189
思路:思路跟链表的轮换问题一致,先整个进行反转,然后在反转0-k-1部分和k-length部分,注意K要是除于length的余,
解法:
-
翻转数组
/** * 数组轮转,先整体翻转,在根据k处分隔成左右两个部分分别翻转 * @param nums * @param k */ public void rotate(int[] nums, int k) { k %= nums.length; //1. 先进行整体翻转 revers(nums,0,nums.length-1); //2.分成两个部分进行分别反转 revers(nums,0,k-1); revers(nums,k,nums.length-1); } public void revers(int[]nums,int start,int end){ while (start<end){ int tmp = nums[start]; nums[start] = nums[end]; nums[end] = tmp; start++; end--; } }
数组区间问题
题目:leetCode:228
思路 :通过快慢指针来实现,慢指针值区间 头,快指针遍历,遇上不连续的区间则输出
解法:
-
快慢指针
/** * 使用快慢指针来进行解决 * @param nums * @return */ public List<String> summaryRanges(int[] nums) { ArrayList<String> list = new ArrayList<>(); int fast = 0; int slow = 0; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < nums.length; i++) { if (nums[slow] == nums[fast]) { //相等则为刚开始区间 stringBuilder.append(nums[slow]); } if (fast+1>=nums.length){ //说明已经便利到尽头 if (slow!=fast){ stringBuilder.append("->" + nums[fast]); } list.add(stringBuilder.toString()); break; } //不相等则继续递增 if (nums[fast + 1] != nums[fast] + 1) { //不递增,结束 if (slow != fast) { stringBuilder.append("->" + nums[fast]); } list.add(stringBuilder.toString()); stringBuilder = new StringBuilder(); //递增 fast++; slow = fast; }else { fast++; } } return list; }
-
优化版本
/** * 使用快慢指针来进行解决 * @param nums * @return */ public List<String> summaryRanges(int[] nums) { ArrayList<String> list = new ArrayList<>(); int slow = 0; for (int fast = 0; fast < nums.length; fast++) { //fast向后遍历,直到不满足连续递增(即 nums[fast + 1] != nums[fast] + 1) //或者fast 达到数组边界,则当前连续递增区间[slow,fast]遍历完毕,将其写入结果列表 if (fast + 1 == nums.length || nums[fast + 1] != nums[fast] + 1) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(nums[slow]); if (slow != fast) { stringBuilder.append("->" + nums[fast]); } list.add(stringBuilder.toString()); //将 slow 指向更新为fast+1,作为下一个区间的起始位置 slow = fast + 1; } } return list; }
字符串替换空格问题
题目:剑指offer
思路:1. 利用char数组管理,直接把原来的原始数组的中的空格位置进行替换,如果不能申请额外的空间,则通过双指针进行移动和替换
解法 :
-
利用char数组(可变数组)
/** * 方法1:创建新的字符串 * * @param str * @return */ public static String replaceSpace1(StringBuffer str) { String res = ""; for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (c == ' ') res += "%20"; else res += c; } return res; }
-
利用 快慢双指针(固定数组)
/** * 方法2:在原数组基础上改 * * @param str * @return */ public static String replaceSpace2(StringBuffer str) { if (str == null) return null; int numOfblank = 0;//空格数量 int len = str.length(); for (int i = 0; i < len; i++) { //计算空格数量 if (str.charAt(i) == ' ') numOfblank++; } str.setLength(len + 2 * numOfblank); //设置长度 int fast = len - 1; //两个指针 int slow = (len + 2 * numOfblank) - 1; while (fast >= 0 && slow > fast) { char c = str.charAt(fast); if (c == ' ') { fast--; str.setCharAt(slow--, '0'); str.setCharAt(slow--, '2'); str.setCharAt(slow--, '%'); } else { str.setCharAt(slow, c); fast--; slow--; } } return str.toString(); }