Leetcode 2896. 執行操作使兩個字符串相等

https://leetcode.cn/problems/apply-operations-to-make-two-strings-equal/description/

給你兩個下標從 0 開始的二進制字符串 s1 和 s2 ,兩個字符串的長度都是 n ,再給你一個正整數 x 。
你可以對字符串 s1 執行以下操作 任意次 :
選擇兩個下標 i 和 j ,將 s1[i] 和 s1[j] 都反轉,操作的代價爲 x 。
選擇滿足 i < n - 1 的下標 i ,反轉 s1[i] 和 s1[i + 1] ,操作的代價爲 1 。
請你返回使字符串 s1 和 s2 相等的 最小 操作代價之和,如果無法讓二者相等,返回 -1 。
注意 ,反轉字符的意思是將 0 變成 1 ,或者 1 變成 0 。



示例 1:
輸入:s1 = "1100011000", s2 = "0101001010", x = 2
輸出:4
解釋:我們可以執行以下操作:
- 選擇 i = 3 執行第二個操作。結果字符串是 s1 = "1101111000" 。
- 選擇 i = 4 執行第二個操作。結果字符串是 s1 = "1101001000" 。
- 選擇 i = 0 和 j = 8 ,執行第一個操作。結果字符串是 s1 = "0101001010" = s2 。
總代價是 1 + 1 + 2 = 4 。這是最小代價和。

示例 2:
輸入:s1 = "10110", s2 = "00011", x = 4
輸出:-1
解釋:無法使兩個字符串相等。
 

提示:
n == s1.length == s2.length
1 <= n, x <= 500
s1 和 s2 只包含字符 '0' 和 '1' 。

常規dp 記憶化搜索
dp[i][j][k] 表示處理到i爲止的時候前面有j個話費爲x的匹配 前面有k個移動區間匹配時候的最小操作數

class Solution {
public:
    int costx = 0;
    string a, b;
    vector<vector<vector<int>>> dp;
    //int dp[510][510][2];
    int dfs(int idx, int revX, int prevRev) {
        if (idx < 0 && (revX > 0 || 1 == prevRev)) {
            return 0x3f3f3f3f/2;
        }
        else if (idx < 0 && 0 == revX && 0 == prevRev) {
            return 0;
        }

        if (dp[idx][revX][prevRev] != 0x3f3f3f3f) {
            return dp[idx][revX][prevRev];
        }

        int& res = dp[idx][revX][prevRev];
        res = 0x3f3f3f3f;
        if (a[idx] == b[idx] && prevRev == 0) {
            res = dfs(idx - 1, revX, 0);
            return res;
        }
        //兩者不相等 或者相等但是有上一步的翻轉?
        if (a[idx] != b[idx] && prevRev == 1) {
            res = dfs(idx - 1, revX, 0);
            return res;
        }

        //否則三種選擇 第一種操作  第二種操作 之前已經有花費x的翻轉 這次免費
        int ret1 = dfs(idx - 1, revX + 1, 0) + costx;
        int ret2 = dfs(idx - 1, revX, 1) + 1;
        int ret3 = 0x3f3f3f3f;
        if (revX > 0)
            ret3 = dfs(idx - 1, revX - 1, 0);

        res = min(ret1, min(ret2, ret3));
        return res;
    }

    int minOperations(string s1, string s2, int x) {
        int diff = 0;
        for (int i = 0; i < s1.size(); i++) {
            if (s1[i] != s2[i]) diff++;
        }
        if (diff % 2 != 0) return -1;

        int n = s1.size();


        //memset(dp, -1, sizeof dp);
        dp.resize(n + 10, vector<vector<int>>(n + 10, vector<int>(2, 0x3f3f3f3f)));

        costx = x;
        a = s1, b = s2;
        int ret = dfs(n - 1, 0, 0);

        return ret;
    }
};

優化dp
參考此地址 https://www.acwing.com/file_system/file/content/whole/index/content/10200077/
首先要注意到每次操作都是成對解決不用的兩個位置
要麼是花費x 要麼是兩個點ij之間距離 i-j 的花費
所以根據不同位置的奇偶性做不同判斷
//dp[x][1]表示處理到x的位置還留有一個話費爲x的配對
//dp[x][0] 表示全部配對處理完畢 或者留待下一個進行移動區間匹配處理(根據奇偶性判斷)

先找到所有值不同位置的下標,如果總下標數爲奇數,則直接返回 −1
考慮下標對之間的匹配反轉,對於下標 i和 j,可以直接通過 x的代價直接反轉,
也可以通過 j−i的代價移動反轉。注意到,通過移動反轉時,i和 j之間不存在其他下標對(否則可以拆成兩組下標對使得代價變小)。
所以對於一個偶數個數的下標來說,可以和上一個位置的下標通過移動反轉,也可以和前面一個沒有匹配的下標直接反轉。
對於一個奇數個數的下標來說,可以和上一個位置的下標通過移動反轉(保留前面某一個沒有反轉的下標),也可以作爲一個新的沒有反轉的下標)。

複雜度 O(n)

class Solution {
public:
    int minOperations(string s1, string s2, int x) {
        vector<int> p;  p.push_back(-1);

        for (int i = 0; i < s1.size(); i++) {
            if (s1[i] != s2[i]) { p.push_back(i); }
        }
        if (p.size() %2 == 0) return -1;

        int dp[510][2]; memset(dp, 0x3f, sizeof dp);

        dp[0][0] = 0;
        //dp[x][1]表示處理到x的位置還留有一個話費爲x的配對
        //dp[x][0] 表示全部配對處理完畢 或者留待下一個進行移動區間匹配處理(根據奇偶性判斷)
        for (int i = 1; i < p.size(); i++) {
            if (i % 2 == 0) {
                dp[i][0] = min(dp[i - 1][0] + p[i] - p[i - 1], dp[i - 1][1]);
                dp[i][1] = dp[i - 1][1];
            }
            else {
                dp[i][1] = min(dp[i - 1][0] + x, dp[i-1][1]+p[i]-p[i-1]);
                dp[i][0] = dp[i - 1][0];
            }
        }


        cout << dp[p.size() - 1][0] << endl;

        return  dp[p.size() - 1][0];
    }
};

我的視頻題解空間

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章