1. 程式人生 > 其它 >LeetCode第223場周賽題解

LeetCode第223場周賽題解

技術標籤:Leetcode

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)

class Solution {
public:
    vector<int> p;
    int find(int x) {
        if (p[x] == x) return x;
        return p[x] = find(p[x]);
    }
    
    int minimumHammingDistance(vector<int>& source, vector<int>& target, vector<vector<int>>& allowedSwaps) {
        int n = target.size();
        p.resize(n, 0);
        for (int i = 0; i < n; i++) p[i] = i;

        for (auto &point: allowedSwaps) {
            int ra = find(point[0]), rb = find(point[1]);//可以交換source中下標為point[0]和point[1]中的元素
            p[ra] = rb;//合併
        }

        vector<unordered_map<int, int>> group;
        vector<unordered_map<int, int>> groupTraget;
        unordered_map<int, int> groupIdx;//root --> groupIdx
        for (int i = 0; i < p.size(); i++) {//根據並查集中每組的資訊,按照每組將source和target中的元素抽取出來
            int root = find(i);
            if (groupIdx.count(root)) {
                int idx = groupIdx[root];
                group[idx][source[i]]++;
                groupTraget[idx][target[i]]++;
            } else {
                groupIdx[root] = group.size();
                group.push_back({pair<int, int>(source[i], 1)});
                groupTraget.push_back({pair<int, int>(target[i], 1)});
            }
        }

        int res = 0;
        for (int i = 0; i < group.size(); i++) {//計算兩個集合中元素的差異,即計算漢明距離
            for (auto &[k, v]: group[i]) {
                if (groupTraget[i].count(k)) {
                    if (v > groupTraget[i][k]) res += (v - groupTraget[i][k]);
                    groupTraget[i].erase(k);
                }
                else {
                    res += v;
                }
            }
        }

        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;//剪枝
        }
    }
};