双指针的思想
双指针其实就是两个变量,看题可以理解
双指针的分类:如图这种一个在前一个在后的方式称为快慢指针
从两端向中间走:对撞型指针或者相向指针(常用)
从中间向两边走:背向型
插入排序用的是快慢型双指针
快速排序用的是对撞型双指针:二叉树的前序遍历+对撞型双指针
删除元素专题
该专题其实是添加的变形
原地移除所有数值等于val的元素
Leetcode27
快慢双指针
思路分析:
slow之前的为有效部分,fast则指向当前访问元素
遍历过程会出现两种情况:
如果nums[fast]的值不为val,就将其所指向的值移动到nums[slow++]处,slow有效个数+1;
如果nums[fast]的值等于val,仍未找到与val不同的值,fast++(继续遍历链表),slow等待
代码实现:
public int removeElement(int[] nums, int val) {
int length = nums.length;
int slow = 0;
//fast从头到尾遍历整个链表,fast不管怎样都要进行++
for (int fast = 0;fast<length;fast++){
//找到不同的值
if (nums[fast] != val){
nums[slow++] = nums[fast];
}
}
//slow是有效长度,每发现一个都要进行++
return slow;
}
对撞双指针
对撞双指针也叫做交换移除
思路分析:
与上一种做法思路类似,只不过是从右侧往左遍历,找到不是val的值来顶替左侧是val的值
当左指针>右指针时结束遍历(如果是奇数的数组有可能会出现恰好中间值也满足条件的情况)
代码实现
public int removeElement(int[] nums, int val) {
int right = nums.length - 1;
int left = 0;
//从左往右开始进行改变,将左边全部变为为有效值
for (; left <= right; ) {
if (nums[left] == val && nums[right] != val) {
//恰好直接进行互换并覆盖,两个指针同时移动
nums[left++] = nums[right--];
}
else if (nums[left] != val){
left++;
}
else if (nums[right] == val){
right--;
}
}
return left;
}
删除有序数组中的重复项
Leetcode26
思路分析:
一眼双指针类型的题目,一个指针负责数组遍历,另一个指向有效数组的最后一个位置
注意事项:slow指针从1开始,毕竟如果数组只有一个元素直接返回slow就可以了
数组中的第一个元素不需要比较,从第二位开始就可以了
代码实现:
public int removeDuplicates(int[] nums) {
int slow = 1;
int fast = 0;
for (; fast < nums.length ; fast++) {
if (nums[fast] != nums[slow-1]) {
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
元素奇偶移动专题
leetcode905
题目要求:奇偶排序数组,令数组所有的偶数元素在奇数元素的前面
思路分析:
方法一:创建一个新数组,两次遍历数组分别把偶数和奇数添加到新数组中去(不推荐)
方法二:与上一道题类似,使用对撞型指针的方法(只不过将比较的对象变成了奇偶数)
由于需要整个数组中的所有元素,不能使用覆盖法(与删除有所区别),不过也是左右指针同时遍历,如果左边符合条件就往右走一步,右边符合条件就往左走一步(直到left>right)遍历完整条链表
代码实现:
方法二:
public int[] sortArrayByParity(int[] nums) {
int left = 0;
int right = nums.length-1;
int temp;
//左右指针相遇后退出
for (;left <= right;){
//左右条件都满足,左右各走一步
if (nums[left] %2 ==1 && nums[right] %2 ==0){
temp = nums[left];
nums[left] = nums[right];
nums[right]= temp;
left++;
right--;
}
//左边符合条件往后走
if (nums[left] %2 ==0){
left++;
}
//右边符合条件往左走
if (nums[right] %2 ==1){
right--;
}
}
return nums;
}
数组轮转问题
leetcode189
题目要求:将一个数组中的元素向右轮转k个位置,k是非负数
思路分析:
首先想到的就是通过逐次移动实现,实际做起来麻烦
所以推荐方法:两轮翻转
具体实现如下:
1)对整个数组实行翻转,例如[1,2,3,4,5,6,7]先将整体翻转成[7,6,5,4,3,2,1]
2)从k处分隔成左右两个部分,这里就是根据k分成两组[7,6,5]和[4,3,2,1]
3)最后将两个再次翻转得到[5,6,7]和[1,2,3,4],最终结果就是[5,6,7,1,2,3,4]
翻转方法:就是双指针前后交换
代码实现
public static void rotate(int[] nums, int k) {
//当k大于nums.length其实是走过一轮,要取余
k %= nums.length;
//两次翻转
reverse(nums, 0, nums.length - 1);
//第二轮翻转
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
//翻转方法
public static void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start += 1;
end -= 1;
}
}
数组的区间专题
Leetcode228
思路分析:
经典的双指针问题,让慢指针记录连续区间的起始位置,让快指针进行逐一遍历
一旦发现不连续则记录该区间,并将该区间记录到list中去
存在两个边界:fast+1 == nums.length要写在前面,不然会导致后面的条件出现数组越界的问题
nums[fast] + 1 != nums[fast+1] 连续的条件,如果满足就继续遍历
代码实现:
public static List<String> summaryRanges(int[] nums) {
List<String> res = new ArrayList<>();
// slow 初始指向第 1 个区间的起始位置
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]) {
// 将当前区间 [slow, fast] 写入结果列表
StringBuilder sb = new StringBuilder();
sb.append(nums[slow]);
if (slow != fast) {
sb.append("->").append(nums[fast]);
}
res.add(sb.toString());
// 将 slow 指向更新为 fast + 1,作为下一个区间的起始位置
slow = fast + 1;
}
}
return res;
}
字符串替换空格问题
题目要求:实现一个函数,将一个字符串中的每个空格替换成"%20"
思路分析:
第一种方法:如果长度不可变,就重新申请个更大空间,原数组出现空格位置直接替换即可
Java中字符串可以直接拼接
第二种方法:从头开始遍历整个字符串,遇到空格就将后面元素移动(效率低)
先遍历一遍字符串,统计出字符串中空格的总数,找出新串的长度 = 原来的长度+2*空格数目
然后从字符串的尾部开始复制和替换操作,利用双指针分别指向原始字符串和新字符串的末尾
出现两种情况: fast指向的不是空格,则将其直接复制到slow位置,然后fast和slow同时向前走
fast指向的是空格,在slow位置插入一个%20,fast向前走一步
//方法一
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;
}
//方法二
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;
//当slow == fast说明前面已经没有空格了,可以节约时间
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();
}