131. Palindrome Partitioning

回溯法
class Solution {
public List<List<String>> partition(String s) {
List<List<String>> res = new ArrayList<List<String>>();
if(s==null || s.length()==0)
return res;
backtracking(s,0,res,new ArrayList<String>());
return res;
}
public void backtracking(String s,int start,List<List<String>> res,List<String> arr){
if(arr.size() > 0 && start==s.length()){
res.add(new ArrayList<String>(arr));
return;
}
for(int i=start;i<s.length();i++){
if(isPalindrome(s,start,i)){
arr.add(s.substring(start,i+1));
backtracking(s,i+1,res,arr);
arr.remove(arr.size()-1);
}
}
}
public boolean isPalindrome(String s,int start,int end){
while(start<=end ){
if(s.charAt(start)==s.charAt(end)){
start++;
end--;
}
else{
return false;
}
}
return true;
}
}
132. Palindrome Partitioning II

對于輸入字符串如s=“aab”,一個直觀的思路是判斷“aab”是否構(gòu)成回文,根據(jù)回文的特點是對稱性,那么,我們可以判斷s[0]與s[2]是否相等,不等,因此“aab”不能構(gòu)成回文,此后再怎么判斷呢???這種無章法的操作沒有意義,因為一個字符串構(gòu)成回文的情況是很復(fù)雜的,對于一個長度為n的字符串,其構(gòu)成回文的子串長度可能的長度分布范圍可以是1—n:整體構(gòu)成回文如“baab”,則子串長度可為n=4;如“cab”,只能構(gòu)成長度為1的回文子串。
鑒于上述分析,對于一個字符串,我們需要考慮所有可能的分割,這個問題可以抽象成一個DP問題,對于一個長度為n的字符串,設(shè)DP[i][j]表示第i個字符到第j個字符是否構(gòu)成回文,若是,則DP[i][j]=1;若否,則DP[i][j]=0;如此,根據(jù)回文的約束條件(對稱性),DP[i][j]構(gòu)成回文需滿足:
1、輸入字符串s[i]==s[j],對稱性;
2、條件1滿足并不能保證i到j(luò)構(gòu)成回文,還須:(j-i)<=1或者DP[i+1][j-1]=1;即,i、j相鄰或者i=j,也就是相鄰字符相等構(gòu)成回文或者字符自身構(gòu)成回文,如果i、j不相鄰或者相等,i到j(luò)構(gòu)成回文的前提就是DP[i+1][j-1]=1.
所以狀態(tài)轉(zhuǎn)移方程:DP[i][j]=(s[i]==s[j]&&(j-i<=1||DP[i+1][j-1]==1))。由于i必須小于j,i>=0&&i<s.length().如此,DP[i][j]構(gòu)成一個下三角矩陣,空間、時間復(fù)雜度均為O(n2),如下所示:對于長度為4的字符串s=“baab”,其中紅色部分為i>j,為無意義部分;綠色部分i==j,即字符串自身必然構(gòu)成回文,DP[i][j]=1;白色部分,為長度大于1的子串,需要狀態(tài)轉(zhuǎn)移方程進(jìn)行判斷。
經(jīng)過判斷,得到狀態(tài)矩陣:綠色部分,即字符串“baab”可構(gòu)成的回文子串分割情況:綠色部分DP[0][0]、DP[1][1]、DP[2][2]和DP[3][3]構(gòu)成的是單字符回文子串,DP[1][2]和DP[0][3]構(gòu)成的是多字符回文子串。
在得到輸入字符串的所有回文子串的分割情況之后,我們需要考慮如何求取回文子串的最小分割次數(shù),顯然,回文子串越長,分割次數(shù)越少,因此,分割的時候回文子串應(yīng)分別取最大長度,如上面的例子,s="baab",可行的分割情況有三種:(顯然,最少分割次數(shù)為0,當(dāng)然,根據(jù)DP[][]矩陣可以很容易求取最長回文子串?。。。。?。
1、{DP[0][0]、DP[1][1]、DP[2][2]、DP[3][3]};
2、{DP[0][0]、DP[1][2]、DP[3][3]};
3、{DP[0][3]}
當(dāng)輸入字符串所有可能的分割情況求出來之后,我們需要進(jìn)一步尋找最少分割次數(shù),我們可以用一個一維數(shù)組來存儲分割次數(shù):設(shè)int[] cut=new int[s.length()+1],cut[i]表示第i個字符到最后一個字符所構(gòu)成的子串的最小分割次數(shù),這里的i有約束條件,就是第i個位置必須是可進(jìn)行回文分割的,即DP[i][j]==1 (j>=i&&j<s.length()),故:
根據(jù)這個公式,我們最終要求的cut[0]與cut[0]、cut[1]...cut[len]都有關(guān),直接求需要遞歸,效率低,因此我們可以逆序求解,即:先求cut[len-1],最后求cut[0].
class Solution {
public int minCut(String s) {
int [][] dp=new int[s.length()][s.length()];
int [] cut=new int[s.length()+1];
cut[s.length()] = 0;
for(int i=s.length()-1;i>=0;i--)
{
cut[i]=Integer.MAX_VALUE;
for(int j=i;j<s.length();j++)
{
if(s.charAt(i)==s.charAt(j)&&(j-i<=1||dp[i+1][j-1]==1))
{
dp[i][j]=1;
cut[i]=Math.min(1+cut[j+1],cut[i]);
}
}
}
return cut[0]-1;
}
}
134. Gas Station

首先,總的油要比總的消費多,同時要保證每一個站點的時候,油都要比消費多,如果不行的話,我們要從下一個站點作為起點進(jìn)行嘗試,從前面的站點作為起點已經(jīng)不合適了。
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
int sumGas = 0;
int sumCost = 0;
int start = 0;
int tank = 0;
for (int i = 0; i < gas.length; i++) {
sumGas += gas[I];
sumCost += cost[I];
tank += gas[i] - cost[I];
if (tank < 0) {
start = i + 1;
tank = 0;
}
}
if (sumGas < sumCost) {
return -1;
} else {
return start;
}
}
}
135. Candy

