LeetCode第223场周赛题解

LeetCode第223场周赛

注:题目来源LeetCode

1720. 解码异或后的数组

未知 整数数组 arrn 个非负整数组成。

经编码后变为长度为 n - 1 的另一个整数数组 encoded ,其中 encoded[i] = arr[i] XOR arr[i + 1] 。例如,arr = [1,0,2,1] 经编码后得到 encoded = [1,2,3]

给你编码后的数组 encoded 和原数组 arr 的第一个元素 firstarr[0])。

请解码返回原数组 arr 。可以证明答案存在并且是唯一的。

示例 1:
输入:encoded = [1,2,3], first = 1
输出:[1,0,2,1]
解释:若 arr = [1,0,2,1] ,那么 first = 1 且 encoded = [1 XOR 0, 0 XOR 2, 2 XOR 1] = [1,2,3]

示例 2:
输入:encoded = [6,2,7,3], first = 4
输出:[4,2,0,7,4]

思路:只要利用异或运算的性质,如果有a ^ b = c,则有b = c ^ aa = c ^ b,时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)

class Solution {
public:
    vector<int> decode(vector<int>& encoded, int first) {
        vector<int> res;
        res.push_back(first);
        int pre = first;
        for (int i = 0; i < encoded.size(); i++) {
            res.push_back(encoded[i] ^ first);
            first = res[res.size() - 1];
        }
        return res;
    }
};
1721. 交换链表中的节点

给你链表的头节点 head 和一个整数 k

交换 链表正数第 k 个节点和倒数第 k 个节点的值后,返回链表的头节点(链表 从 1 开始索引)。

在这里插入图片描述

示例 1:

输入:head = [1,2,3,4,5], k = 2
输出:[1,4,3,2,5]
示例 2:

输入:head = [7,9,6,6,7,8,3,0,9,5], k = 5
输出:[7,9,6,6,8,7,3,0,9,5]
示例 3:

输入:head = [1], k = 1
输出:[1]
示例 4:

输入:head = [1,2], k = 1
输出:[2,1]
示例 5:

输入:head = [1,2,3], k = 2
输出:[1,2,3]

思路

方法一:直接遍历一遍链表,并且把对链表的遍历结果存储到哈希表中,然后直接交换整数第k个元素和倒数第k个元素的值。时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)

class Solution {
public:
    ListNode* swapNodes(ListNode* head, int k) {
        vector<ListNode*> vec;
        while (head) {
            vec.push_back(head);
            head = head->next;
        }
        int n = vec.size();
        swap(vec[k - 1]->val, vec[n - k]->val);//交换正数第k个元素和倒数第k个元素
        return vec[0];
    }
};

方法二:利用快慢指针,首先,使用一个指针firstK从链表头移动k-1步指向第k个元素,然后快指针从firstK出发,慢指针从头出发,当快指针到达链表末尾时,慢指针指向倒数第k个元素。然后交换正数第k个元素和倒数第k个元素的值。时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)

class Solution {
public:
    ListNode* swapNodes(ListNode* head, int k) {
        auto firstK = head;
        int cnt = k - 1;
        while (cnt--) {//定位第k个元素
           firstK = firstK->next;
        }

        auto fast = firstK, slow = head;//快指针从firstK出发,慢指针从头出发
        while (fast->next) {//当快指针到达链表末尾时,慢指针指向倒数第k个元素
            fast = fast->next;
            slow = slow->next;
        }

        swap(firstK->val, slow->val);//交换正数第k个元素和倒数第k个元素
        return head;
    }
};

1722. 执行交换操作后的最小汉明距离

给你两个整数数组 sourcetarget ,长度都是 n 。还有一个数组 allowedSwaps ,其中每个 allowedSwaps[i] = [ai, bi] 表示你可以交换数组 source 中下标为 aibi(下标从 0 开始)的两个元素。注意,你可以按 任意 顺序 多次 交换一对特定下标指向的元素。

相同长度的两个数组 sourcetarget 间的 汉明距离 是元素不同的下标数量。形式上,其值等于满足 source[i] != target[i]下标从 0 开始)的下标 i0 <= i <= n-1)的数量。

在对数组 source 执行 任意 数量的交换操作后,返回 sourcetarget 间的 最小汉明距离

