5.8
328. 奇偶链表 - 力扣(LeetCode)
- 如果是空链表,evenList的初始节点就不存在,因此特殊处理,直接返回;
- 初始化奇数索引节点链表oddList初始化为头节点,偶数索引节点链表evenList初始化为次节点;
- 记录偶数索引节点链表头节点evenList,用于之后的拼接;【奇数索引节点的头节点就是head,不需要在额外记录了】
- 更新奇偶索引链表的节点;
- 最后一个奇数索引节点和首个偶数索引节点拼接起来;
返回头节点head;
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if(!head)return head; // 空链表,直接返回
ListNode* odd = head, *even = head->next; // 奇数索引节点链表odd初始化为头节点,偶数索引节点链表even初始化为次节点
ListNode* evenhead = even; // 记录偶数索引节点链表头节点
// 当前偶数节点不为空且当前偶数节点之后还有节点
while(even && even->next){
odd->next = even->next; // 下一个奇数索引节点是当前偶数索引节点的下一个节点
odd = odd->next; // 更新当前奇数索引节点
even->next = odd->next; // 下一个偶数索引节点是新的奇数索引节点的下一个节点
even = even->next; // 更新偶数索引节点
}
odd->next = evenhead; // 最后一个奇数索引节点和首个偶数索引节点拼接起来
return head;
}
};
25. K 个一组翻转链表 - 力扣(LeetCode)
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
// 统计节点个数
int n = 0;
for (ListNode* cur = head; cur; cur = cur->next) {
n++;
}
ListNode dummy(0, head);
ListNode* p0 = &dummy;
ListNode* pre = nullptr;
ListNode* cur = head;
// k 个一组处理
for (; n >= k; n -= k) {
for (int i = 0; i < k; i++) { // 同 92 题
ListNode* nxt = cur->next;
cur->next = pre; // 每次循环只修改一个 next,方便大家理解
pre = cur;
cur = nxt;
}
ListNode* nxt = p0->next;//翻转后的末尾
p0->next->next = cur;
p0->next = pre;//p0接到翻转后的头
p0 = nxt;//翻转后的末尾是下一个循环的前驱结点
}
return dummy.next;
}
};
349. 两个数组的交集 - 力扣(LeetCode)
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> set;
unordered_set<int> uset(nums1.begin() , nums1.end());
for(int i : nums2){
if(uset.count(i)) set.insert(i);
}
vector<int> res(set.begin() , set.end());
return res;
}
};
核心思路
遍历 nums2 的每个数,如果 nums2[i] 在 nums1 中,并且 nums2[i] 不在答案列表中,则加入答案列表。
优化
把 nums1 中的元素都加到哈希集合 st 中,这样可以 O(1) 判断 nums2[i] 在不在 nums1 中。
为了保证答案列表中没有重复元素,在 nums2[i] 加入答案列表的同时,把 nums2[i] 从哈希集合 st 中去掉,这样后面遍历到相同的元素时,由于其不在 st 中,所以不会加入答案列表。
相当于
if(uset.count(i)){ uset.erase(i); res.push_back(i); }这样可以做到一次遍历:nums1 和 nums2 都只遍历了一次,并且也没有额外遍历答案列表。
class Solution { public: vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { unordered_set<int> st(nums1.begin(), nums1.end()); vector<int> ans; for (int x : nums2) { if (st.erase(x)) { // x 在 st 中 ans.push_back(x); } } return ans; } };复杂度分析
- 时间复杂度:O(n+m),其中 n 是 nums1 的长度,m 是 nums2 的长度。
- 空间复杂度:O(n)。注:可以把长度小的数组转成哈希集合,这样可以做到 O(min(n,m)) 的空间复杂度。
283. 移动零 - 力扣(LeetCode)
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int slow = 0 ;
for (int i = 0; i < nums.size(); i++) {
if(nums[i] == 0) continue;
nums[slow ++] = nums[i];
}
while(slow < nums.size()) nums[slow ++] = 0;
}
};
方法:双指针
核心思路
把 0 视作空位。我们要把所有非零元素都移到数组左边的空位上,并保证非零元素的顺序不变。
例如 nums=[0,0,1,2],把 1 放到最左边的空位上,数组变成 [1,0,0,2]。注意 1 移动过去后,在原来 1 的位置又产生了一个新的空位。也就是说,我们交换了 nums[0]=0 和 nums[2]=1 这两个数。
为了保证非零元素的顺序不变,我们需要维护最左边的空位的位置(下标)。
具体思路
从左到右遍历 nums[i]。
同时维护另一个下标 i0(初始值为 0),并保证下标区间 [i0,i−1] 都是空位,且 i0 指向最左边的空位。
每次遇到 nums[i]!=0 的情况,就把 nums[i] 移动到最左边的空位上,也就是交换 nums[i] 和 nums[i0]。交换后把 i0 和 i 都加一,从而使「i0,i−1」都是空位这一性质仍然成立。
如果 nums[i]=0,无需交换,只把 i 加一。
示例 1 的 nums=[0,1,0,3,12],计算过程如下(下划线表示交换的两个数):
由于每次操作后,[i0,i−1] 对应的元素值全为 0 这一性质始终成立,所以 nums 遍历结束后(i=n),[i0,n−1] 对应的元素值全为 0,且 [0,i0−1] 都是交换过去的非零元素,这样就满足了题目「将所有 0 移动到数组的末尾」的要求。
答疑
问:如果 nums 的前几个数都不是 0 呢?
答:i0 会和 i 同时向右移动,直到遇到 0(或者到达数组末尾)为止。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int i0 = 0;
for (int& x : nums) { // 注意 x 是引用
if (x) {
swap(x, nums[i0]);
i0++;
}
}
}
};
若改为
for (int x : nums)此时
x是数组元素的 临时副本:
- 交换的是副本:
swap(x, nums[i0])会将副本x的值与nums[i0]交换。- 原数组未更新:对副本
x的修改不会影响原数组中的元素。- 结果错误:原数组中的零元素未被正确移动,非零元素的顺序可能混乱。
错误情况(使用值传递):
for (int x : nums) { // x 是副本 if (x) { swap(x, nums[i0]); // 仅修改副本 x 和 nums[i0] i0++; } }执行过程:
i0 = 0,x = 0(不交换)。i0 = 0,x = 1(交换副本x和nums[0]→ 副本x变为0,原数组nums[0]变为1,但原数组的nums[1]仍为1)。i0 = 1,x = 0(不交换)。i0 = 1,x = 3(交换副本x和nums[1]→ 副本x变为1,原数组nums[1]变为3)。
最终结果错误:[1, 3, 0, 3](原数组的最后一个元素未被正确置零)。
复杂度分析
- 时间复杂度:O(n),其中 n 是 nums 的长度。
- 空间复杂度:O(1)。



浙公网安备 33010602011771号