這是我的思路,從左到右判斷需要多少個糖果,如果當(dāng)前位置的比重大于前一個位置的,那么就是前一個位置的糖果+1,如果相等,直接為1,如果小于前面位置的權(quán)重,那么如果前面的糖果為1,則需要作出調(diào)整,如果是大于,則直接賦值1.
class Solution {
public int candy(int[] ratings) {
int[] res = new int[ratings.length];
if(ratings == null || ratings.length==0)
return 0;
for(int i=0;i<ratings.length;i++){
res[i]=1;
}
for(int i=1;i<res.length;i++){
if(ratings[i] > ratings[i-1])
res[i] = res[i-1] + 1;
else if(ratings[i]==ratings[i-1])
res[i] = 1;
else{
if(res[i-1] > 1)
res[i] = 1;
else{
res[i-1] += 1;
int t = i-1;
while(t>0 && res[t]==res[t-1] && ratings[t-1] > ratings[t]){
res[t-1] ++;
t--;
}
}
}
}
int sum=0;
for(int i=0;i<res.length;i++){
sum += res[I];
}
return sum;
}
}
可是上面做法超時了:

正確解法1
兩個數(shù)組,一個數(shù)組只考慮左邊的鄰居,一個數(shù)組只考慮右邊的鄰居,最后兩個數(shù)組對應(yīng)位置中較大的數(shù)作為該位置的需要的糖果數(shù)。
public int candy(int[] ratings) {
if(ratings==null || ratings.length==0)
return 0;
int[] left2right = new int[ratings.length];
int[] right2left = new int[ratings.length];
for(int i=0;i<ratings.length;i++){
left2right[i] = 1;
right2left[i] = 1;
}
for(int i=1;i<ratings.length;i++){
left2right[i] = ratings[i] > ratings[i-1] ? left2right[i-1]+1:1;
right2left[ratings.length-i-1] = ratings[ratings.length-i-1] > ratings[ratings.length-i] ? right2left[ratings.length-i] + 1:1;
}
int sum = 0;
for(int i=0;i<ratings.length;i++){
sum += Math.max(left2right[i],right2left[I]);
}
return sum;
}
}
正確解法2
只用一個數(shù)組,先從左往右遍歷,再從右往左遍歷,這種方式其實和第一種解法是一樣的,但是只用到了一個數(shù)組,空間復(fù)雜度比較低。
public class Solution {
public int candy(int[] ratings) {
int[] candies = new int[ratings.length];
Arrays.fill(candies, 1);
for (int i = 1; i < ratings.length; i++) {
if (ratings[i] > ratings[i - 1]) {
candies[i] = candies[i - 1] + 1;
}
}
int sum = candies[ratings.length - 1];
for (int i = ratings.length - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) {
candies[i] = Math.max(candies[i], candies[i + 1] + 1);
}
sum += candies[I];
}
return sum;
}
}
136. Single Number

利用兩個數(shù)的異或是0,遍歷一邊數(shù)組即可.
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for(int num:nums){
res ^= num;
}
return res;
}
}
137. Single Number II

借鑒136題的思路,我們把二進(jìn)制的問題轉(zhuǎn)換為三進(jìn)制來求解,二進(jìn)制中,異或是兩個一樣的為0,那么在三進(jìn)制中,我們設(shè)計的是三個一樣的為0.
class Solution {
public int singleNumber(int[] nums) {
int ones = 0;
int twos = 0;
int threes = 0;
for(int num:nums){
twos |= ones & num;
ones = ones ^ num;
threes = ones & twos;
ones &= ~threes;
twos &= ~threes;
}
return ones;
}
}
139. Word Break

回溯法:超時
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
if(s==null || s.length()==0 || wordDict==null || wordDict.size()==0)
return false;
boolean res = search(s,wordDict,0);
return res;
}
public boolean search(String s,List<String> wordDict,int start){
if(start == s.length())
return true;
for(int i=start;i<s.length();i++){
if(wordDict.contains(s.substring(start,i+1))){
boolean res = search(s,wordDict,i+1);
if(res)
return true;
}
}
return false;
}
}
動態(tài)規(guī)劃
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
if(s==null && wordDict==null)
return true;
if(s==null || wordDict==null)
return false;
boolean[] dp = new boolean[s.length()+1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
//如果dp[j]為true且字典中有j-i的子串的話,為true
if (dp[j] && wordDict.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
140. Word Break II

回溯,超時
class Solution {
public List<String> wordBreak(String s, List<String> wordDict) {
List<String> res = new ArrayList<String>();
if(s==null || wordDict==null)
return res;
search(res,s,wordDict,new StringBuilder(),0);
return res;
}
public void search(List<String> res,String s,List<String> wordDict,StringBuilder str,int start){
if(start == s.length()){
res.add(new StringBuilder(str.delete(0,1).toString()).toString());
return;
}
for(int i=start;i<s.length();i++){
if(wordDict.contains(s.substring(start,i+1))){
StringBuilder newstr = new StringBuilder(str.toString());
newstr.append(" ");
newstr.append(s.substring(start,i+1));
search(res,s,wordDict,newstr,i+1);
}
}
}
}
