斐波那契數列html
遞歸和動態規劃都是將原問題拆成多個子問題而後求解,他們之間最本質的區別是,動態規劃保存了子問題的解,避免重複計算。java
題目描述:有 N 階樓梯,每次能夠上一階或者兩階,求有多少種上樓梯的方法。面試
定義一個數組 dp 存儲上樓梯的方法數(爲了方便討論,數組下標從 1 開始),dp[i] 表示走到第 i 個樓梯的方法數目。算法
第 i 個樓梯能夠從第 i-1 和 i-2 個樓梯再走一步到達,走到第 i 個樓梯的方法數爲走到第 i-1 和第 i-2 個樓梯的方法數之和。數組
考慮到 dp[i] 只與 dp[i - 1] 和 dp[i - 2] 有關,所以能夠只用兩個變量來存儲 dp[i - 1] 和 dp[i - 2],使得原來的 O(N) 空間複雜度優化爲 O(1) 複雜度。優化
public int climbStairs(int n) { if (n <= 2) { return n; } int pre2 = 1, pre1 = 2; for (int i = 2; i < n; i++) { int cur = pre1 + pre2; pre2 = pre1; pre1 = cur; } return pre1; }
題目描述:搶劫一排住戶,可是不能搶鄰近的住戶,求最大搶劫量。spa
定義 dp 數組用來存儲最大的搶劫量,其中 dp[i] 表示搶到第 i 個住戶時的最大搶劫量。3d
因爲不能搶劫鄰近住戶,若是搶劫了第 i -1 個住戶,那麼就不能再搶劫第 i 個住戶,因此
public int rob(int[] nums) { int pre2 = 0, pre1 = 0; for (int i = 0; i < nums.length; i++) { int cur = Math.max(pre2 + nums[i], pre1); pre2 = pre1; pre1 = cur; } return pre1; }
public int rob(int[] nums) { if (nums == null || nums.length == 0) { return 0; } int n = nums.length; if (n == 1) { return nums[0]; } return Math.max(rob(nums, 0, n - 2), rob(nums, 1, n - 1)); } private int rob(int[] nums, int first, int last) { int pre2 = 0, pre1 = 0; for (int i = first; i <= last; i++) { int cur = Math.max(pre1, pre2 + nums[i]); pre2 = pre1; pre1 = cur; } return pre1; }
題目描述:有 N 個 信 和 信封,它們被打亂,求錯誤裝信方式的數量。
定義一個數組 dp 存儲錯誤方式數量,dp[i] 表示前 i 個信和信封的錯誤方式數量。假設第 i 個信裝到第 j 個信封裏面,而第 j 個信裝到第 k 個信封裏面。根據 i 和 k 是否相等,有兩種狀況:
綜上所述,錯誤裝信數量方式數量爲:
題目描述:假設農場中成熟的母牛每一年都會生 1 頭小母牛,而且永遠不會死。第一年有 1 只小母牛,從第二年開始,母牛開始生小母牛。每隻小母牛 3 年以後成熟又能夠生小母牛。給定整數 N,求 N 年後牛的數量。
第 i 年成熟的牛的數量爲:
[[1,3,1],
[1,5,1],
[4,2,1]]
Given the above grid map, return 7. Because the path 1→3→1→1→1 minimizes the sum.
題目描述:求從矩陣的左上角到右下角的最小路徑和,每次只能向右和向下移動。
public int minPathSum(int[][] grid) { if (grid.length == 0 || grid[0].length == 0) { return 0; } int m = grid.length, n = grid[0].length; int[] dp = new int[n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (j == 0) { dp[j] = dp[j]; // 只能從上側走到該位置 } else if (i == 0) { dp[j] = dp[j - 1]; // 只能從左側走到該位置 } else { dp[j] = Math.min(dp[j - 1], dp[j]); } dp[j] += grid[i][j]; } } return dp[n - 1]; }
題目描述:統計從矩陣左上角到右下角的路徑總數,每次只能向右或者向下移動。
public int uniquePaths(int m, int n) { int[] dp = new int[n]; Arrays.fill(dp, 1); for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { dp[j] = dp[j] + dp[j - 1]; } } return dp[n - 1]; }
也能夠直接用數學公式求解,這是一個組合問題。機器人總共移動的次數 S=m+n-2,向下移動的次數 D=m-1,那麼問題能夠當作從 S 中取出 D 個位置的組合數量,這個問題的解爲 C(S, D)。
public int uniquePaths(int m, int n) { int S = m + n - 2; // 總共的移動次數 int D = m - 1; // 向下的移動次數 long ret = 1; for (int i = 1; i <= D; i++) { ret = ret * (S - D + i) / i; } return (int) ret; }
303. Range Sum Query - Immutable (Easy)
Given nums = [-2, 0, 3, -5, 2, -1]
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
求區間 i ~ j 的和,能夠轉換爲 sum[j + 1] - sum[i],其中 sum[i] 爲 0 ~ i - 1 的和。
class NumArray { private int[] sums; public NumArray(int[] nums) { sums = new int[nums.length + 1]; for (int i = 1; i <= nums.length; i++) { sums[i] = sums[i - 1] + nums[i - 1]; } } public int sumRange(int i, int j) { return sums[j + 1] - sums[i]; } }
413. Arithmetic Slices (Medium)
A = [0, 1, 2, 3, 4]
return: 6, for 3 arithmetic slices in A:
[0, 1, 2],
[1, 2, 3],
[0, 1, 2, 3],
[0, 1, 2, 3, 4],
[ 1, 2, 3, 4],
[2, 3, 4]
dp[i] 表示以 A[i] 爲結尾的等差遞增子區間的個數。
當 A[i] - A[i-1] == A[i-1] - A[i-2],那麼 [A[i-2], A[i-1], A[i]] 構成一個等差遞增子區間。並且在以 A[i-1] 爲結尾的遞增子區間的後面再加上一個 A[i],同樣能夠構成新的遞增子區間。
dp[2] = 1
[0, 1, 2]
dp[3] = dp[2] + 1 = 2
[0, 1, 2, 3], // [0, 1, 2] 以後加一個 3
[1, 2, 3] // 新的遞增子區間
dp[4] = dp[3] + 1 = 3
[0, 1, 2, 3, 4], // [0, 1, 2, 3] 以後加一個 4
[1, 2, 3, 4], // [1, 2, 3] 以後加一個 4
[2, 3, 4] // 新的遞增子區間
綜上,在 A[i] - A[i-1] == A[i-1] - A[i-2] 時,dp[i] = dp[i-1] + 1。
由於遞增子區間不必定以最後一個元素爲結尾,能夠是任意一個元素結尾,所以須要返回 dp 數組累加的結果。
public int numberOfArithmeticSlices(int[] A) { if (A == null || A.length == 0) { return 0; } int n = A.length; int[] dp = new int[n]; for (int i = 2; i < n; i++) { if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) { dp[i] = dp[i - 1] + 1; } } int total = 0; for (int cnt : dp) { total += cnt; } return total; }
題目描述:For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).
public int integerBreak(int n) { int[] dp = new int[n + 1]; dp[1] = 1; for (int i = 2; i <= n; i++) { for (int j = 1; j <= i - 1; j++) { dp[i] = Math.max(dp[i], Math.max(j * dp[i - j], j * (i - j))); } } return dp[n]; }
題目描述:For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.
public int numSquares(int n) { List<Integer> squareList = generateSquareList(n); int[] dp = new int[n + 1]; for (int i = 1; i <= n; i++) { int min = Integer.MAX_VALUE; for (int square : squareList) { if (square > i) { break; } min = Math.min(min, dp[i - square] + 1); } dp[i] = min; } return dp[n]; } private List<Integer> generateSquareList(int n) { List<Integer> squareList = new ArrayList<>(); int diff = 3; int square = 1; while (square <= n) { squareList.add(square); square += diff; diff += 2; } return squareList; }
題目描述:Given encoded message "12", it could be decoded as "AB" (1 2) or "L" (12).
public int numDecodings(String s) { if (s == null || s.length() == 0) { return 0; } int n = s.length(); int[] dp = new int[n + 1]; dp[0] = 1; dp[1] = s.charAt(0) == '0' ? 0 : 1; for (int i = 2; i <= n; i++) { int one = Integer.valueOf(s.substring(i - 1, i)); if (one != 0) { dp[i] += dp[i - 1]; } if (s.charAt(i - 2) == '0') { continue; } int two = Integer.valueOf(s.substring(i - 2, i)); if (two <= 26) { dp[i] += dp[i - 2]; } } return dp[n]; }
已知一個序列 {S1, S2,...,Sn},取出若干數組成新的序列 {Si1, Si2,..., Sim},其中 i一、i2 ... im 保持遞增,即新序列中各個數仍然保持原數列中的前後順序,稱新序列爲原序列的一個 子序列 。
若是在子序列中,當下標 ix > iy 時,Six > Siy,稱子序列爲原序列的一個 遞增子序列 。
定義一個數組 dp 存儲最長遞增子序列的長度,dp[n] 表示以 Sn 結尾的序列的最長遞增子序列長度。對於一個遞增子序列 {Si1, Si2,...,Sim},若是 im < n 而且 Sim < Sn,此時 {Si1, Si2,..., Sim, Sn} 爲一個遞增子序列,遞增子序列的長度增長 1。知足上述條件的遞增子序列中,長度最長的那個遞增子序列就是要找的,在長度最長的遞增子序列上加上 Sn 就構成了以 Sn 爲結尾的最長遞增子序列。所以 dp[n] = max{ dp[i]+1 | Si < Sn && i < n} 。
由於在求 dp[n] 時可能沒法找到一個知足條件的遞增子序列,此時 {Sn} 就構成了遞增子序列,須要對前面的求解方程作修改,令 dp[n] 最小爲 1,即:
對於一個長度爲 N 的序列,最長遞增子序列並不必定會以 SN 爲結尾,所以 dp[N] 不是序列的最長遞增子序列的長度,須要遍歷 dp 數組找出最大值纔是所要的結果,max{ dp[i] | 1 <= i <= N} 即爲所求。
300. Longest Increasing Subsequence (Medium)
public int lengthOfLIS(int[] nums) { int n = nums.length; int[] dp = new int[n]; for (int i = 0; i < n; i++) { int max = 1; for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) { max = Math.max(max, dp[j] + 1); } } dp[i] = max; } return Arrays.stream(dp).max().orElse(0); }
使用 Stream 求最大值會致使運行時間過長,能夠改爲如下形式:
int ret = 0; for (int i = 0; i < n; i++) { ret = Math.max(ret, dp[i]); } return ret;
以上解法的時間複雜度爲 O(N2),可使用二分查找將時間複雜度下降爲 O(NlogN)。
定義一個 tails 數組,其中 tails[i] 存儲長度爲 i + 1 的最長遞增子序列的最後一個元素。對於一個元素 x,
例如對於數組 [4,3,6,5],有:
tails len num
[] 0 4
[4] 1 3
[3] 1 6
[3,6] 2 5
[3,5] 2 null
能夠看出 tails 數組保持有序,所以在查找 Si 位於 tails 數組的位置時就可使用二分查找。
public int lengthOfLIS(int[] nums) { int n = nums.length; int[] tails = new int[n]; int len = 0; for (int num : nums) { int index = binarySearch(tails, len, num); tails[index] = num; if (index == len) { len++; } } return len; } private int binarySearch(int[] tails, int len, int key) { int l = 0, h = len; while (l < h) { int mid = l + (h - l) / 2; if (tails[mid] == key) { return mid; } else if (tails[mid] > key) { h = mid; } else { l = mid + 1; } } return l; }
646. Maximum Length of Pair Chain (Medium)
Input: [[1,2], [2,3], [3,4]]
Output: 2
Explanation: The longest chain is [1,2] -> [3,4]
題目描述:對於 (a, b) 和 (c, d) ,若是 b < c,則它們能夠構成一條鏈。
public int findLongestChain(int[][] pairs) { if (pairs == null || pairs.length == 0) { return 0; } Arrays.sort(pairs, (a, b) -> (a[0] - b[0])); int n = pairs.length; int[] dp = new int[n]; Arrays.fill(dp, 1); for (int i = 1; i < n; i++) { for (int j = 0; j < i; j++) { if (pairs[j][1] < pairs[i][0]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } } return Arrays.stream(dp).max().orElse(0); }
376. Wiggle Subsequence (Medium)
Input: [1,7,4,9,2,5]
Output: 6
The entire sequence is a wiggle sequence.
Input: [1,17,5,10,13,15,10,5,16,8]
Output: 7
There are several subsequences that achieve this length. One is [1,17,10,13,10,16,8].
Input: [1,2,3,4,5,6,7,8,9]
Output: 2
要求:使用 O(N) 時間複雜度求解。
public int wiggleMaxLength(int[] nums) { if (nums == null || nums.length == 0) { return 0; } int up = 1, down = 1; for (int i = 1; i < nums.length; i++) { if (nums[i] > nums[i - 1]) { up = down + 1; } else if (nums[i] < nums[i - 1]) { down = up + 1; } } return Math.max(up, down); }
對於兩個子序列 S1 和 S2,找出它們最長的公共子序列。
定義一個二維數組 dp 用來存儲最長公共子序列的長度,其中 dp[i][j] 表示 S1 的前 i 個字符與 S2 的前 j 個字符最長公共子序列的長度。考慮 S1i 與 S2j 值是否相等,分爲兩種狀況:
綜上,最長公共子序列的狀態轉移方程爲:
對於長度爲 N 的序列 S1 和長度爲 M 的序列 S2,dp[N][M] 就是序列 S1 和序列 S2 的最長公共子序列長度。
與最長遞增子序列相比,最長公共子序列有如下不一樣點:
public int lengthOfLCS(int[] nums1, int[] nums2) { int n1 = nums1.length, n2 = nums2.length; int[][] dp = new int[n1 + 1][n2 + 1]; for (int i = 1; i <= n1; i++) { for (int j = 1; j <= n2; j++) { if (nums1[i - 1] == nums2[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } return dp[n1][n2]; }
有一個容量爲 N 的揹包,要用這個揹包裝下物品的價值最大,這些物品有兩個屬性:體積 w 和價值 v。
定義一個二維數組 dp 存儲最大價值,其中 dp[i][j] 表示前 i 件物品體積不超過 j 的狀況下能達到的最大價值。設第 i 件物品體積爲 w,價值爲 v,根據第 i 件物品是否添加到揹包中,能夠分兩種狀況討論:
第 i 件物品可添加也能夠不添加,取決於哪一種狀況下最大價值更大。所以,0-1 揹包的狀態轉移方程爲:
public int knapsack(int W, int N, int[] weights, int[] values) { int[][] dp = new int[N + 1][W + 1]; for (int i = 1; i <= N; i++) { int w = weights[i - 1], v = values[i - 1]; for (int j = 1; j <= W; j++) { if (j >= w) { dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w] + v); } else { dp[i][j] = dp[i - 1][j]; } } } return dp[N][W]; }
空間優化
在程序實現時能夠對 0-1 揹包作優化。觀察狀態轉移方程能夠知道,前 i 件物品的狀態僅與前 i-1 件物品的狀態有關,所以能夠將 dp 定義爲一維數組,其中 dp[j] 既能夠表示 dp[i-1][j] 也能夠表示 dp[i][j]。此時,
由於 dp[j-w] 表示 dp[i-1][j-w],所以不能先求 dp[i][j-w],以防將 dp[i-1][j-w] 覆蓋。也就是說要先計算 dp[i][j] 再計算 dp[i][j-w],在程序實現時須要按倒序來循環求解。
public int knapsack(int W, int N, int[] weights, int[] values) { int[] dp = new int[W + 1]; for (int i = 1; i <= N; i++) { int w = weights[i - 1], v = values[i - 1]; for (int j = W; j >= 1; j--) { if (j >= w) { dp[j] = Math.max(dp[j], dp[j - w] + v); } } } return dp[W]; }
沒法使用貪心算法的解釋
0-1 揹包問題沒法使用貪心算法來求解,也就是說不能按照先添加性價比最高的物品來達到最優,這是由於這種方式可能形成揹包空間的浪費,從而沒法達到最優。考慮下面的物品和一個容量爲 5 的揹包,若是先添加物品 0 再添加物品 1,那麼只能存放的價值爲 16,浪費了大小爲 2 的空間。最優的方式是存放物品 1 和物品 2,價值爲 22.
id | w | v | v/w |
---|---|---|---|
0 | 1 | 6 | 6 |
1 | 2 | 10 | 5 |
2 | 3 | 12 | 4 |
變種
徹底揹包:物品數量爲無限個
多重揹包:物品數量有限制
多維費用揹包:物品不只有重量,還有體積,同時考慮這兩種限制
其它:物品之間相互約束或者依賴
416. Partition Equal Subset Sum (Medium)
Input: [1, 5, 11, 5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].
能夠當作一個揹包大小爲 sum/2 的 0-1 揹包問題。
public boolean canPartition(int[] nums) { int sum = computeArraySum(nums); if (sum % 2 != 0) { return false; } int W = sum / 2; boolean[] dp = new boolean[W + 1]; dp[0] = true; for (int num : nums) { // 0-1 揹包一個物品只能用一次 for (int i = W; i >= num; i--) { // 從後往前,先計算 dp[i] 再計算 dp[i-num] dp[i] = dp[i] || dp[i - num]; } } return dp[W]; } private int computeArraySum(int[] nums) { int sum = 0; for (int num : nums) { sum += num; } return sum; }
Input: nums is [1, 1, 1, 1, 1], S is 3.
Output: 5
Explanation:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
There are 5 ways to assign symbols to make the sum of nums be target 3.
該問題能夠轉換爲 Subset Sum 問題,從而使用 0-1 揹包的方法來求解。
能夠將這組數當作兩部分,P 和 N,其中 P 使用正號,N 使用負號,有如下推導:
sum(P) - sum(N) = target
sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)
2 * sum(P) = target + sum(nums)
所以只要找到一個子集,令它們都取正號,而且和等於 (target + sum(nums))/2,就證實存在解。
public int findTargetSumWays(int[] nums, int S) { int sum = computeArraySum(nums); if (sum < S || (sum + S) % 2 == 1) { return 0; } int W = (sum + S) / 2; int[] dp = new int[W + 1]; dp[0] = 1; for (int num : nums) { for (int i = W; i >= num; i--) { dp[i] = dp[i] + dp[i - num]; } } return dp[W]; } private int computeArraySum(int[] nums) { int sum = 0; for (int num : nums) { sum += num; } return sum; }
DFS 解法:
public int findTargetSumWays(int[] nums, int S) { return findTargetSumWays(nums, 0, S); } private int findTargetSumWays(int[] nums, int start, int S) { if (start == nums.length) { return S == 0 ? 1 : 0; } return findTargetSumWays(nums, start + 1, S + nums[start]) + findTargetSumWays(nums, start + 1, S - nums[start]); }
Input: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
Output: 4
Explanation: There are totally 4 strings can be formed by the using of 5 0s and 3 1s, which are "10","0001","1","0"
這是一個多維費用的 0-1 揹包問題,有兩個揹包大小,0 的數量和 1 的數量。
public int findMaxForm(String[] strs, int m, int n) { if (strs == null || strs.length == 0) { return 0; } int[][] dp = new int[m + 1][n + 1]; for (String s : strs) { // 每一個字符串只能用一次 int ones = 0, zeros = 0; for (char c : s.toCharArray()) { if (c == '0') { zeros++; } else { ones++; } } for (int i = m; i >= zeros; i--) { for (int j = n; j >= ones; j--) { dp[i][j] = Math.max(dp[i][j], dp[i - zeros][j - ones] + 1); } } } return dp[m][n]; }
Example 1:
coins = [1, 2, 5], amount = 11
return 3 (11 = 5 + 5 + 1)
Example 2:
coins = [2], amount = 3
return -1.
題目描述:給一些面額的硬幣,要求用這些硬幣來組成給定面額的錢數,而且使得硬幣數量最少。硬幣能夠重複使用。
由於硬幣能夠重複使用,所以這是一個徹底揹包問題。徹底揹包只須要將 0-1 揹包中逆序遍歷 dp 數組改成正序遍歷便可。
public int coinChange(int[] coins, int amount) { if (amount == 0 || coins == null || coins.length == 0) { return 0; } int[] dp = new int[amount + 1]; for (int coin : coins) { for (int i = coin; i <= amount; i++) { //將逆序遍歷改成正序遍歷 if (i == coin) { dp[i] = 1; } else if (dp[i] == 0 && dp[i - coin] != 0) { dp[i] = dp[i - coin] + 1; } else if (dp[i - coin] != 0) { dp[i] = Math.min(dp[i], dp[i - coin] + 1); } } } return dp[amount] == 0 ? -1 : dp[amount]; }
Input: amount = 5, coins = [1, 2, 5] Output: 4 Explanation: there are four ways to make up the amount: 5=5 5=2+2+1 5=2+1+1+1 5=1+1+1+1+1
徹底揹包問題,使用 dp 記錄可達成目標的組合數目。
public int change(int amount, int[] coins) { if (amount == 0 || coins == null || coins.length == 0) { return 0; } int[] dp = new int[amount + 1]; dp[0] = 1; for (int coin : coins) { for (int i = coin; i <= amount; i++) { dp[i] += dp[i - coin]; } } return dp[amount]; }
s = "leetcode",
dict = ["leet", "code"].
Return true because "leetcode" can be segmented as "leet code".
dict 中的單詞沒有使用次數的限制,所以這是一個徹底揹包問題。該問題涉及到字典中單詞的使用順序,所以可理解爲涉及順序的徹底揹包問題。
求解順序的徹底揹包問題時,對物品的迭代應該放在最裏層。
public boolean wordBreak(String s, List<String> wordDict) { int n = s.length(); boolean[] dp = new boolean[n + 1]; dp[0] = true; for (int i = 1; i <= n; i++) { for (String word : wordDict) { // 對物品的迭代應該放在最裏層 int len = word.length(); if (len <= i && word.equals(s.substring(i - len, i))) { dp[i] = dp[i] || dp[i - len]; } } } return dp[n]; }
377. Combination Sum IV (Medium)
nums = [1, 2, 3]
target = 4
The possible combination ways are:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
Note that different sequences are counted as different combinations.
Therefore the output is 7.
涉及順序的徹底揹包。
public int combinationSum4(int[] nums, int target) { if (nums == null || nums.length == 0) { return 0; } int[] maximum = new int[target + 1]; maximum[0] = 1; Arrays.sort(nums); for (int i = 1; i <= target; i++) { for (int j = 0; j < nums.length && nums[j] <= i; j++) { maximum[i] += maximum[i - nums[j]]; } } return maximum[target]; }
309. Best Time to Buy and Sell Stock with Cooldown(Medium)
題目描述:交易以後須要有一天的冷卻時間。
public int maxProfit(int[] prices) { if (prices == null || prices.length == 0) { return 0; } int N = prices.length; int[] buy = new int[N]; int[] s1 = new int[N]; int[] sell = new int[N]; int[] s2 = new int[N]; s1[0] = buy[0] = -prices[0]; sell[0] = s2[0] = 0; for (int i = 1; i < N; i++) { buy[i] = s2[i - 1] - prices[i]; s1[i] = Math.max(buy[i - 1], s1[i - 1]); sell[i] = Math.max(buy[i - 1], s1[i - 1]) + prices[i]; s2[i] = Math.max(s2[i - 1], sell[i - 1]); } return Math.max(sell[N - 1], s2[N - 1]); }
714. Best Time to Buy and Sell Stock with Transaction Fee (Medium)
Input: prices = [1, 3, 2, 8, 4, 9], fee = 2
Output: 8
Explanation: The maximum profit can be achieved by:
Buying at prices[0] = 1
Selling at prices[3] = 8
Buying at prices[4] = 4
Selling at prices[5] = 9
The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
題目描述:每交易一次,都要支付必定的費用。
public int maxProfit(int[] prices, int fee) { int N = prices.length; int[] buy = new int[N]; int[] s1 = new int[N]; int[] sell = new int[N]; int[] s2 = new int[N]; s1[0] = buy[0] = -prices[0]; sell[0] = s2[0] = 0; for (int i = 1; i < N; i++) { buy[i] = Math.max(sell[i - 1], s2[i - 1]) - prices[i]; s1[i] = Math.max(buy[i - 1], s1[i - 1]); sell[i] = Math.max(buy[i - 1], s1[i - 1]) - fee + prices[i]; s2[i] = Math.max(s2[i - 1], sell[i - 1]); } return Math.max(sell[N - 1], s2[N - 1]); }
123. Best Time to Buy and Sell Stock III (Hard)
public int maxProfit(int[] prices) { int firstBuy = Integer.MIN_VALUE, firstSell = 0; int secondBuy = Integer.MIN_VALUE, secondSell = 0; for (int curPrice : prices) { if (firstBuy < -curPrice) { firstBuy = -curPrice; } if (firstSell < firstBuy + curPrice) { firstSell = firstBuy + curPrice; } if (secondBuy < firstSell - curPrice) { secondBuy = firstSell - curPrice; } if (secondSell < secondBuy + curPrice) { secondSell = secondBuy + curPrice; } } return secondSell; }
188. Best Time to Buy and Sell Stock IV (Hard)
public int maxProfit(int k, int[] prices) { int n = prices.length; if (k >= n / 2) { // 這種狀況下該問題退化爲普通的股票交易問題 int maxProfit = 0; for (int i = 1; i < n; i++) { if (prices[i] > prices[i - 1]) { maxProfit += prices[i] - prices[i - 1]; } } return maxProfit; } int[][] maxProfit = new int[k + 1][n]; for (int i = 1; i <= k; i++) { int localMax = maxProfit[i - 1][0] - prices[0]; for (int j = 1; j < n; j++) { maxProfit[i][j] = Math.max(maxProfit[i][j - 1], prices[j] + localMax); localMax = Math.max(localMax, maxProfit[i - 1][j] - prices[j]); } } return maxProfit[k][n - 1]; }
583. Delete Operation for Two Strings (Medium)
Input: "sea", "eat"
Output: 2
Explanation: You need one step to make "sea" to "ea" and another step to make "eat" to "ea".
能夠轉換爲求兩個字符串的最長公共子序列問題。
public int minDistance(String word1, String word2) { int m = word1.length(), n = word2.length(); int[][] dp = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (word1.charAt(i - 1) == word2.charAt(j - 1)) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); } } } return m + n - 2 * dp[m][n]; }
Example 1:
Input: word1 = "horse", word2 = "ros"
Output: 3
Explanation:
horse -> rorse (replace 'h' with 'r')
rorse -> rose (remove 'r')
rose -> ros (remove 'e')
Example 2:
Input: word1 = "intention", word2 = "execution"
Output: 5
Explanation:
intention -> inention (remove 't')
inention -> enention (replace 'i' with 'e')
enention -> exention (replace 'n' with 'x')
exention -> exection (replace 'n' with 'c')
exection -> execution (insert 'u')
題目描述:修改一個字符串成爲另外一個字符串,使得修改次數最少。一次修改操做包括:插入一個字符、刪除一個字符、替換一個字符。
public int minDistance(String word1, String word2) { if (word1 == null || word2 == null) { return 0; } int m = word1.length(), n = word2.length(); int[][] dp = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { dp[i][0] = i; } for (int i = 1; i <= n; i++) { dp[0][i] = i; } for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (word1.charAt(i - 1) == word2.charAt(j - 1)) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j])) + 1; } } } return dp[m][n]; }
題目描述:最開始只有一個字符 A,問須要多少次操做可以獲得 n 個字符 A,每次操做能夠複製當前全部的字符,或者粘貼。
Input: 3 Output: 3 Explanation: Intitally, we have one character 'A'. In step 1, we use Copy All operation. In step 2, we use Paste operation to get 'AA'. In step 3, we use Paste operation to get 'AAA'.
public int minSteps(int n) { if (n == 1) return 0; for (int i = 2; i <= Math.sqrt(n); i++) { if (n % i == 0) return i + minSteps(n / i); } return n; }
public int minSteps(int n) { int[] dp = new int[n + 1]; int h = (int) Math.sqrt(n); for (int i = 2; i <= n; i++) { dp[i] = i; for (int j = 2; j <= h; j++) { if (i % j == 0) { dp[i] = dp[j] + dp[i / j]; break; } } } return dp[n]; }