有几个需要注意的要点:
// 需要new一个新的对象,不可以直接写成res.add(sub);
res.add(new ArrayList<>(sub));
// 如果是回溯算法,记得撤销动作,如果是基本类型的修改不用撤销
sub.remove(sub.size()-1);
46. 全排列
class Solution {
public List<List<Integer>> permute(int[] nums) {
int n=nums.length;
boolean[] used=new boolean[n];
List<Integer> path=new ArrayList<>();
List<List<Integer>> res=new ArrayList<>();
backtrack(nums,path,used,res);
return res;
}
private void backtrack(int[] nums,List<Integer> path,boolean[] used,List<List<Integer>> res){
if(path.size()==nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i=0;i<nums.length;i++){
if(!used[i]){
path.add(nums[i]);
used[i]=true;
backtrack(nums,path,used,res);
used[i]=false;
path.remove(path.size()-1);
}
}
}
}
剑指 Offer 38. 字符串的排列
剑指 Offer 38. 字符串的排列
和上面的全排列非常相似,区别:这道题input中用来排列的元素可能重复
题目中给的abc是没有重复字母的,乍一看和上面一题一模一样,但是,第一次提交后发现,输入是”aab”时,重复的a会导致有重复的排列。需要去重。
去重的方法:
- 排序,把相同的字符都放在一起
在backtrack函数中,if(i>0&&array[i]==array[i-1]&&!used[i-1]) continue; 如果当前元素和前一个元素相同,且前一个元素没有被用到,那么跳过。(如果前一个a没有被用到现在的组合中,而使用了现在这个a,那就和使用了前一个a没用现在这个a的排列重复了)
class Solution {
public String[] permutation(String s) {
char[] array=s.toCharArray();
List<String> resList=new ArrayList<>();
StringBuilder sb=new StringBuilder();
boolean[] used=new boolean[array.length];
Arrays.sort(array);
backtrack(sb,resList,array,used);
String[] result = resList.toArray(new String[resList.size()]);
return result;
}
private void backtrack(StringBuilder sb,List<String> resList,char[] array,boolean[] used){
if(sb.length()==array.length){
resList.add(sb.toString());
return;
}
for(int i=0;i<array.length;i++){
if(used[i]) continue;
if(i>0&&array[i]==array[i-1]&&!used[i-1]) continue;
sb.append(array[i]);
used[i]=true;
backtrack(sb,resList,array,used);
used[i]=false;
sb.deleteCharAt(sb.length()-1);
}
}
}
131. 分割回文串
题目链接:131. 分割回文串
class Solution {
public List<List<String>> partition(String s) {
List<List<String>> res=new ArrayList<>();
ArrayList<String> sub=new ArrayList<>();
dfs(s,0,sub,res);
return res;
}
private void dfs(String s,int start,ArrayList<String> sub,List<List<String>> res){
if(start==s.length()){
res.add(new ArrayList<>(sub));
return;
}
for(int i=start;i<s.length();i++){
String s1=s.substring(start,i+1);
if(!isPalindrome(s1)) continue;
sub.add(s1);
dfs(s,i+1,sub,res);
sub.remove(sub.size()-1);
}
}
private boolean isPalindrome(String s){
if(s==null||s.length()<2) return true;
int l=0,r=s.length()-1;
while(l<r){
if(s.charAt(l++)!=s.charAt(r--)){
return false;
}
}
return true;
}
}
用dp来记录,进一步提高效率(提高了一点点点)
class Solution {
public List<List<String>> partition(String s) {
List<List<String>> res=new ArrayList<>();
ArrayList<String> sub=new ArrayList<>();
int len=s.length();
boolean[][] dp=new boolean[len][len];
for(int i=0;i<len;i++){
isPalindrome(s,i,i,dp);
isPalindrome(s,i,i+1,dp);
}
dfs(s,0,sub,res,dp);
return res;
}
private void dfs(String s,int start,ArrayList<String> sub,List<List<String>> res,boolean[][] dp){
if(start==s.length()){
res.add(new ArrayList<>(sub));
return;
}
for(int i=start;i<s.length();i++){
if(!dp[start][i]) continue;
sub.add(s.substring(start,i+1));
dfs(s,i+1,sub,res,dp);
sub.remove(sub.size()-1);
}
}
private void isPalindrome(String s,int i,int j,boolean[][] dp){
int len=s.length();
while(i>=0&&j<len&&s.charAt(i)==s.charAt(j)){
dp[i][j]=true;
i--;
j++;
}
}
}
有一道困难题,看起来和这个很类似,但其实不能用相同方法来解决,会超时。
132. 分割回文串 II 困难
132. 分割回文串 II
使用动态规划,dp[i]表示s.substring(0,i)需要最少切割几次class Solution {
public int minCut(String s) {
if(s == null || s.length() <= 1)
return 0;
int len = s.length();
int dp[] = new int[len];
Arrays.fill(dp, len-1);
for(int i = 0; i < len; i++){
findMinCut(s , i , i , dp); // 奇数回文串以1个字符为中心
findMinCut(s, i , i+1 , dp); // 偶数回文串以2个字符为中心
}
return dp[len-1];
}
private void findMinCut(String s, int i, int j, int[] dp){
int len = s.length();
while(i >= 0 && j < len && s.charAt(i) == s.charAt(j)){
//以当前字符为中心的最大回文串左侧为i,右侧为j,那s[i]~s[j]长度是不需要切割的
//只需要在s[i-1]处切一刀即可,即dp[i-1]+1。所以更新dp[j] = min(dp[j] , dp[i-1]+1)
dp[j] = Math.min(dp[j] , (i==0?-1:dp[i-1])+1);
i--;
j++;
}
}
}
93. 复原 IP 地址
题目:93. 复原 IP 地址
这个答案的效率不太好,但是是我自己想出来的,所以记录一下思路
有几个要点:
(1)当前的数字最短能有多短
比如:输入的数据为“25525511135”,一共是11位,在不知道具体数字的情况下,我们也能知道,最短可以是2位,如果4个数字中有一个是1位,那么就会出现数字多余的情况。
(2)当前的数字最长能有多长
比如:输入的数据为“1111”,一共是11位,在不知道具体数字的情况下,我们也能知道,最长只可以是1位,如果4个数字中有一个是2位,那么就会出现数字不够用的情况。
(3)转换成数字之前的格式校验
当时没注意到这一点,导致漏了很多0
比如”01”这个字符串是不符合要求的,虽然转换成int之后是1。转换之前需要进行校验。
(4)把结果加入resList的方法
一开始path我是用的List
if(nums.size()==4){
String str=nums.get(0)+"."+nums.get(1)+"."+nums.get(2)+"."+nums.get(3);
resList.add(str);
return;
}
可以看到优化了很多很多
class Solution {
public List<String> restoreIpAddresses(String s) {
int len=s.length();
if(len>12||len<4) return new ArrayList<>();
List<String> resList=new ArrayList<>();
List<String> path=new ArrayList<>();
dfs(0,path,resList,s);
return resList;
}
private void dfs(int start,List<String> path,List<String> resList,String s){
if(path.size()==4){
resList.add(String.join(".", path)); //优化了很多!!!
return;
}
int len=minLen(start,path,s);
int limit=maxLen(start, path, s);
for(;len<=limit&&(start+len)<=s.length();len++){
String str=s.substring(start,start+len);
if(str.length()>1&&str.charAt(0)=='0') continue;
int num=Integer.parseInt(str);
if(num>=0&&num<=255){
path.add(str);
dfs(start+len,path,resList,s);
path.remove(path.size()-1);
}
}
}
private int minLen(int start,List<String> path,String s){
int used=path.size();
int remainNumber=s.length()-start;
int remainPos=4-used;
if((remainNumber/3)==(remainPos-1)){
return remainNumber%3==0?1:remainNumber%3;
}else if((remainNumber/3)==remainPos){
return 3;
}else{
return 1;
}
}
private int maxLen(int start,List<String> path,String s){
int used=path.size();
int remainNumber=s.length()-start;
int remainPos=4-used;
int len=remainNumber-remainPos+1;
return Math.min(3,len);
}
}
大佬的思路:
代码更简洁,但是效率没提升
class Solution {
public List<String> restoreIpAddresses(String s) {
int len=s.length();
if(len>12||len<4) return new ArrayList<>();
List<String> resList=new ArrayList<>();
List<String> path=new ArrayList<>();
dfs(0,path,resList,s);
return resList;
}
private void dfs(int start,List<String> path,List<String> resList,String s){
if(path.size()==4){
resList.add(String.join(".", path));
return;
}
for(int len=1;len<=3&&(start+len)<=s.length();len++){
String str=s.substring(start,start+len);
if(str.length()>1&&str.charAt(0)=='0') continue;
//不够了或有多余的
//int remainNumber=s.length()-start-len;
//int remainPos=4-path.size()-1;
if((s.length()-start-len)<(3-path.size())||(s.length()-start-len)>(3-path.size())*3) continue;
int num=Integer.parseInt(str);
if(num>=0&&num<=255){
path.add(str);
dfs(start+len,path,resList,s);
path.remove(path.size()-1);
}
}
}
}
剑指 Offer 12. 矩阵中的路径
链接:剑指 Offer 12. 矩阵中的路径
写的有点丑陋
class Solution {
int m,n;
char[] c;
boolean[][] visited;
char[][] board;
int[][] directions=new int[][]{{0,1},{0,-1},{1,0},{-1,0}};
public boolean exist(char[][] board, String word) {
this.board=board;
m=board.length;
n=board[0].length;
c=word.toCharArray();
visited=new boolean[m][n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(board[i][j]==c[0]){
visited[i][j]=true;
if(backtrack(i,j,1)) return true;
visited[i][j]=false;;
}
}
}
return false;
}
private boolean backtrack(int i,int j,int next){
if(next==c.length) return true;
for(int[] dir:directions){
int x=i+dir[0];
int y=j+dir[1];
if(x>=0&&y>=0&&x<m&&y<n&&c[next]==board[x][y]&&!visited[x][y]){
visited[x][y]=true;
if(backtrack(x, y, next+1)) return true;
visited[x][y]=false;
}
}
return false;
}
}