Leetcode 129 移除元素 | 轮转数组
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
2 代码实现
水的,双指针。
c++
class Solution { public: int removeElement(vector<int>& nums, int val) { int n = nums.size() ; int left = 0 ; for (int right = 0 ; right < n ; right ++){ if (nums[right] != val ){ nums[left] = nums[right ]; left ++ ; } } return left ; } };js
/** * @param {number[]} nums * @param {number} val * @return {number} */ var removeElement = function(nums, val) { const n = nums.length ; let left = 0 ; for(let right = 0 ; right < n ; right ++){ if (nums [right] != val){ nums[left] = nums[right]; left ++ ; } } return left; };3 题目
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3 输出:[5,6,7,1,2,3,4]解释: 向右轮转 1 步: [7,1,2,3,4,5,6]向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,1,2,3,4]
示例 2:
输入:nums = [-1,-100,3,99], k = 2 输出:[3,99,-1,-100] 解释: 向右轮转 1 步: [99,-1,-100,3] 向右轮转 2 步: [3,99,-1,-100]
提示:
1 <= nums.length <= 105-231 <= nums[i] <= 231 - 10 <= k <= 105
4 代码实现
c++
class Solution { public: void rotate(vector<int>& nums, int k) { int n = nums.size(); vector<int> newArr(n); for (int i = 0; i < n; ++i) { newArr[(i + k) % n] = nums[i]; } nums.assign(newArr.begin(), newArr.end()); } }; js
/** * @param {number[]} nums * @param {number} k * @return {void} Do not return anything, modify nums in-place instead. */ var rotate = function(nums, k) { const n = nums.length ; k = k % n ; let res = new Array(n) ; for(let i = 0 ; i < n ; i ++){ res[(i + k )% n ] = nums[i]; } for (let i = 0 ; i < n ; i++){ nums[i] = res [i]; } };思考
之前看到链表里这种轮转的操作是改成循环列表来首尾相接,其实我看了一下题解的代码,实现思路就是开辟一个新的数组。然后在循环里面操作,类似于循环数组,取余去重。主要就是要得到新的地址...
js写卡住了
/** * @param {number[]} nums * @param {number} k * @return {void} Do not return anything, modify nums in-place instead. */ var rotate = function(nums, k) { int n = nums.length ; let res = [] ; for(let i = 0 ; i < n ; i ++){ res[(i + n )% k ] = nums[i]; } };你现在是想完成 JavaScript 版本的数组旋转函数,但代码里有语法错误和逻辑错误,并且不知道如何把临时数组的结果写回原数组。我先帮你修正问题,再完整实现这个功能。
1. 先分析你代码中的问题
- 语法错误:JavaScript 中声明变量不能用
int,应该用let/const; - 逻辑错误:旋转的核心公式写错了,应该是
(i + k) % n而不是(i + n) % k(而且 k 可能大于数组长度,需要先对 k 取模); - 功能缺失:没有把临时数组
res的值赋值回原数组nums(题目要求原地修改)。
2. 完整的正确代码
/** * @param {number[]} nums * @param {number} k * @return {void} Do not return anything, modify nums in-place instead. */ var rotate = function(nums, k) { const n = nums.length; // 关键:k可能大于数组长度,先取模减少无效旋转(比如数组长度5,k=7等价于k=2) k = k % n; // 创建临时数组存储旋转后的结果 const res = new Array(n); // 核心逻辑:把原数组元素放到旋转后的正确位置 for (let i = 0; i < n; i++) { res[(i + k) % n] = nums[i]; } // 关键步骤:把临时数组的值写回原数组(实现原地修改) for (let i = 0; i < n; i++) { nums[i] = res[i]; } }; 3. 代码关键部分解释
(1)k = k % n 的作用
比如数组长度是 5,k=7,旋转 7 次和旋转 2 次效果完全一样(7 % 5 = 2),这一步能避免多余的计算,也能防止 k 为 0 时的无效操作。
(2)核心公式 (i + k) % n
假设原数组是 [1,2,3,4,5],k=2:
- i=0 时,
(0+2)%5=2→ res[2] = 1 - i=1 时,
(1+2)%5=3→ res[3] = 2 - i=2 时,
(2+2)%5=4→ res[4] = 3 - i=3 时,
(3+2)%5=0→ res[0] = 4 - i=4 时,
(4+2)%5=1→ res [1] = 5最终 res =[4,5,1,2,3],符合旋转 2 次的结果。
(3)把 res 写回 nums
题目要求 modify nums in-place(原地修改),不能直接 nums = res(这会让 nums 指向新数组,原数组不会被修改),必须逐个赋值 nums[i] = res[i]。
4. 测试用例
// 测试1:常规情况 let nums1 = [1,2,3,4,5,6,7]; rotate(nums1, 3); console.log(nums1); // 输出 [5,6,7,1,2,3,4] // 测试2:k大于数组长度 let nums2 = [1,2,3]; rotate(nums2, 4); // 4%3=1,等价于旋转1次 console.log(nums2); // 输出 [3,1,2] // 测试3:k=0(不旋转) let nums3 = [1,2]; rotate(nums3, 0); console.log(nums3); // 输出 [1,2] 进阶优化(可选)
如果想不用临时数组,直接原地旋转(空间复杂度 O (1)),可以用 “三次反转法”:
var rotate = function(nums, k) { const n = nums.length; k = k % n; // 辅助函数:反转数组[start, end]区间的元素 const reverse = (arr, start, end) => { while (start < end) { [arr[start], arr[end]] = [arr[end], arr[start]]; start++; end--; } }; reverse(nums, 0, n-1); // 反转整个数组 reverse(nums, 0, k-1); // 反转前k个元素 reverse(nums, k, n-1); // 反转剩余元素 }; 总结
- 核心错误:旋转公式写错(
(i + k) % n而非(i + n) % k),且未处理 k 大于数组长度的情况; - 原地修改关键:不能直接赋值数组,需逐个把临时数组的值写回原数组;
- 优化思路:三次反转法可实现 O (1) 空间复杂度的原地旋转。
5 小结
没什么难点,主要是数组的api,还有一些细节,基础要更扎实一点,然后注意专注!!一题是昨天的一题是今天白天玩玩学学折腾的,我觉得自己不够专心,总是在想着玩...
召唤ai老师
核心易错点 & 基础知识点总结
1. 数组 “原地修改” 的关键
- 不能直接替换数组引用(比如 JS 中
nums = res),必须通过索引逐个赋值(nums[i] = res[i]); - C++ 中
vector的assign函数是例外(会直接替换原数组内容),但本质也是底层重新赋值。
2. 取模运算的核心作用
- 处理 “循环索引”:轮转数组中
(i + k) % n让索引超出数组长度时,回到开头(实现 “轮转”); - 简化无效操作:
k = k % n剔除重复轮转(比如 k=100,n=7,等价于 k=2)。
3. 语言基础细节
- JS 无
int类型,变量声明用let/const; - C++
vector与 JS 数组的索引操作一致(nums[i]),但 JS 数组无需提前指定长度,C++vector需用size()获取长度。
学习建议(针对 “不够专心 + 基础不扎实”)
1. 基础巩固:聚焦 “最小知识点闭环”
每做一道题,先拆解核心知识点(比如这两道题的核心是「双指针」「取模运算」「数组索引」),做完后用 5 分钟总结:
- 这道题的核心公式 / 逻辑是什么?
- 我写错的地方是语法问题还是思路问题?比如轮转数组你最初写错公式,就是 “循环索引” 的思路没吃透,可单独写 2 个例子(比如 n=5,k=2)手动计算索引,验证公式。
2. 专注提升:短时间高强度练习
- 每次只做 1 道题,设定 20 分钟时限,期间关闭无关软件,专注拆解问题;
- 卡壳时先自己梳理:“我现在卡在哪一步?是语法不会还是思路不懂?” 比如你最初不知道怎么把临时数组写回原数组,就是 “原地修改” 的概念没吃透,针对性查知识点即可。
3. 复盘简化:用 “一句话总结” 巩固
每道题做完后,用一句话总结核心解法:
- 移除元素:双指针,快指针找有效元素,慢指针存有效元素;
- 轮转数组:临时数组 + 取模算索引,或三次反转实现原地轮转。
最终小结
- 这两道题无复杂算法,核心是数组基础操作 + 细节处理(取模、原地修改、双指针);
- 易错点集中在 “取模公式” 和 “原地修改的实现方式”,通过手动算例子、对比错误代码可快速纠正;
- 提升专注度的关键是 “拆解问题 + 限时练习”,把注意力聚焦在 “解决当前最小问题” 上,而非想着其他事。
你能意识到 “不够专心” 的问题已经很棒,接下来只要把每道题的核心细节吃透,用短时间、高专注的方式练习,基础和效率都会快速提升。