1. 程式人生 > 其它 >img的srcset屬性的作用

img的srcset屬性的作用

最長公共子序列類問題

最長公共子序列

最長公共子序列

給定兩個字串 text1 和 text2,返回這兩個字串的最長公共子序列的長度。如果不存在公共子序列,返回 0 。

一個字串的 子序列 是指這樣一個新的字串:它是由原字串在不改變字元的相對順序的情況下刪除某些字元(也可以不刪除任何字元)後組成的新字串。

例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
兩個字串的 公共子序列 是這兩個字串所共同擁有的子序列。

備忘錄法

// 備忘錄,消除重疊子問題
int[][] memo;

/* 主函式 */
int longestCommonSubsequence(String s1, String s2) {
    int m = s1.length(), n = s2.length();
    // 備忘錄值為 -1 代表未曾計算
    memo = new int[m][n];
    for (int[] row : memo) 
        Arrays.fill(row, -1);
    // 計算 s1[0..] 和 s2[0..] 的 lcs 長度
    return dp(s1, 0, s2, 0);
}

// 定義:計算 s1[i..] 和 s2[j..] 的最長公共子序列長度
int dp(String s1, int i, String s2, int j) {
    // base case
    if (i == s1.length() || j == s2.length()) {
        return 0;
    }
    // 如果之前計算過,則直接返回備忘錄中的答案
    if (memo[i][j] != -1) {
        return memo[i][j];
    }
    // 根據 s1[i] 和 s2[j] 的情況做選擇
    if (s1.charAt(i) == s2.charAt(j)) {
        // s1[i] 和 s2[j] 必然在 lcs 中
        memo[i][j] = 1 + dp(s1, i + 1, s2, j + 1);
    } else {
        // s1[i] 和 s2[j] 至少有一個不在 lcs 中
        memo[i][j] = Math.max(
            dp(s1, i + 1, s2, j),
            dp(s1, i, s2, j + 1)
        );
    }
    return memo[i][j];
}

ps:原本應該是

// 情況一、s1[i] 不在 lcs 中
 dp(s1, i + 1, s2, j),
// 情況二、s2[j] 不在 lcs 中
dp(s1, i, s2, j + 1),
// 情況三、都不在 lcs 中
dp(s1, i + 1, s2, j + 1)

有一個小的優化,情況三「s1[i]s2[j] 都不在 lcs 中」其實可以直接忽略

因為我們在求最大值嘛,情況三在計算 s1[i+1..]s2[j+1..]lcs 長度,這個長度肯定是小於等於情況二 s1[i..]s2[j+1..] 中的 lcs 長度的,因為 s1[i+1..]s1[i..] 短嘛,那從這裡面算出的 lcs

當然也不可能更長嘛。

同理,情況三的結果肯定也小於等於情況一。說白了,情況三被情況一和情況二包含了,所以我們可以直接忽略掉情況三

dp陣列法

int longestCommonSubsequence(String s1, String s2) {
    int m = s1.length(), n = s2.length();
    int[][] dp = new int[m + 1][n + 1];
    // 定義:s1[0..i-1] 和 s2[0..j-1] 的 lcs 長度為 dp[i][j]
    // 目標:s1[0..m-1] 和 s2[0..n-1] 的 lcs 長度,即 dp[m][n]
    // base case: dp[0][..] = dp[..][0] = 0

    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            // 現在 i 和 j 從 1 開始,所以要減一
            if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
                // s1[i-1] 和 s2[j-1] 必然在 lcs 中
                dp[i][j] = 1 + dp[i - 1][j - 1];
            } else {
                // s1[i-1] 和 s2[j-1] 至少有一個不在 lcs 中
                dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
            }
        }
    }

    return dp[m][n];
}

字串的刪除操作

字串的刪除操作

給定兩個單詞 word1word2 ,返回使得 word1word2 相同所需的最小步數

每步 可以刪除任意一個字串中的一個字元。

題目讓我們計算將兩個字串變得相同的最少刪除次數,那我們可以思考一下,最後這兩個字串會被刪成什麼樣子?

刪除的結果不就是它倆的最長公共子序列嘛!

public int minDistance(String word1, String word2) {
        int lcs=longestCommonSubsequence(word1,word2);
        return word1.length()-lcs+word2.length()-lcs;
    }

最小 ASCII 刪除和

兩個字串的最小ASCII刪除和

// 備忘錄
int memo[][];
/* 主函式 */    
int minimumDeleteSum(String s1, String s2) {
    int m = s1.length(), n = s2.length();
    // 備忘錄值為 -1 代表未曾計算
    memo = new int[m][n];
    for (int[] row : memo) 
        Arrays.fill(row, -1);

    return dp(s1, 0, s2, 0);
}

// 定義:將 s1[i..] 和 s2[j..] 刪除成相同字串,
// 最小的 ASCII 碼之和為 dp(s1, i, s2, j)。
int dp(String s1, int i, String s2, int j) {
    int res = 0;
    // base case
    if (i == s1.length()) {
        // 如果 s1 到頭了,那麼 s2 剩下的都得刪除
        for (; j < s2.length(); j++)
            res += s2.charAt(j);
        return res;
    }
    if (j == s2.length()) {
        // 如果 s2 到頭了,那麼 s1 剩下的都得刪除
        for (; i < s1.length(); i++)
            res += s1.charAt(i);
        return res;
    }

    if (memo[i][j] != -1) {
        return memo[i][j];
    }

    if (s1.charAt(i) == s2.charAt(j)) {
        // s1[i] 和 s2[j] 都是在 lcs 中的,不用刪除
        memo[i][j] = dp(s1, i + 1, s2, j + 1);
    } else {
        // s1[i] 和 s2[j] 至少有一個不在 lcs 中,刪一個
        memo[i][j] = Math.min(
            s1.charAt(i) + dp(s1, i + 1, s2, j),
            s2.charAt(j) + dp(s1, i, s2, j + 1)
        );
    }
    return memo[i][j];
}

base case 有一定區別,計算 lcs 長度時,如果一個字串為空,那麼 lcs 長度必然是 0;但是這道題如果一個字串為空,另一個字串必然要被全部刪除,所以需要計算另一個字串所有字元的 ASCII 碼之和。

關於狀態轉移,當 s1[i]s2[j] 相同時不需要刪除,不同時需要刪除,所以可以利用 dp 函式計算兩種情況,得出最優的結果。