Leetcode——链表和数组(3)
移动零
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
- 必须在原数组上操作,不能拷贝额外的数组。
- 尽量减少操作次数。
双指针
用替换法in-place来做,需要用两个指针,一个不停的向后扫,找到非零位置,然后和前面那个指针交换位置即可
class Solution {
public:
void moveZeroes(vector<int>& nums) {
for (int i = 0, j = 0; i < nums.size(); ++i) {
if (nums[i]) {
swap(nums[i], nums[j++]);
}
}
}
};
优化
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int left = 0, right = 0;
while (right < nums.size()) {
if (nums[right]) {
swap(nums[left++], nums[right]);
}
++right;
}
}
};
移除元素
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
需要一个变量用来计数,然后遍历原数组,
如果当前的值和给定值不同,就把当前值覆盖计数变量的位置,并将计数变量加1。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int res = 0;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] != val) nums[res++] = nums[i];
}
return res;
}
};
移除链表元素
删除链表中等于给定值 val
的所有节点。
示例:
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
遍历
定义几个辅助指针,然后遍历原链表,
遇到与给定值相同的元素,将该元素的前后连个节点连接起来,然后删除该元素即可,
要注意的是还是需要在链表开头加上一个dummy node
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *dummy = new ListNode(-1), *pre = dummy;
dummy->next = head;
while (pre->next) {
if (pre->next->val == val) {
ListNode *t = pre->next;
pre->next = t->next;
t->next = NULL;
delete t;
} else {
pre = pre->next;
}
}
return dummy->next;
}
};
简化
当判断下一个结点的值跟给定值相同的话,直接跳过下一个结点,
将next指向下下一个结点,而根本不断开下一个结点的next,更不用删除下一个结点了。
最后还要验证头结点是否需要删除,要的话直接返回下一个结点
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if (!head) return NULL;
ListNode *cur = head;
while (cur->next) {
if (cur->next->val == val) cur->next = cur->next->next;
else cur = cur->next;
}
return head->val == val ? head->next : head;
}
};
递归
通过递归调用到链表末尾,然后回来,需要要删的元素,将链表next指针指向下一个元素即可
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if (!head) return NULL;
head->next = removeElements(head->next, val);
return head->val == val ? head->next : head;
}
};
删除链表中的节点
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
现有一个链表 -- head = [4,5,1,9],它可以表示为:
示例 1:
输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
示例 2:
输入: head = [4,5,1,9], node = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
说明:
- 链表至少包含两个节点。
- 链表中所有节点的值都是唯一的。
- 给定的节点为非末尾节点并且一定是链表中的一个有效节点。
- 不要从你的函数中返回任何结果。
先把当前节点的值用下一个节点的值覆盖了,然后我们删除下一个节点即可
C++
class Solution {
public:
void deleteNode(ListNode* node) {
node->val = node->next->val;
ListNode *tmp = node->next;
node->next = tmp->next;
delete tmp;
}
};
Java
public class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
找到所有数组中消失的数字
给定一个范围在 1 ≤ a[i] ≤ n
( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。
找到所有在 [1, n] 范围之间没有出现在数组中的数字。
您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[5,6]
方法一
对于每个数字nums[i]
,
如果其对应的nums[nums[i] - 1]
是正数,我们就赋值为其相反数,
如果已经是负数了,就不变了,那么最后我们只要把留下的整数对应的位置加入结果res
中即可
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
vector<int> res;
for (int i = 0; i < nums.size(); ++i) {
int idx = abs(nums[i]) - 1;
nums[idx] = (nums[idx] > 0) ? -nums[idx] : nums[idx];
}
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] > 0) {
res.push_back(i + 1);
}
}
return res;
}
};
方法二
将nums[i]
置换到其对应的位置nums[nums[i]-1]
上去,
比如对于没有缺失项的正确的顺序应该是[1, 2, 3, 4, 5, 6, 7, 8]
,而我们现在却是[4,3,2,7,8,2,3,1]
,
我们需要把数字移动到正确的位置上去,
比如第一个4就应该和7先交换个位置,以此类推,最后得到的顺序应该是[1, 2, 3, 4, 3, 2, 7, 8]
,
我们最后在对应位置检验,如果nums[i]
和i+1
不等,那么我们将i+1
存入结果res中即可
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
vector<int> res;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] != nums[nums[i] - 1]) {
swap(nums[i], nums[nums[i] - 1]);
--i;
}
}
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] != i + 1) {
res.push_back(i + 1);
}
}
return res;
}
};
方法三
在nums[nums[i]-1]
位置累加数组长度n
,注意nums[i]-1
有可能越界,所以我们需要对n取余,
最后要找出缺失的数只需要看nums[i]
的值是否小于等于n即可,
最后遍历完nums[i]
数组为[12, 19, 18, 15, 8, 2, 11, 9]
,
我们发现有两个数字8和2小于等于n
,那么就可以通过i+1
来得到正确的结果5和6了
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
vector<int> res;
int n = nums.size();
for (int i = 0; i < n; ++i) {
nums[(nums[i] - 1) % n] += n;
}
for (int i = 0; i < n; ++i) {
if (nums[i] <= n) {
res.push_back(i + 1);
}
}
return res;
}
};
数组中重复的数据
给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。
找到所有出现两次的元素。
你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[2,3]
方法一
这类问题的核心是就是找nums[i]
和nums[nums[i] - 1]
的关系,
我们的做法是,对于每个nums[i]
,我们将其对应的nums[nums[i] - 1]
取相反数,
如果其已经是负数了,说明之前存在过,我们将其加入结果res中即可
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
vector<int> res;
for (int i = 0; i < nums.size(); ++i) {
int idx = abs(nums[i]) - 1;
if (nums[idx] < 0) res.push_back(idx + 1);
nums[idx] = -nums[idx];
}
return res;
}
};
方法二
将nums[i]
置换到其对应的位置nums[nums[i]-1]
上去,
比如对于没有重复项的正确的顺序应该是[1, 2, 3, 4, 5, 6, 7, 8]
,
而我们现在却是[4, 3, 2, 7, 8, 2, 3, 1]
,我们需要把数字移动到正确的位置上去,
比如第一个4就应该和7先交换个位置,以此类推,最后得到的顺序应该是[1, 2, 3, 4, 3, 2, 7, 8]
,
我们最后在对应位置检验,如果nums[i]
和i+1
不等,那么我们将nums[i]
存入结果res
中即可
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
vector<int> res;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] != nums[nums[i] - 1]) {
swap(nums[i], nums[nums[i] - 1]);
--i;
}
}
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] != i + 1) res.push_back(nums[i]);
}
return res;
}
};
方法三
在nums[nums[i]-1]
位置累加数组长度n,注意nums[i]-1
有可能越界,所以我们需要对n取余,
最后要找出现两次的数只需要看nums[i]
的值是否大于2n即可,
最后遍历完nums[i]
数组为[12, 19, 18, 15, 8, 2, 11, 9]
,
我们发现有两个数字19和18大于2n,那么就可以通过i+1
来得到正确的结果2和3了
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
vector<int> res;
int n = nums.size();
for (int i = 0; i < n; ++i) {
nums[(nums[i] - 1) % n] += n;
}
for (int i = 0; i < n; ++i) {
if (nums[i] > 2 * n) res.push_back(i + 1);
}
return res;
}
};
寻找重复数
给定一个包含 n + 1 个整数的数组 nums
,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
说明:
- 不能更改原数组(假设数组是只读的)。
- 只能使用额外的 O(1) 的空间。
- 时间复杂度小于 O(n2) 。
- 数组中只有一个重复的数字,但它可能不止重复出现一次。
二分搜索法
在区间 [1, n] 中搜索,
首先求出中点 mid
,然后遍历整个数组,统计所有小于等于 mid
的数的个数,
如果个数小于等于 mid
,则说明重复值在 [mid+1, n]
之间,
反之,重复值应在 [1, mid-1]
之间,
然后依次类推,直到搜索完成,此时的 low 就是我们要求的重复值
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int left = 1, right = nums.size();
while (left < right){
int mid = left + (right - left) / 2, cnt = 0;
for (int num : nums) {
if (num <= mid) ++cnt;
}
if (cnt <= mid) left = mid + 1;
else right = mid;
}
return right;
}
};
快慢指针
由于题目限定了区间 [1,n],所以可以巧妙的利用坐标和数值之间相互转换,
而由于重复数字的存在,那么一定会形成环,用快慢指针可以找到环并确定环的起始位置,
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = 0, fast = 0, t = 0;
while (true) {
slow = nums[slow];
fast = nums[nums[fast]];
if (slow == fast) break;
}
while (true) {
slow = nums[slow];
t = nums[t];
if (slow == t) break;
}
return slow;
}
};
位操作
遍历每一位,然后对于 32 位中的每一个位 bit,都遍历一遍从0到 n-1,将0到 n-1 中的每一个数都跟 bit 相 ‘与’,若大于0,则计数器 cnt1 自增1。
同时0到 n-1 也可以当作 nums
数组的下标,从而让 nums
数组中的每个数字也跟 bit 相 ‘与’,
若大于0,则计数器 cnt2 自增1。
最后比较若 cnt2 大于 cnt1,则将 bit 加入结果 res 中。
对于每一位,0到 n-1 中所有数字中该位上的1的个数应该是固定的,
如果 nums
数组中所有数字中该位上1的个数多了,说明重复数字在该位上一定是1,这样我们把重复数字的所有为1的位都累加起来,就可以还原出了这个重复数字
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int res = 0, n = nums.size();
for (int i = 0; i < 32; ++i) {
int bit = (1 << i), cnt1 = 0, cnt2 = 0;
for (int k = 0; k < n; ++k) {
if ((k & bit) > 0) ++cnt1;
if ((nums[k] & bit) > 0) ++cnt2;
}
if (cnt2 > cnt1) res += bit;
}
return res;
}
};
缺失的第一个正数
给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。
示例 1:
输入: [1,2,0]
输出: 3
示例 2:
输入: [3,4,-1,1]
输出: 2
示例 3:
输入: [7,8,9,11,12]
输出: 1
提示:
你的算法的时间复杂度应为O(n),并且只能使用常数级别的额外空间。
把1放在数组第一个位置 nums[0]
,2放在第二个位置 nums[1]
,
即需要把 nums[i]
放在 nums[nums[i] - 1]
上,
遍历整个数组,
如果 nums[i] != i + 1
, 而 nums[i]
为整数且不大于n,
另外 nums[i]
不等于 nums[nums[i] - 1]
的话,将两者位置调换,
如果不满足上述条件直接跳过,
最后再遍历一遍数组,如果对应位置上的数不正确则返回正确的数
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; ++i) {
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
swap(nums[i], nums[nums[i] - 1]);
}
}
for (int i = 0; i < n; ++i) {
if (nums[i] != i + 1) return i + 1;
}
return n + 1;
}
};
缺失数字
给定一个包含 0, 1, 2, ..., n
中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。
示例 1:
输入: [3,0,1]
输出: 2
示例 2:
输入: [9,6,4,2,3,5,7,0,1]
输出: 8
说明:
你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现?
公式
用等差数列的求和公式求出0到n之间所有的数字之和,
然后再遍历数组算出给定数字的累积和,
然后做减法,差值就是丢失的那个数字
class Solution {
public:
int missingNumber(vector<int>& nums) {
int sum = 0, n = nums.size();
for (auto &a : nums) {
sum += a;
}
return 0.5 * n * (n + 1) - sum;
}
};
位操作
既然0到n之间少了一个数,我们将这个少了一个数的数组合0到n之间完整的数组异或一下,那么相同的数字都变为0了,剩下的就是少了的那个数字了
class Solution {
public:
int missingNumber(vector<int>& nums) {
int res = 0;
for (int i = 0; i < nums.size(); ++i) {
res ^= (i + 1) ^ nums[i];
}
return res;
}
};