示例 1:
输入:source = [1,2,3,4], target = [2,1,4,5], allowedSwaps = [[0,1],[2,3]]
输出:1
解释:source 可以按下述方式转换:
- 交换下标 0 和 1 指向的元素:source = [2,1,3,4]
- 交换下标 2 和 3 指向的元素:source = [2,1,4,3]
  source 和 target 间的汉明距离是 1 ,二者有 1 处元素不同,在下标 3 。

示例 2:
输入:source = [1,2,3,4], target = [1,3,2,4], allowedSwaps = []
输出:2
解释:不能对 source 执行交换操作。
source 和 target 间的汉明距离是 2 ,二者有 2 处元素不同,在下标 1 和下标 2 。

示例 3:
输入:source = [5,1,2,4,3], target = [1,5,4,2,3], allowedSwaps = [[0,4],[4,2],[1,3],[1,4]]
输出:0

思路:这题的关键是要得出这样一个性质,如果allowedSwaps中有[a, b][b, c],那么source中通过任意次数交换后,可以得到元素source[a]source[b]source[c]任意排列,也可以从图论的角度理解这个性质,allowedSwaps中有[a, b][b, c],则在图中有abbc的无向边,下标abc构成一个联通分量,通过交换任意次的性质可以得到该联通分量内任意元素的排列

​ 这个性质不只是适用于只有2对交换关系。更一般地,可以得出,假设有一个下标集合Ssource中下标在S集合中的元素可以交换任意次,那么可以得到source中这n个元素的任意排列,可以使用并查集维护这样可以任意交换元素的集合。因为我们要计算最小的汉明距离,则我们就将source中这n个元素的顺序,转化为与target中这n个下标对应的元素的顺序相同。设source中下标集合S的所有元素构成集合S1target中下标集合S的所有元素构成集合S2,如果S1中有一个元素在S2中没有对应元素,那么汉明距离增加1。

​ 时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度 O ( n ) O(n) O(n)

const int N = 1e5 + 10;
class Solution {
public:
    int p[N];
    int find(int x) {
        if (x == p[x]) return x;
        return p[x] = find(p[x]);
    }
    int minimumHammingDistance(vector<int>& source, vector<int>& target, vector<vector<int>>& swaps) {
        int n = source.size();

        for (int i = 1; i <= n; i++) 
            p[i] = i;
        
        for (auto &s: swaps) {//加入并查集
            int a = s[0], b = s[1];
            if (find(a) != find(b))
                p[find(a)] = find(b);
        }

        unordered_map<int, vector<int>> g;
        int res = 0;
        for (int i = 0; i < n; i++) {//从并查集中提取出相同集合内的下标
            g[find(i)].push_back(i);
        }

        for (auto &[k, v]: g) {
            vector<int> a, b, c;
            for (int i = 0; i < v.size(); i++) {
                a.push_back(source[v[i]]);
                b.push_back(target[v[i]]);
            }
            //计算source和target在对应下标集合不同元素的个数
            sort(a.begin(), a.end());
            sort(b.begin(), b.end());
            auto i = a.begin(), j = b.begin();
            
            //双指针找交集
            int cnt = 0;
            while (i < a.end() && j < b.end()) {
                if (*i == *j) {
                    i++;
                    j++;
                    cnt++;
                } else if (*i > *j) j++;
                else if (*i < *j) i++;
            }
            // std::set_intersection(a.begin(), a.end(), b.begin(), b.end(), std::back_inserter(c));
            res += v.size() - cnt;
        }
        return res;
    }
};

1723. 完成所有工作的最短时间

给你一个整数数组 jobs ,其中 jobs[i] 是完成第 i 项工作要花费的时间。

请你将这些工作分配给 k 位工人。所有工作都应该分配给工人,且每项工作只能分配给一位工人。工人的 工作时间 是完成分配给他们的所有工作花费时间的总和。请你设计一套最佳的工作分配方案,使工人的 最大工作时间 得以 最小化 。

返回分配方案中尽可能 最小最大工作时间

示例 1:
输入:jobs = [3,2,3], k = 3
输出:3
解释:给每位工人分配一项工作,最大工作时间是 3 。

示例 2:
输入:jobs = [1,2,4,7,8], k = 2
输出:11
解释:按下述方式分配工作:
1 号工人:1、2、8(工作时间 = 1 + 2 + 8 = 11)
2 号工人:4、7(工作时间 = 4 + 7 = 11)
最大工作时间是 11 。

