菜雞每日一題系列打卡87天
每天一道算法題目
小夥伴們一起留言打卡
堅持就是勝利,我們一起努力!
題目描述(引自LeetCode)
給定一個字符串s1,我們可以把它遞歸地分割成兩個非空子字符串,從而將其表示爲二叉樹。
下圖是字符串s1 = "great"的一種可能的表示形式。
great
/ \
gr eat
/ \ / \
g r e at
/ \
a t
在擾亂這個字符串的過程中,我們可以挑選任何一個非葉節點,然後交換它的兩個子節點。
例如,如果我們挑選非葉節點"gr",交換它的兩個子節點,將會產生擾亂字符串"rgeat"。
rgeat
/ \
rg eat
/ \ / \
r g e at
/ \
a t
我們將"rgeat"稱作"great"的一個擾亂字符串。
同樣地,如果我們繼續交換節點"eat"和"at"的子節點,將會產生另一個新的擾亂字符串"rgtae"。
rgtae
/ \
rg tae
/ \ / \
r g ta e
/ \
t a
我們將"rgtae"稱作"great"的一個擾亂字符串。
給出兩個長度相等的字符串s1和s2,判斷s2是否是s1的擾亂字符串。
示例 1:
輸入: s1 = "great", s2 = "rgeat"
輸出: true
示例 2:
輸入: s1 = "abcde", s2 = "caebd"
輸出: false
題目分析
這道題比較容易想到的方式是遞歸,基於leetcode測試集提供的s1和s2均由字母組成,且長度比較小時,遞歸是可行的,而且相對比較容易實現;但當組成s1和s2的字符集變大時,或者長度達到一定規模時,就需要考慮採用動態規劃。
在這裏,菜雞給出了兩種解法,供大家參考。話不多說,上代碼!
代碼實現
// 遞歸
class Solution {
public boolean isScramble(String s1, String s2) {
// 特殊情況處理
if (s1 == null || s2 == null) return s1 == s2;
if (s1.length() != s2.length()) return false;
// dfs
return dfs(s1, s2, 0, 0, s1.length());
}
private boolean dfs(String s1, String s2, int i, int j, int l){
// 遞歸終止條件
if (l == 1) return s1.charAt(i) == s2.charAt(j);
// 剪枝
int[] tmp = new int[26];
for (int k = 0; k < l; k++) {
tmp[s1.charAt(i + k) - 'a']++;
tmp[s2.charAt(j + k) - 'a']--;
}
for (int c : tmp) if (c != 0) return false;
// 遞歸
for (int k = 1; k < l; k++) {
if (dfs(s1, s2, i, j, k) && dfs(s1, s2, i + k, j + k, l - k)) return true;
if (dfs(s1, s2, i, j + l - k, k) && dfs(s1, s2, i + k, j, l - k)) return true;
}
// 返回
return false;
}
}
// 動態規劃
class Solution {
public boolean isScramble(String s1, String s2) {
// 特殊情況處理
if (s1 == null || s2 == null) return s1 == s2;
int n = s1.length();
if (n != s2.length()) return false;
// 初始化dp數組
boolean[][][] dp = new boolean[n][n][n + 1];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dp[i][j][1] = s1.charAt(i) == s2.charAt(j);
}
}
// 計算dp數組
for (int l = 2; l <= n; l++) {
for (int i = 0; i <= n - l; i++) {
for (int j = 0; j <= n - l; j++) {
for (int k = 1; k <= l - 1; k++) {
if (dp[i][j][k] && dp[i + k][j + k][l - k]) {
dp[i][j][l] = true;
break;
}
if (dp[i][j + l - k][k] && dp[i + k][j][l - k]) {
dp[i][j][l] = true;
break;
}
}
}
}
}
// 返回結果
return dp[0][0][n];
}
}
代碼分析
對代碼進行分析,遞歸方式的時間複雜度爲階乘級別;遞歸調用佔用了大量棧空間,空間複雜度爲階乘級別。
動態規劃方式,計算dp數組需要四層for循環,時間複雜度爲O(n^4);同時,空間上需要三維數組進行狀態存儲,空間複雜度爲O(n^3)。
執行結果
遞歸方式的執行結果
動態規劃方式的執行結果
注意:上述執行結果的差異受leetcode測試集的限制,並不能客觀表現遞歸方式和動態規劃方式之間的差異。
學習 | 工作 | 分享
????長按關注“有理想的菜雞”
只有你想不到,沒有你學不到