目录
1.移除元素之27力扣真题
题目描述:
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝 int len = removeElement(nums, val); // 在函数里修改输入数组对于调用者是可见的。 // 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。 for (int i = 0; i < len; i++) { print(nums[i]); }
示例 1:
输入:nums = [3,2,2,3], val = 3 输出:2, nums = [2,2] 解释:函数应该返回新的长度2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2 输出:5, nums = [0,1,3,0,4] 解释:函数应该返回新的长度5, 并且 nums 中的前五个元素为0,1,3,0,4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100
思考点:Java中数组大小是固定的,不能直接从数组中删除元素,这道题要求我们原地修改,又不允许创建新的空间,只能对当前数组进行操作。
思路1:通过快慢指针 (两个指针同向而行,一快一慢),Java数组中并没有真正意义上的指针,但可以把索引看成数组中的指针,进行操作。具体地,需要快指针fast和慢指针slow,一开始它们都指向数组的起始位置,让fast先去前面探路,如果遇到的元素不为val那么就将该元素赋值给slow位置,并让slow前进一步。
代码实现:
class Solution {
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;
}
}
思考:以上代码的时间复杂度为O(n),在最坏情况下(输入数组中没有元素等于 val)快慢指针各遍历了数组一次,注意到题目中说 元素的顺序可以改变 可以对代码进行进一步的优化。
思路:试想这样一种情况,要移除的元素恰好在数组的开头,例如序列 [1,5,3,3,5],当 val 为 1 时,需要把每一个元素都左移一位。如果我们直接把最后一个元素5移动到开头,同样满足题目要求,此思想对要删除的元素在数组中较少时十分有效。此时使用左右指针(两个指针相向而行或者相背而行),分别指向数组的最左边和最右边,向中间移动遍历该序列,当左指针与右指针重合时,左右指针遍历了数组中所有的元素,这样两个指针在最坏的情况下合起来只遍历了一次数组。具体地,当左指针指向元素等于val时,把右指针指向元素赋值给左指针指向元素,并向左移动右指针,否则向右移动左指针,直到两个指针重合,完成遍历。
代码实现:
class Solution {
public int removeElement(int[] nums, int val) {
// 定义左右指针,分别指向数组的两端
int left = 0, right = nums.length - 1;
while (left <= right) {
// 如果左指针指向的元素等于要删除的元素
if (nums[left] == val) {
// 将右指针指向的元素覆盖掉这个元素
nums[left] = nums[right];
// 向左移动右指针
right--;
} else {
// 左指针指向的元素不是要删除的元素,继续往右遍历
left++;
}
}
return left;
}
}
2.巩固练习之26力扣真题
题目描述
给你一个 非严格递增排列 的数组 nums
,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。
考虑 nums
的唯一元素的数量为 k
,你需要做以下事情确保你的题解可以被通过:
- 更改数组
nums
,使nums
的前k
个元素包含唯一元素,并按照它们最初在nums
中出现的顺序排列。nums
的其余元素与nums
的大小不重要。 - 返回
k
。
判题标准:
系统会用下面的代码来测试你的题解:
int[] nums = [...]; // 输入数组 int[] expectedNums = [...]; // 长度正确的期望答案 int k = removeDuplicates(nums); // 调用 assert k == expectedNums.length; for (int i = 0; i < k; i++) { assert nums[i] == expectedNums[i]; }
如果所有断言都通过,那么您的题解将被 通过。
示例 1:
输入:nums = [1,1,2] 输出:2, nums = [1,2,_] 解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1,2。不需要考虑数组中超出新长度后面的元素。
示例 2:
输入:nums = [0,0,1,1,1,2,2,3,3,4] 输出:5, nums = [0,1,2,3,4] 解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为0,1,2,3,4 。不需要考虑数组中超出新长度后面的元素。
提示:
1 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
nums
已按 非严格递增 排列
思考:题目是一个非严格递增数组,说明数组中的重复元素必定是相邻的,要求删除重复元素,且不能打乱升序序列,实际上就是将不重复的元素移动到数组的左侧。
思路:使用两个指针,一个为p在前面,一个为q在后面,比较两个位置的元素是否相等,若相等,直接将q往后移动一位,若不相等,将q位置的元素赋值给p+1位置的元素,然后将q往后移动一位。
代码实现:
class Solution {
public int removeDuplicates(int[] nums) {
// 双指针,定义两个指针,初始指向数组中第一个和第二个位置
int p = 0, q = 1;
// 利用p不断遍历数组中的元素
while(q < nums.length) {
// 由于是非降序数组,重复元素一定是相邻的
// 判断两个指针指向位置元素是否相同
if(nums[p] != nums[q]){
// 如果不同,把q指针指向的元素移动到p指针后面一个位置
// ++p,先对p进行+1操作,然年再赋值
nums[++p] = nums[q];
}
// q++不断向后遍历数组
q++;
}
// 返回数组中元素个数,为p+1,p是从0开始的。
return p+1;
}
}
3.巩固练习之283力扣真题
题目描述:
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12] 输出: [1,3,12,0,0]
示例 2:
输入: nums = [0] 输出: [0]
提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1
进阶:你能尽量减少完成的操作次数吗?:参考快速排序的思想,一次排序
思路: 可以将所有的非0元素移动到数组的左边,然后将剩下的元素赋值为0。具体的定义一个指针变量j用于记录数组中非0元素的个数以及存放的位置,即遍历的时候每遇到一个非0元素就将其往数组左边挪,遍历完成之后j指针指向的位置就是0元素开始的位置,将j指针之后的元素都赋值为0即可。
代码实现:
class Solution {
public void moveZeroes(int[] nums) {
if (nums == null) {
return;
}
// 定义一个数记录数组中非0元素的个数
int j = 0;
for (int i = 0; i < nums.length; i++) {
// 如果该元素不为0,将其放到数组的左边
if (nums[i] != 0) {
nums[j++] = nums[i];
}
}
// 将非0元素之后的位置赋值为0
for (int i = j; i < nums.length; i++) {
nums[i] = 0;
}
}
}
思考:用0做分界点,把不等于0的元素放到0的左边,等于0的元素放到0的右边。具体的,使用两个指针i,j如果nums[i] != 0就交换nums[i]和nums[j],指针j用于记录非零数移动的个数以及下标。这样就只用进行一次遍历完成移除0的操作。
代码实现:
class Solution {
public void moveZeroes(int[] nums) {
if(nums == null){
return;
}
// 定义一个指针j记录不等于0的元素位置
int j = 0;
// 使用指针i遍历数组,如果i指向的元素不等于0就存放到j的位置,并且j++
for (int i = 0; i < nums.length; i++) {
if(nums[i] != 0){
int temp = nums[i];
nums[i] = nums[j];
nums[j++] = temp;
}
}
}
4.巩固练习之844力扣真题
题目描述:
给定 s
和 t
两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true
。#
代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
示例 1:
输入:s = "ab#c", t = "ad#c" 输出:true 解释:s 和 t 都会变成 "ac"。
示例 2:
输入:s = "ab##", t = "c#d#" 输出:true 解释:s 和 t 都会变成 ""。
示例 3:
输入:s = "a#c", t = "b" 输出:false 解释:s 会变成 "c",但 t 仍然是 "b"。
提示:
1 <= s.length, t.length <= 200
s
和t
只含有小写字母以及字符'#'
思路:最容易想到的就是将给定的字符串中的退格符和它前面的字符都删掉,得到最终的字符串形式,然后比较这两个字符串是否相等。这种情况可以使用Java重构字符串。由于Java中的字符串(String)是不可变的,这意味着一旦创建,它们的内容就不能被更改。如果需要修改字符串,可以使用StringBuilder
类来构建一个可变的字符序列,然后通过调用toString()
方法转换为字符串。StringBuilder
是用于修改字符串的类,使用append
方法来拼接字符串,效率更高。具体的,如果当前字符不是退格键将其添加到stringbuffer中,如果是退格键,先判断stringbuffer的长度,大于0时删除一位。
class Solution {
public boolean backspaceCompare(String s, String t) {
return build(s).equals(build(t));
}
// 使用重构字符串
public static String build(String s){
StringBuffer s1 = new StringBuffer();
int length = s.length();
for(int i = 0; i< length; i++){
char c = s.charAt(i);
if(c != '#'){
s1.append(c);
}else{
if(s1.length() > 0){
s1.deleteCharAt(s1.length()-1);
}
}
}
return s1.toString();
}
}
思路:一个字符是否会被删掉,取决于其后面有没有退格键#,因此可以借用快慢指针,快指针用于搜索字符后面有没有退格,如果没有的话,快慢指针都往后移,并且将快指针指向的字符赋值给慢指针指向的字符,如果快指针遇到了退格键,快指针继续前进,慢指针后退一步。(注意:这里要判断慢指针指向位置是否大于0,防止数组索引越界的情况)
代码实现:
class Solution {
public boolean backspaceCompare(String s, String t) {
char[] s1 = s.toCharArray();
char[] t1 = t.toCharArray();
return removeBackspace(s1).equals(removeBackspace(t1));
}
public static String removeBackspace(char[] c){
// 定义两个指针,相当于快慢指针
int i = 0, j = 0;
while(j < c.length){
if (c[j] != '#') {
// 遇到的不是退格键#,快慢指针都右移
c[i++] = c[j++];
}else{
// 遇到退格键,快指针j继续向前,慢指针i后退一步
j++;
if(i > 0){
i--;
}
}
}
return new String(c).substring(0,i);
}
}
5.巩固练习之977力扣真题
题目描述:
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10] 输出:[0,1,9,16,100] 解释:平方后,数组变为 [16,1,0,9,100] 排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11] 输出:[4,9,9,49,121]
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
已按 非递减顺序 排序
进阶:
- 请你设计时间复杂度为
O(n)
的算法解决本问题
思路:使用暴力解法, 直接对数组进行平方操作,然后再排序
class Solution {
public int[] sortedSquares(int[] nums) {
for(int i = 0; i < nums.length; i++){
nums[i] *= nums[i];
}
Arrays.sort(nums);
return nums;
}
}
此算法的时间复杂度为O(n+nlogn)
进阶思路:由于初始数组是有序的,绝对值较大的数在两端,即以0为分割线,平方后数组应该是由大到小然后由小到大的。此时可以考虑双指针法,定义左右指针分别指向数组的两端,比较两端的平方数的大小,依次将其存放在数组的右边。需要定义一个新数组来存放平方以后的数值。
代码实现:
class Solution {
public int[] sortedSquares(int[] nums) {
// 定义左右指针,分别指向数组的两端
int left = 0, right = nums.length - 1;
// 定义数组用于存放平方后的结果序列
int[] res = new int[nums.length];
// 新数组的索引
int index = right;
while (left <= right) {
// 最右边数的平方大于等于最左边的数的平方
if (nums[right] * nums[right] >= nums[left] * nums[left]) {
// 将大值存放在结果数组的相对最右边
res[index--] = nums[right] * nums[right];
// 右指针左移一位
right--;
} else {
// 将大值存放在结果数组的相对最右边
res[index--] = nums[left] * nums[left];
// 左指针右移一位
left++;
}
}
return res;
}
}
此算法的时间复杂度为O(n)