两道算法题
两道算法题
合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 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.length == m + nnums2.length == n
代码如下:
class Solution {
public:void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {int i=m-1;//nums1下标最后一个有效元素int j=n-1;//nums2小标最后一个有效元素int k=m+n-1;//扩容后的while(j>=0){if(i>=0&&nums1[i]>nums2[j]){nums1[k--]=nums1[i--];}else{nums1[k--]=nums2[j--];}}}
};
一、题目核心要求
题目要求很简单:
- •你有两个**已经排好序(非递减)**的数组:
nums1和nums2。 - •
nums1的总长度是m + n,但有效元素只有前m个,后面n个位置是 0(预留出来给你放nums2的元素)。 - •你要把
nums2合并到nums1中,并且合并后的nums1仍然是有序的。 - •不能返回新数组,必须直接修改
nums1。
二、代码思路解析
class Solution {
public:void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {int i = m - 1; // nums1 有效部分的最后一个元素下标int j = n - 1; // nums2 的最后一个元素下标int k = m + n - 1; // nums1 最终位置的最后一个位置// 从后往前比较,选较大的放在 nums1 的末尾while (j >= 0) {if (i >= 0 && nums1[i] > nums2[j]) {nums1[k--] = nums1[i--];} else {nums1[k--] = nums2[j--];}}}
};
关键点:
- 1.为什么从后往前? •因为
nums1后面是空位(0),如果从前往后合并,会覆盖nums1中还没比较的元素。 •从后往前合并,可以利用 nums1 尾部的空位,不会破坏还未比较的数据。 - 2.三个指针的作用: •
i:指向nums1有效部分的最后一个元素(初始为m-1)。 •j:指向nums2的最后一个元素(初始为n-1)。 •k:指向nums1最终结果的最后一个位置(初始为m+n-1)。 - 3.循环逻辑: •比较
nums1[i]和nums2[j],把较大的那个数放到nums1[k]。 •然后对应的指针(i或j)和k往前移一位。 •循环条件是j >= 0,意思是只要nums2中还有元素没处理完,就继续。 •如果nums2的元素先用完了,那么nums1前面剩下的元素本来就在正确的位置,不需要再移动。
三、举例说明
用示例 1 来模拟一下:
nums1 = [1, 2, 3, 0, 0, 0], m = 3
nums2 = [2, 5, 6], n = 3
初始:
i = 2 (指向3), j = 2 (指向6), k = 5
步骤:
- 1.比较 3 和 6 → 6 大,把 6 放到 k=5:
[1,2,3,0,0,6],j=1, k=4 - 2.比较 3 和 5 → 5 大,把 5 放到 k=4:
[1,2,3,0,5,6],j=0, k=3 - 3.比较 3 和 2 → 3 大,把 3 放到 k=3:
[1,2,3,3,5,6],i=1, k=2 - 4.比较 2 和 2 → 一样大,走 else,把 2 放到 k=2:
[1,2,2,3,5,6],j=-1, k=1 - 5.j < 0,循环结束。结果正确。
四、如果你不理解的地方可能是
- 1.为什么循环条件是
while (j >= 0)? •因为当nums2的元素全部插入后,合并就完成了。nums1前面剩余的元素已经有序且位置正确。 - 2.如果
nums1的元素先比较完(i < 0)怎么办? •比如示例3:nums1 = [0], m=0, nums2=[1], n=1•一开始 i=-1, j=0, k=0。 •因为 i<0,直接走 else,把 nums2 的元素依次放进 nums1 前面剩余位置即可。 - 3.时间复杂度 O(m+n),空间复杂度 O(1)(没有使用额外数组)。
移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
- 更改
nums数组,使nums的前k个元素包含不等于val的元素。nums的其余元素和nums的大小并不重要。 - 返回
k。
用户评测:
评测机将使用以下代码测试您的解决方案:
int[] nums = [...]; // 输入数组
int val = ...; // 要移除的值
int[] expectedNums = [...]; // 长度正确的预期答案。// 它以不等于 val 的值排序。int k = removeElement(nums, val); // 调用你的实现assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {assert nums[i] == expectedNums[i];
}
如果所有的断言都通过,你的解决方案将会 通过。
示例 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 个元素之外留下了什么并不重要(因此它们并不计入评测)。
提示:
0 <= nums.length <= 1000 <= nums[i] <= 500 <= val <= 100
代码如下:
class Solution {
public:int removeElement(vector<int>& nums, int val) {int slow=0;for(int fast=0;fast<nums.size();fast++){if(nums[fast]!=val){nums[slow]=nums[fast];slow++;}}return slow;}
};
。
这个解法怎么理解?
想象你有两个指针:
- •快指针:侦察兵,负责遍历整个数组,检查每个元素
- •慢指针:建筑师,负责构建新的有效数组
工作流程:
- 1.快指针从头到尾扫描数组
- 2.当快指针发现一个不等于val的元素时: •把这个元素复制到慢指针的位置 •慢指针向前走一步
- 3.当快指针发现等于val的元素时: •直接跳过,慢指针不动
举例说明:nums = [3,2,2,3], val = 3
初始:slow=0, fast=0, nums=[3,2,2,3]
第1步:fast=0, nums[0]=3等于val → 跳过,slow不动
数组不变,slow=0, fast=1
第2步:fast=1, nums[1]=2不等于val → 复制到slow位置
nums[0] = nums[1] → 数组变成[2,2,2,3]
slow从0变成1, fast=2
第3步:fast=2, nums[2]=2不等于val → 复制到slow位置
nums[1] = nums[2] → 数组变成[2,2,2,3]
slow从1变成2, fast=3
第4步:fast=3, nums[3]=3等于val → 跳过,slow不动
数组不变,slow=2, fast=4
循环结束,返回slow=2
最终结果:数组前2个元素[2,2]是有效部分,长度=2
为什么这个解法更简单?
- 1.逻辑清晰:快指针只管扫描,慢指针只管接收有效元素
- 2.符合直觉:就像把好的元素挑出来放到前面
- 3.易于理解:不需要考虑元素交换和边界问题
这个解法的时间复杂度也是O(n),空间复杂度O(1),而且代码只有6行,非常简洁!
