5.8

328. 奇偶链表 - 力扣(LeetCode)

image-20240731111427114.png

  1. 如果是空链表,evenList的初始节点就不存在,因此特殊处理,直接返回;
  2. 初始化奇数索引节点链表oddList初始化为头节点,偶数索引节点链表evenList初始化为次节点;
  3. 记录偶数索引节点链表头节点evenList,用于之后的拼接;【奇数索引节点的头节点就是head,不需要在额外记录了】
  4. 更新奇偶索引链表的节点;
  5. 最后一个奇数索引节点和首个偶数索引节点拼接起来;
    返回头节点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)

image-20250508153537211

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] 不在答案列表中,则加入答案列表。

优化

  1. 把 nums1 中的元素都加到哈希集合 st 中,这样可以 O(1) 判断 nums2[i] 在不在 nums1 中。

  2. 为了保证答案列表中没有重复元素,在 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 这两个数。

为了保证非零元素的顺序不变,我们需要维护最左边的空位的位置(下标)。

具体思路

  1. 从左到右遍历 nums[i]。

  2. 同时维护另一个下标 i0(初始值为 0),并保证下标区间 [i0,i−1] 都是空位,且 i0 指向最左边的空位。

  3. 每次遇到 nums[i]!=0 的情况,就把 nums[i] 移动到最左边的空位上,也就是交换 nums[i] 和 nums[i0]。交换后把 i0 和 i 都加一,从而使「i0,i−1」都是空位这一性质仍然成立。

  4. 如果 nums[i]=0,无需交换,只把 i 加一。

  5. 示例 1 的 nums=[0,1,0,3,12],计算过程如下(下划线表示交换的两个数):

  6. 由于每次操作后,[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 = 0x = 0(不交换)。
  • i0 = 0x = 1(交换副本 xnums[0] → 副本 x 变为 0,原数组 nums[0] 变为 1,但原数组的 nums[1] 仍为 1)。
  • i0 = 1x = 0(不交换)。
  • i0 = 1x = 3(交换副本 xnums[1] → 副本 x 变为 1,原数组 nums[1] 变为 3)。
    最终结果错误:[1, 3, 0, 3](原数组的最后一个元素未被正确置零)。

复杂度分析

  • 时间复杂度:O(n),其中 n 是 nums 的长度。
  • 空间复杂度:O(1)。
posted @ 2025-05-09 02:08  七龙猪  阅读(1)  评论(0)    收藏  举报
-->