提示

1 <= k <= jobs.length <= 12
1 <= jobs[i] <= 1 0 7 10^7 107

思路:根据数据范围可以有效的算法时间复杂度可能是指数级别,故有效算法可能是搜索或者状态压缩DP。

方法一:状态压缩DP

下面考虑如果使用状态压缩DP来该问题。因为最终求解的是工人的工作时间,即完成分配给他们的所有工作花费时间的总和,首先预处理出该值。假设一共有n个作业( n ≤ 12 n \leq 12 n12),那么一个工人被分配到的所有可能的作业最多有 2 n 2^n 2n种情况(n的所有子集),可以使用一个整数 i 上的低 n 位表示被分配到的作业情况,如果整数i的第 j 位为1,表示被分配到第jobs[j]个工作,有了这样的状态表示,可以枚举 i ,即 jobs 的所有可能分配方案,对于每个 i ,枚举 i 的每一位,如果 i 的第 j 位为1,去掉第 j 位的任务集合为left,可以使用递推式sum[i] = sum[left] + jobs[j],计算出当前完成所有当前工作集合所需的工作时间

下面考虑如何定义动态规划的状态以及转移。动态规划的状态可以根据题意定义,题目怎样设问就怎样定义,故设f[i][j]表示前 i ( 0 ≤ i < k 0\leq i<k 0i<k)个人完成工作 j ( 0 ≤ j < 2 n 0 \leq j < 2^n 0j<2n)的 最小最大工作时间 ,这里工作 j 是一个二进制的状态表示,j中为1的为表示当前的工作集合,故最终答案即为f[k - 1][(1 << n)- 1]

状态的转移需要枚举j的所有子集s,考虑计算f[i][j],可以将前i - 1个人完成任务j - s,第i个人完成任务s,记录每个人的最大工作时间,然后f[i][j]等于所有方案中去最小的最大工作时间。

时间复杂度 O ( k ∗ 3 n ) O(k*3^n) O(k3n),空间复杂度 O ( k ∗ 2 n ) O(k*2^n) O(k2n)njobs的长度,k为工人人数。

class Solution {
public:
    int minimumTimeRequired(vector<int>& jobs, int k) {
        int n = jobs.size();

        vector<int> sum(1 << n, 0);//sum[i]表示完成工作集合i所需的工作时间
        for (int i = 1; i < (1 << n); i++) {//预处理
            for (int j = 0; j < n; j++) {
                if (i & (1 << j)) {
                    int left = i - (1 << j);
                    sum[i] = sum[left] + jobs[j];
                    break;//sum[i]不需要重复计算
                }
            }
        }

        vector<vector<int>> f(k, vector<int>(1 << n, -1));//f[i][j]前i个人完成工作集合j的最小的最大工作时间
        for (int i = 0; i < (1 << n); i++) f[0][i] = sum[i];//一个人完成所有任务所需的时间

        for (int i = 1; i < k; i++) {
            for (int j = 0; j < (1 << n); j++) {
                int minTime = INT_MAX;
                for (int s = j; s; s = (s - 1) & j) {
                    int left = j - s;
                    int curTime = max(f[i - 1][left], sum[s]);//前i - 1个人完成任务left,第i个人完成任务s
                    minTime = min(minTime, curTime);
                }
                f[i][j] = minTime;
            }
        }
        return f[k - 1][(1 << n) - 1];
    }
};

方法二:DFS,注意剪枝。

class Solution {
public:
    int res = INT_MAX;
    int minimumTimeRequired(vector<int>& jobs, int k) {
        vector<int> sum(k, 0);
        dfs(jobs, k, sum, 0);
        return res;
    }

    void dfs(vector<int> &jobs, int k, vector<int> &sum, int x) {//将jobs的前x个工作分配给k个人,sum[i]表示第i个人的工作时间
        if (x == jobs.size()) {
            res = min(res, *max_element(sum.begin(), sum.end()));
            return;
        }
        for (int i = 0; i < k; i++) {
            if (sum[i] + jobs[x] >= res) continue;//剪枝
            sum[i] += jobs[x];
            dfs(jobs, k, sum, x + 1);
            sum[i] -= jobs[x];
            if (sum[i] == 0) break;//剪枝
        }
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值