17. 按摩師
一個有名的按摩師會收到源源不斷的預約請求,每個預約都可以選擇接或不接。在每次預約服務之間要有休息時間,因此她不能接受相鄰的預約。給定一個預約請求序列,替按摩師找到最優的預約集合(總預約時間最長),返回總的分鐘數。
注意:本題相對原題稍作改動
示例 1:
輸入: [1,2,3,1]
輸出: 4
解釋: 選擇 1 號預約和 3 號預約,總時長 = 1 + 3 = 4。
示例 2:
輸入: [2,7,9,3,1]
輸出: 12
解釋: 選擇 1 號預約、 3 號預約和 5 號預約,總時長 = 2 + 9 + 1 = 12。
示例 3:
輸入: [2,1,4,5,3,1,1,3]
輸出: 12
解釋: 選擇 1 號預約、 3 號預約、 5 號預約和 8 號預約,總時長 = 2 + 4 + 3 + 3 = 12。
思路
1.動態規劃法。最大的序列和爲 Max(前一位之前最大的序列和,前兩位之前最大的序列和加上現在着這個數)
public int massage(int[] nums) {
int[] dp = new int[nums.length];
if (nums.length == 0) {
return 0;
}
if (nums.length == 1) {
return nums[0];
}
dp[0] = nums[0];
dp[1] = Math.max(dp[0], nums[1]);
//最大的序列和爲 Max(前一位之前最大的序列和,前兩位之前最大的序列和加上現在着這個數)
for (int i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[nums.length - 1];
}
2.基於思路1優化,使用兩個變量循環表示前一位之前最大的序列和和前兩位之前最大的序列和。
public int massage(int[] nums) {
int fast = 0, slow = 0;
//fast爲往前一位 slow爲往前兩位
for (int i = 0; i < nums.length; i++) {
int dp = Math.max(fast, slow + nums[i]);
slow = fast;
fast = dp;
}
return fast;
}
擴展
打家劫舍 II
你是一個專業的小偷,計劃偷竊沿街的房屋,每間房內都藏有一定的現金。這個地方所有的房屋都圍成一圈,這意味着第一個房屋和最後一個房屋是緊挨着的。同時,相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。給定一個代表每個房屋存放金額的非負整數數組,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。
1.數組組成了環形,不能從第一個房子偷到最後一個房子,所以統計從第一個房子不偷情況下最高金額,再統計最後一個房子不偷的情況下的最高金額,取他們的最大值,統計方法與上題一樣。
public int rob(int[] nums) {
if (nums.length == 0) {
return 0;
}
if (nums.length == 1) {
return nums[0];
}
//將數組分爲兩份,第一份統計第一個房子不偷第二份統計最後一個房子不偷。
return Math.max(robTwice(Arrays.copyOfRange(nums, 1, nums.length)),
robTwice(Arrays.copyOfRange(nums, 0, nums.length - 1)));
}
public int robTwice(int[] nums) {
int fast = 0, slow = 0;
//fast爲往前一位 slow爲往前兩位
for (int i = 0; i < nums.length; i++) {
int dp = Math.max(fast, slow + nums[i]);
slow = fast;
fast = dp;
}
return fast;
}
斐波那契數
斐波那契數,通常用 F(n) 表示,形成的序列稱爲斐波那契數列。該數列由 0 和 1 開始,後面的每一項數字都是前面兩項數字的和。也就是:F(0) = 0, F(1) = 1F(N) = F(N - 1) + F(N - 2), 其中 N > 1.給定 N,計算 F(N)。
1.動態規劃思路,非遞歸,耗時少。
public int fib(int n){
if(n==0 || n==1){
return n;
}
int fn1=0;
int fn2=1;
for(int i=2;i<=n;i++){
fn2+=fn1;
fn1=fn2-fn1;
}
return fn2;
}
2.使用遞歸,代碼簡潔,但是耗時較久。
public int fib(int N) {
if (N<=1){
return N;
}
return fib(N-1)+fib(N-2);
}
最長迴文子串
給定一個字符串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度爲 1000。
1.使用中心擴展法,遍歷字符串,統計每個數相鄰的數及其兩邊的數是否相等,即是否構成迴文串。然後向兩邊擴展,每次記錄迴文串長度,最後選出長度最大的字符串。
public String longestPalindrome(String s) {
int longLength = 0, index = 0;
for (int i = 0; i < s.length(); i++) {
int left = i - 1, right = i + 1, temp = 1;
//如果左邊構成迴文串,繼續向左邊拓展
while (left >= 0 && s.charAt(i) == s.charAt(left)) {
temp++;
left--;
}
//如果右邊構成迴文串,繼續向右邊拓展
while (right < s.length() && s.charAt(i) == s.charAt(right)) {
temp++;
right++;
}
//左右兩邊構成迴文串,同時拓展
while (right < s.length() && left >= 0 && s.charAt(left) == s.charAt(right)) {
temp += 2;
left--;
right++;
}
//選出迴文串最初的長度和開始下標
if (longLength < temp) {
longLength = temp;
//left最後進行了--,所以要加一
index = left + 1;
}
}
return s.substring(index, index + longLength);
}
2.使用動態規劃法,要判斷一個字符串是否是迴文,只需要判斷這個字符串的左右兩邊字符相等且他們中包含的字符串也是迴文串即可。
public String longestPalindrome(String s) {
//如果是空或者一位,直接返回
if (s == null || s.length() < 2) {
return s;
}
int length = s.length(), longLength = 1, index = 0;
//動態規劃二位數組,dp[left][right]=true 表示left到right這段索引的字符串爲迴文串
boolean[][] dp = new boolean[length][length];
//依次往後面遍歷,類似暴力法,只不過用動態規劃作爲判斷條件
for (int right = 1; right < s.length(); right++) {
for (int left = 0; left < right; left++) {
//如果左右字符相等且子字符串爲迴文串,則當前字符串也爲迴文串。right-left<3表示left和初始狀態沒有公共部分
if (s.charAt(right) == s.charAt(left) && (right - left < 3 || dp[left + 1][right - 1])) {
dp[left][right] = true;
if (longLength < right - left + 1) {
longLength = right - left + 1;
index = left;
}
}
}
}
return s.substring(index, index + longLength);
}
爬樓梯
假設你正在爬樓梯。需要 n 階你才能到達樓頂。每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?
1.動態規劃法,要到n階,那麼先要到n-1階隨後走一步或者n-2階隨後兩步兩種情況。所以將問題化爲計算跳到倒數第二個臺階和倒數第一個臺階之和。非遞歸寫法。
public int climbStairs(int n) {
if (n == 0) {
return 1;
}
if (n == 1 || n == 2) {
return n;
}
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
2.遞歸寫法
public int climbStairs(int n) {
if (n <= 2) {
return n;
}
return climbStairs(n - 1) + climbStairs(n - 2);
}
最大子序和
給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。
1.動態規劃,遍歷數組,如果之前統計的值爲負數,則捨棄掉重新統計,然後判斷捨棄之前與當前值的值比較,取出最大值。直至遍歷結束。
public int maxSubArray(int[] nums) {
int res = nums[0];
int sum = 0;
for (int i=0;i<nums.length;i++) {
//如果之前的值爲正數,就繼續統計
if (sum > 0)
sum += nums[i];
//如果之前的值爲負數,就捨棄掉重新計算
else
sum = nums[i];
//取出當前值和以前的最大值中較大的。
res = Math.max(res, sum);
}
return res;
}