自己记录一下刷题内容。
合并有序数组
题目
给你两个按 非递减顺序 排列的整数数组 nums1
**和 nums2
,另有两个整数 m
和 n
,分别表示 nums1
和 nums2
中的元素数目。
请你 合并 nums2
**到 nums1
中,使合并后的数组同样按 非递减顺序 排列。
注意: 最终,合并后数组不应由函数返回,而是存储在数组 nums1
中。为了应对这种情况,nums1
的初始长度为 m + n
,其中前 m
个元素表示应合并的元素,后 n
个元素为 0
,应忽略。nums2
的长度为 n
。
示例 1:
css
输入: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
解释: 需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
css
输入: nums1 = [1], m = 1, nums2 = [], n = 0
输出: [1]
解释: 需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
ini
输入: nums1 = [0], m = 0, nums2 = [1], n = 1
输出: [1]
解释: 需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
提示:
nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-109 <= nums1[i], nums2[j] <= 109
进阶: 你可以设计实现一个时间复杂度为 O(m + n)
的算法解决此问题吗?
法一:Array.prototype.sort()
第一时间就想到了这个,本来以为是一个不太行的方法,结果官方题解第一个就是这个。实在是没想到,原来算法题可以调库的吗???
思路:很简单,就直接将第二个数组塞到nums1里,然后对nums1进行调用sort即可。
代码:
js
/**
* @param {number[]} nums1
* @param {number} m
* @param {number[]} nums2
* @param {number} n
* @return {void} Do not return anything, modify nums1 in-place instead.
*/
var merge = (nums1, m, nums2, n)=>{
for(let j = 0; j<n; ++j){
nums1[m+j] = nums2[j];
}
nums1.sort((a, b)=>{
return a-b;
});
}
法二:双指针
刚才的实在是太奇特了,还是得正经做一下。
思路:用指示变量i,j指示数组下标,需要新建一个数组,比较原来两数组中元素的大小,决定将谁的元素插入到新数组中,同时注意移动下标。最后完成后,逐个将新数组的元素赋值给nums1。
代码:
js
/**
* @param {number[]} nums1
* @param {number} m
* @param {number[]} nums2
* @param {number} n
* @return {void} Do not return anything, modify nums1 in-place instead.
*/
var merge = function(nums1, m, nums2, n) {
let i=0, j=0;
const temp = [];
let cur; // 记录选中元素
while(i<m || j<n){
// 某一格数组到头了但还都在循环中,需要特殊判断,直接将没到头的那个数组的元素全按下标顺序塞进去即可
if(i === m){ // nums1到头了
cur = nums2[j++];
}else if(j === n){ // nums2到头了
cur = nums1[i++];
}else if(nums1[i] <= nums2[j]){ // 相等的情况下也是nums1优先进入新数组(满足nums1中元素必须在前面)
cur = nums1[i++];
}else{
cur = nums2[j++];
}
temp.push(cur); // 将选中的元素放到新数组中
}
for(let r = 0; r<temp.length; ++r){ // 回赋给nums1
nums1[r] = temp[r];
}
};
移除元素
题目
给你一个数组 nums
**和一个值 val
,你需要 原地 移除所有数值等于 val
**的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以**「引用」**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
go
// nums 是以"引用"方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
示例 1:
ini
输入: 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:
ini
输入: 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
解法
思路:找到值与输入的相等的元素,然后让它后面的元素依次向前覆盖即可,并更新当前的数组有效长度。
代码:
js
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) {
function doRemove(nums, index, len){ // 定义一下这个函数,依次向前覆盖
for(let i = index; i<len; ++i){
if(i+1 !== nums.length){
nums[i] = nums[i+1];
}
}
}
let numberOfDelete = 0;
let i = 0;
while(i<nums.length-numberOfDelete){
if(nums[i] === val){
doRemove(nums, i, nums.length-numberOfDelete);
++numberOfDelete;
--i;
}
++i;
}
return nums.length-numberOfDelete;
};
这个解法的时间和空间代价还是挺低的。
删除有序数组中的重复项
题目
给你一个 非严格递增排列 的数组 nums
,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。
考虑 nums
的唯一元素的数量为 k
,你需要做以下事情确保你的题解可以被通过:
- 更改数组
nums
,使nums
的前k
个元素包含唯一元素,并按照它们最初在nums
中出现的顺序排列。nums
的其余元素与nums
的大小不重要。 - 返回
k
。
判题标准:
系统会用下面的代码来测试你的题解:
java
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:
ini
输入: nums = [1,1,2]
输出: 2, nums = [1,2,_]
解释: 函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:
ini
输入: 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
已按 非严格递增 排列
法一:ES6 Set数据结构
思路:ES6新增的Set数据结构是一个类似数组的数据结构,但是其中不会出现重复元素,可以利用这个特性来解题。当然这个方法还是有些奇怪。
代码:
js
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
// 直接ES6
let temp = new Set(nums);
temp = Array.from(temp);
for(let i = 0; i<temp.length; ++i){
nums[i] = temp[i];
}
return temp.length;
};
时间很快但空间很拉跨(很显然空间要拉跨):
法二:双指针
还是得整点正常的解法的。双指针就很不错。
思路 :还是遍历,不过是从下标为1开始遍历,使用一个index记录一下不重复的数量同时当一下新数组的下标指针。需要注意,数组为有序数组,所以如果是重复元素,必然是相邻的,所以可以用nums[i]与nums[i-1]
来比较比较,看是不是相同,不相同的话就可以将nums[index]
覆盖为nums[i]
。
代码:
js
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
// 双指针
if(nums.length === 0){// 防止一个没有
return 0;
}
// 注意:因为是有序数组,所以相同重复元素肯定是相邻的
let index = 1;
for(let i = 1; i<nums.length; ++i){ // 从1开始找就行
if(nums[i] !== nums[i-1]){ // 前面的和后面的不相等,不重复了就
nums[index] = nums[i]; // 直接覆盖掉即可(要么是自己盖自己,要么是覆盖掉一个重复的)
++index; // 唯一元素数量++,并且相当于修改后的数组的下标后移
}
}
return index;
};
表现比较中庸。