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”是否构成回文,根据回文的特点是对称性,那么,我们可以判断s[0]与s[2]是否相等,不等,因此“aab”不能构成回文,此后再怎么判断呢???这种无章法的操作没有意义,因为一个字符串构成回文的情况是很复杂的,对于一个长度为n的字符串,其构成回文的子串长度可能的长度分布范围可以是1—n:整体构成回文如“baab”,则子串长度可为n=4;如“cab”,只能构成长度为1的回文子串。
鉴于上述分析,对于一个字符串,我们需要考虑所有可能的分割,这个问题可以抽象成一个DP问题,对于一个长度为n的字符串,设DP[i][j]表示第i个字符到第j个字符是否构成回文,若是,则DP[i][j]=1;若否,则DP[i][j]=0;如此,根据回文的约束条件(对称性),DP[i][j]构成回文需满足:
1、输入字符串s[i]==s[j],对称性;
2、条件1满足并不能保证i到j构成回文,还须:(j-i)<=1或者DP[i+1][j-1]=1;即,i、j相邻或者i=j,也就是相邻字符相等构成回文或者字符自身构成回文,如果i、j不相邻或者相等,i到j构成回文的前提就是DP[i+1][j-1]=1.
所以状态转移方程: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]构成一个下三角矩阵,空间、时间复杂度均为O(n2),如下所示:对于长度为4的字符串s=“baab”,其中红色部分为i>j,为无意义部分;绿色部分i==j,即字符串自身必然构成回文,DP[i][j]=1;白色部分,为长度大于1的子串,需要状态转移方程进行判断。
经过判断,得到状态矩阵:绿色部分,即字符串“baab”可构成的回文子串分割情况:绿色部分DP[0][0]、DP[1][1]、DP[2][2]和DP[3][3]构成的是单字符回文子串,DP[1][2]和DP[0][3]构成的是多字符回文子串。
在得到输入字符串的所有回文子串的分割情况之后,我们需要考虑如何求取回文子串的最小分割次数,显然,回文子串越长,分割次数越少,因此,分割的时候回文子串应分别取最大长度,如上面的例子,s="baab",可行的分割情况有三种:(显然,最少分割次数为0,当然,根据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]}
当输入字符串所有可能的分割情况求出来之后,我们需要进一步寻找最少分割次数,我们可以用一个一维数组来存储分割次数:设int[] cut=new int[s.length()+1],cut[i]表示第i个字符到最后一个字符所构成的子串的最小分割次数,这里的i有约束条件,就是第i个位置必须是可进行回文分割的,即DP[i][j]==1 (j>=i&&j<s.length()),故:
根据这个公式,我们最终要求的cut[0]与cut[0]、cut[1]...cut[len]都有关,直接求需要递归,效率低,因此我们可以逆序求解,即:先求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
首先,总的油要比总的消费多,同时要保证每一个站点的时候,油都要比消费多,如果不行的话,我们要从下一个站点作为起点进行尝试,从前面的站点作为起点已经不合适了。
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
这是我的思路,从左到右判断需要多少个糖果,如果当前位置的比重大于前一个位置的,那么就是前一个位置的糖果+1,如果相等,直接为1,如果小于前面位置的权重,那么如果前面的糖果为1,则需要作出调整,如果是大于,则直接赋值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
两个数组,一个数组只考虑左边的邻居,一个数组只考虑右边的邻居,最后两个数组对应位置中较大的数作为该位置的需要的糖果数。
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
只用一个数组,先从左往右遍历,再从右往左遍历,这种方式其实和第一种解法是一样的,但是只用到了一个数组,空间复杂度比较低。
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
利用两个数的异或是0,遍历一边数组即可.
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for(int num:nums){
res ^= num;
}
return res;
}
}
137. Single Number II
借鉴136题的思路,我们把二进制的问题转换为三进制来求解,二进制中,异或是两个一样的为0,那么在三进制中,我们设计的是三个一样的为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;
}
}
动态规划
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);
}
}
}
}