1. 删除元素专题
1.1 原地移除所有数组为val的元素
给你一个数组
nums
和一个值val
,你需要 原地 移除所有数值等于val
的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用
O(1)
额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思考
在删除的时候,从删除位置开始的所有元素都要向前移动,所以这题的关键是如果有很多值为val的元素的时候,如何避免反复向前移动呢?
快慢双指针
- 定义两个指针slow和fast,初始值都是0。
- slow之前的位置都是有效部分
- fast表示当前要访问的元素
- 遍历的时候,fast不断向后移动:
- 如果
nums[fast]
的值不为val,则将其移动到nums[slow++]
处 - 如果
nums[fast]
的值为val,则fast继续向前移动,slow先等待
- 如果
class Solution {
public int removeElement(int[] nums, int val) {
int slow=0;
for (int fast = 0; fast < nums.length - 1; fast++) {
if (nums[fast]!=val) {
nums[slow++]=nums[fast];
}
}
//返回最后新长度
return slow;
}
}
对撞双指针(交换移除)
思想:从右侧找到不是val的值来顶替左侧是val的值。
以nums = [0,1,2,2,3,0,4,2], val = 2为例:
- 若left指向元素不为2一直往右移动,当
nums[left]==2
,用nums[right]
覆盖,若nums[right]==2,则向左移动直到nums[right]!=2
- 终止条件:left==right
class Solution {
public int removeElement(int[] nums, int val) {
int left = 0;
int right = nums.length - 1;
for (left = 0; left <= right; ) {
if (nums[left] == val && nums[right] != val) {
//左右替换
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
if (nums[left] != val) left++;
if (nums[right] == val) right--;
}
return left;
}
}
拓展🤔==“对撞双指针+覆盖”法==
思路:当nums[left]==val
的时候,将nums[right]
位置的元素覆盖nums[left]
,right--
继续循环,否则才让left++
❗️这里特别注意一点,若nums[right]==val
,此时将nums[left]覆盖后,还是会有nums[left]==val
,则在下次循环中,继续将right–后的元素覆盖给它。
class Solution {
public int removeElement(int[] nums, int val) {
int left=0;
int right= nums.length-1;
for ( left = 0; left <=right;) {
if (nums[left]==val)
nums[left]=nums[right--];
else
left++;
}
return left;
}
}
特别说明:
举例数组[3,2,2,3],val=3: left=right的时候在数组下标为1的位置出,此时nums[left]!=3,要执行left++操作——>left!=right,且left>right,left=数组长度了。
1.2 删除有序数组的重复项
给你一个 升序排列 的数组
nums
,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回nums
中唯一元素的个数。考虑
nums
的唯一元素的数量为k
,你需要做以下事情确保你的题解可以被通过:
- 更改数组
nums
,使nums
的前k
个元素包含唯一元素,并按照它们最初在nums
中出现的顺序排列。nums
的其余元素与nums
的大小不重要。- 返回
k
。
思路:
- 快指针fast表示遍历数组到达的下标位置,初始fast=0
- 慢指针slow表示下一个不同元素要填入的下标位置,初始slow=1
class Solution {
public int removeDuplicates(int[] nums) {
int slow=1;//表示下一个不同元素要填入的下标位置
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast]!=nums[slow-1]) {//若不等,则将fast指向元素覆盖到slow位置出,slow在指向下一个要填入元素的位置
nums[slow]=nums[fast];
slow++;
}
}
return slow;
}
}
疑问
for循环中,
fast < nums.length
,而不是fast < nums.length-1
?
在这段代码中,我们需要遍历整个数组,并通过检查当前元素与上一个不同元素是否相等来判断是否是重复元素。如果循环条件是 fast < nums.length-1
,那么最后一个元素将被忽略,因为循环在 fast
到达 nums.length-1
时结束,而最后一个元素无法与下一个元素进行比较。
因此,为了确保将最后一个元素也考虑在内,循环的条件应为 fast < nums.length
。这样,在最后一次循环中,fast
将达到 nums.length-1
,并检查最后一个元素是否与前一个元素重复。如果重复,则不会将其添加到 slow
的位置,否则将其添加到 slow
位置并将 slow
的值加1。最后返回 slow
,即为不重复元素的个数。
2. 元素奇偶移动专题
给你一个整数数组
nums
,将nums
中的的所有偶数元素移动到数组的前面,后跟所有奇数元素。返回满足此条件的 任一数组 作为答案。
思路
对撞型双指针
left=0
、right=arr.length-1
- left:从0开始逐个检查每个位置是否为偶数。是偶数,跳过;是奇数,停下
- right:从右向左检查。是奇数,跳过;是偶数,停下
- 交换
array[left]
和array[right]
, - 再次循环,直到left>=right
class Solution {
public int[] sortArrayByParity(int[] nums) {
int left=0;
int right= nums.length-1;
while (left < right) {
//意思就是left为奇数,right为偶数
if (nums[left]%2>nums[right]%2) {
int temp=nums[left];
nums[left]=nums[right];
nums[right]=temp;
}
if (nums[left]%2==0)left++;
if (nums[right]%2==1)right--;
}
return nums;
}
}
3. 数组轮转问题
给定一个整数数组
nums
,将数组中的元素向右轮转k
个位置,其中k
是非负数。
这里介绍一种简单的方法:两轮翻转
思路
- 首先对整个数组实行翻转,例如 [1,2,3,4,5,6,7] 我们先将其整体翻转成[7,6,5,4,3,2,1]。
- 从 k 处分隔成左右两个部分,这里就是根据k将其分成两组 [7,6,5] 和[4,3,2,1]。
- 最后将两个再次翻转就得到[5,6,7] 和[1,2,3,4],最终结果就是[5,6,7,1,2,3,4]
class Solution {
public void rotate(int[] nums, int k) {
k%= nums.length;//这里要注意
reverse(nums,0, nums.length-1);
reverse(nums,0,k-1);
reverse(nums,k, nums.length-1);
}
public 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;
}
}
}
4. 字符串替换空格问题
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
class Solution {
public String replaceSpace(String s) {
StringBuilder res = new StringBuilder();
for (char c : s.toCharArray()) {
if (c==' ') {
res.append("%20");
}else {
res.append(c);
}
}
return res.toString();
}
}
这个 StringBuilder
对象 res
在这个程序中用于保存替换后的字符串。因为字符串是不可变类型,因此我们需要一个可变类型来保存新的字符串。每次遇到空格时,我们将 %20
添加到 res
中,否则将该字符添加到 res
中。遍历结束后,我们可以通过调用 toString()
方法将其转换为字符串类型。
使用 StringBuilder
对象可以大大提高字符串操作的效率。因为当我们对一个字符串进行重复的添加、删除、替换操作时,每次操作都会生成一个新的字符串对象,这会导致大量的内存分配和垃圾回收,并且效率低下。而使用 StringBuilder
对象则可以避免这些问题,因为它是可变类型,每次操作都是在原有的对象上进行修改,而不会生成新的字符串对象。