在这个数字与字符交织的世界里,字符串和数组构成了计算机科学中最基本的数据结构之一。无论是简单的文本处理还是复杂的算法设计,掌握字符串与数组的操作技巧都是每位程序员不可或缺的能力。本文旨在通过一系列精心挑选的算法题目,带领读者深入探索这些基本数据结构的奥秘,从基础操作到高级应用,我们将逐一解析字符串与数组的典型问题及解决方案。
让我们一同开启这段旅程,迎接字符与数字之间的碰撞,一网打尽那些令人着迷的字符串与数组算法吧!
合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意: 最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入: 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:
输入: nums1 = [1], m = 1, nums2 = [], n = 0
输出: [1]
解释: 需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
输入: nums1 = [0], m = 0, nums2 = [1], n = 1
输出: [1]
解释: 需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。
nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
题解
为了合并两个已排序的数组 nums1 和 nums2,我们可以采用一种叫做“双指针”的方法来实现。这种方法可以从数组的末尾开始比较并放置元素,这样就不需要额外的空间来存储合并后的数组。
function merge(nums1, m, nums2, n) {
// 初始化两个指针和合并数组的写入位置
let p1 = m - 1,
p2 = n - 1,
writePos = m + n - 1;
// 当两个数组中还有元素时,进行比较并合并
while (p1 >= 0 && p2 >= 0) {
if (nums1[p1] > nums2[p2]) {
nums1[writePos] = nums1[p1];
p1--;
} else {
nums1[writePos] = nums2[p2];
p2--;
}
writePos--;
}
// 如果 nums2 中还有剩余元素,则将它们复制到 nums1 的开头
while (p2 >= 0) {
nums1[writePos] = nums2[p2];
p2--;
writePos--;
}
}
// 示例
const nums1 = [1, 2, 3, 0, 0, 0];
const m = 3;
const nums2 = [2, 5, 6];
const n = 3;
merge(nums1, m, nums2, n);
console.log(nums1); // 输出: [1, 2, 2, 3, 5, 6]
代码解析
-
初始化指针:
p1: 指向nums1数组最后一个有效元素的索引。p2: 指向nums2数组最后一个有效元素的索引。writePos: 指向nums1数组中下一个需要写入的索引。
-
比较并合并:
- 从数组的末尾开始比较
nums1和nums2中的元素。 - 如果
nums1[p1]大于nums2[p2],则将较大的元素nums1[p1]写入到nums1[writePos],并将p1和writePos分别向前移动一位。 - 否则,将较小的元素
nums2[p2]写入到nums1[writePos],并将p2和writePos分别向前移动一位。
- 从数组的末尾开始比较
-
处理剩余元素:
- 如果
nums2中还有剩余元素,则将它们依次写入到nums1的剩余位置。
- 如果
这种方法的时间复杂度为 O(m + n),因为它只需要遍历一次两个数组即可完成合并。同时,这种方法的空间复杂度为 O(1),因为我们直接在 nums1 数组上进行了修改,而没有使用额外的空间。
移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
- 更改
nums数组,使nums的前k个元素包含不等于val的元素。nums的其余元素和nums的大小并不重要。 - 返回
k。
示例 1:
输入: nums = [3,2,2,3], val = 3
输出: 2, nums = [2,2,_,_]
解释: 你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
示例 2:
输入: nums = [0,1,2,2,3,0,4,2], val = 2
输出: 5, nums = [0,1,4,0,3,_,_,_]
解释: 你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
题解
我们可以使用双指针技术来原地移除数组中等于 val 的元素,并返回剩余元素的数量。
function removeElement(nums, val) {
let writeIndex = 0; // 用于记录写入的位置
// 遍历数组
for (let readIndex = 0; readIndex < nums.length; readIndex++) {
// 如果当前元素不是 val,则将其写入到 writeIndex 位置,并将 writeIndex 向前移动
if (nums[readIndex] !== val) {
nums[writeIndex] = nums[readIndex];
writeIndex++;
}
}
// 截断数组,只保留与 val 不同的元素
nums.length = writeIndex;
// 返回与 val 不同的元素数量
return writeIndex;
}
// 示例
const nums = [3, 2, 2, 3];
const val = 3;
const k = removeElement(nums, val);
console.log(k); // 输出: 2
console.log(nums.slice(0, k)); // 输出: [2, 2]
代码解析
-
初始化写入指针:
writeIndex用于记录下一个要写入的元素的位置。
-
遍历数组:
- 使用
readIndex从头到尾遍历数组nums。 - 如果当前元素
nums[readIndex]不等于val,则将该元素复制到nums[writeIndex]位置,并将writeIndex向前移动一位。
- 使用
-
截断数组:
- 最终,
writeIndex的值就是与val不同的元素数量。我们将数组的长度设置为writeIndex,以去除多余的元素。
- 最终,
-
返回结果:
- 返回
writeIndex作为与val不同的元素数量。
- 返回
这种方法的时间复杂度为 O(n),因为它只需要遍历一次数组即可完成元素的移除。同时,这种方法的空间复杂度为 O(1),因为我们直接在 nums 数组上进行了修改,而没有使用额外的空间。
删除有序数组中的重复项
给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
- 更改
nums数组,使nums的前k个元素包含不等于val的元素。nums的其余元素和nums的大小并不重要。 - 返回
k。
示例 1:
输入: nums = [3,2,2,3], val = 3
输出: 2, nums = [2,2,_,_]
解释: 你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
示例 2:
输入: nums = [0,1,2,2,3,0,4,2], val = 2
输出: 5, nums = [0,1,4,0,3,_,_,_]
解释: 你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
题解
function removeDuplicates(nums) {
if (nums.length === 0) return 0;
const seen = new Set(); // 用于记录已经出现过的元素
let uniqueIndex = 0; // 用于记录唯一元素的写入位置
// 遍历数组
for (let readIndex = 0; readIndex < nums.length; readIndex++) {
// 如果当前元素还没有出现过,则将该元素复制到 uniqueIndex 位置,并将 uniqueIndex 向前移动
if (!seen.has(nums[readIndex])) {
seen.add(nums[readIndex]);
nums[uniqueIndex] = nums[readIndex];
uniqueIndex++;
}
}
// 截断数组,只保留唯一元素
nums.length = uniqueIndex;
// 返回唯一元素的数量
return uniqueIndex;
}
我们只需要遍历数组一次,因此时间复杂度为 O(n)。使用了额外的 Set 来存储已经出现过的元素,最坏情况下需要存储所有元素,因此空间复杂度为 O(n)。
删除有序数组中的重复项 II
给你一个有序数组 nums ,请你原地删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在原地并在使用 O(1) 额外空间的条件下完成。
示例 1:
输入: nums = [1,1,1,2,2,3]
输出: 5, nums = [1,1,2,2,3]
解释: 函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。
不需要考虑数组中超出新长度后面的元素。
示例 2:
输入: nums = [0,0,1,1,1,1,2,3,3]
输出: 7, nums = [0,0,1,1,2,3,3]
解释: 函数应返回新长度 length = 7, 并且原数组的前七个元素被修改为 0, 0, 1, 1, 2, 3, 3。
不需要考虑数组中超出新长度后面的元素。
题解
function removeDuplicates(nums) {
if (nums.length <= 2) return nums.length;
let writeIndex = 2; // 用于记录写入的位置
// 遍历数组
for (let readIndex = 2; readIndex < nums.length; readIndex++) {
// 如果当前元素与前两个元素不同,或者当前元素与前一个元素相同但前一个元素只出现过一次,
// 则将该元素复制到 writeIndex 位置,并将 writeIndex 向前移动
if (
nums[readIndex] !== nums[writeIndex - 2] ||
(nums[readIndex] === nums[writeIndex - 1] &&
nums[writeIndex - 1] !== nums[writeIndex - 2])
) {
nums[writeIndex] = nums[readIndex];
writeIndex++;
}
}
// 截断数组,只保留最多出现两次的元素
nums.length = writeIndex;
// 返回新的数组长度
return writeIndex;
}
// 示例
const nums = [1, 1, 1, 2, 2, 3];
const k = removeDuplicates(nums);
console.log(k); // 输出: 5
console.log(nums.slice(0, k)); // 输出: [1, 1, 2, 2, 3]
代码解析
-
初始化写入指针:
writeIndex用于记录下一个要写入的元素的位置。初始化为 2,因为我们允许每个元素最多出现两次。
-
遍历数组:
-
使用
readIndex从第三个元素开始遍历数组nums。 -
对于每一个元素
nums[readIndex],我们需要判断是否应该将它复制到writeIndex位置。这里有两个条件:- 如果
nums[readIndex]与前两个元素nums[writeIndex - 2]不同,则将该元素复制到nums[writeIndex]位置,并将writeIndex向前移动一位。 - 如果
nums[readIndex]与前一个元素nums[writeIndex - 1]相同,但前一个元素nums[writeIndex - 1]与前两个元素nums[writeIndex - 2]不同,这意味着前一个元素只出现过一次,因此允许当前元素再次出现。
- 如果
-
-
截断数组:
- 最终,
writeIndex的值就是新数组的长度。我们将数组的长度设置为writeIndex,以去除多余的元素。
- 最终,
-
返回结果:
- 返回
writeIndex作为新数组的长度。
- 返回
这种方法的时间复杂度为 O(n),因为它只需要遍历一次数组即可完成元素的去重。同时,这种方法的空间复杂度为 O(1),因为我们直接在 nums 数组上进行了修改,而没有使用额外的空间。