有几个需要注意的要点:

  1. // 需要new一个新的对象,不可以直接写成res.add(sub);
  2. res.add(new ArrayList<>(sub));
  3. // 如果是回溯算法,记得撤销动作,如果是基本类型的修改不用撤销
  4. sub.remove(sub.size()-1);

经典回溯:

46. 全排列

46. 全排列
image.png

  1. class Solution {
  2. public List<List<Integer>> permute(int[] nums) {
  3. int n=nums.length;
  4. boolean[] used=new boolean[n];
  5. List<Integer> path=new ArrayList<>();
  6. List<List<Integer>> res=new ArrayList<>();
  7. backtrack(nums,path,used,res);
  8. return res;
  9. }
  10. private void backtrack(int[] nums,List<Integer> path,boolean[] used,List<List<Integer>> res){
  11. if(path.size()==nums.length){
  12. res.add(new ArrayList<>(path));
  13. return;
  14. }
  15. for(int i=0;i<nums.length;i++){
  16. if(!used[i]){
  17. path.add(nums[i]);
  18. used[i]=true;
  19. backtrack(nums,path,used,res);
  20. used[i]=false;
  21. path.remove(path.size()-1);
  22. }
  23. }
  24. }
  25. }

剑指 Offer 38. 字符串的排列

剑指 Offer 38. 字符串的排列
和上面的全排列非常相似,区别:这道题input中用来排列的元素可能重复
image.png
题目中给的abc是没有重复字母的,乍一看和上面一题一模一样,但是,第一次提交后发现,输入是”aab”时,重复的a会导致有重复的排列。需要去重。
去重的方法:

  1. 排序,把相同的字符都放在一起
  2. 在backtrack函数中,if(i>0&&array[i]==array[i-1]&&!used[i-1]) continue; 如果当前元素和前一个元素相同,且前一个元素没有被用到,那么跳过。(如果前一个a没有被用到现在的组合中,而使用了现在这个a,那就和使用了前一个a没用现在这个a的排列重复了)

    1. class Solution {
    2. public String[] permutation(String s) {
    3. char[] array=s.toCharArray();
    4. List<String> resList=new ArrayList<>();
    5. StringBuilder sb=new StringBuilder();
    6. boolean[] used=new boolean[array.length];
    7. Arrays.sort(array);
    8. backtrack(sb,resList,array,used);
    9. String[] result = resList.toArray(new String[resList.size()]);
    10. return result;
    11. }
    12. private void backtrack(StringBuilder sb,List<String> resList,char[] array,boolean[] used){
    13. if(sb.length()==array.length){
    14. resList.add(sb.toString());
    15. return;
    16. }
    17. for(int i=0;i<array.length;i++){
    18. if(used[i]) continue;
    19. if(i>0&&array[i]==array[i-1]&&!used[i-1]) continue;
    20. sb.append(array[i]);
    21. used[i]=true;
    22. backtrack(sb,resList,array,used);
    23. used[i]=false;
    24. sb.deleteCharAt(sb.length()-1);
    25. }
    26. }
    27. }

    131. 分割回文串

    题目链接:131. 分割回文串
    Screenshot_20210610_002532.jpg

    1. class Solution {
    2. public List<List<String>> partition(String s) {
    3. List<List<String>> res=new ArrayList<>();
    4. ArrayList<String> sub=new ArrayList<>();
    5. dfs(s,0,sub,res);
    6. return res;
    7. }
    8. private void dfs(String s,int start,ArrayList<String> sub,List<List<String>> res){
    9. if(start==s.length()){
    10. res.add(new ArrayList<>(sub));
    11. return;
    12. }
    13. for(int i=start;i<s.length();i++){
    14. String s1=s.substring(start,i+1);
    15. if(!isPalindrome(s1)) continue;
    16. sub.add(s1);
    17. dfs(s,i+1,sub,res);
    18. sub.remove(sub.size()-1);
    19. }
    20. }
    21. private boolean isPalindrome(String s){
    22. if(s==null||s.length()<2) return true;
    23. int l=0,r=s.length()-1;
    24. while(l<r){
    25. if(s.charAt(l++)!=s.charAt(r--)){
    26. return false;
    27. }
    28. }
    29. return true;
    30. }
    31. }

    用dp来记录,进一步提高效率(提高了一点点点)

    1. class Solution {
    2. public List<List<String>> partition(String s) {
    3. List<List<String>> res=new ArrayList<>();
    4. ArrayList<String> sub=new ArrayList<>();
    5. int len=s.length();
    6. boolean[][] dp=new boolean[len][len];
    7. for(int i=0;i<len;i++){
    8. isPalindrome(s,i,i,dp);
    9. isPalindrome(s,i,i+1,dp);
    10. }
    11. dfs(s,0,sub,res,dp);
    12. return res;
    13. }
    14. private void dfs(String s,int start,ArrayList<String> sub,List<List<String>> res,boolean[][] dp){
    15. if(start==s.length()){
    16. res.add(new ArrayList<>(sub));
    17. return;
    18. }
    19. for(int i=start;i<s.length();i++){
    20. if(!dp[start][i]) continue;
    21. sub.add(s.substring(start,i+1));
    22. dfs(s,i+1,sub,res,dp);
    23. sub.remove(sub.size()-1);
    24. }
    25. }
    26. private void isPalindrome(String s,int i,int j,boolean[][] dp){
    27. int len=s.length();
    28. while(i>=0&&j<len&&s.charAt(i)==s.charAt(j)){
    29. dp[i][j]=true;
    30. i--;
    31. j++;
    32. }
    33. }
    34. }

    有一道困难题,看起来和这个很类似,但其实不能用相同方法来解决,会超时。

    132. 分割回文串 II 困难

    132. 分割回文串 II
    使用动态规划,dp[i]表示s.substring(0,i)需要最少切割几次

    1. class Solution {
    2. public int minCut(String s) {
    3. if(s == null || s.length() <= 1)
    4. return 0;
    5. int len = s.length();
    6. int dp[] = new int[len];
    7. Arrays.fill(dp, len-1);
    8. for(int i = 0; i < len; i++){
    9. findMinCut(s , i , i , dp); // 奇数回文串以1个字符为中心
    10. findMinCut(s, i , i+1 , dp); // 偶数回文串以2个字符为中心
    11. }
    12. return dp[len-1];
    13. }
    14. private void findMinCut(String s, int i, int j, int[] dp){
    15. int len = s.length();
    16. while(i >= 0 && j < len && s.charAt(i) == s.charAt(j)){
    17. //以当前字符为中心的最大回文串左侧为i,右侧为j,那s[i]~s[j]长度是不需要切割的
    18. //只需要在s[i-1]处切一刀即可,即dp[i-1]+1。所以更新dp[j] = min(dp[j] , dp[i-1]+1)
    19. dp[j] = Math.min(dp[j] , (i==0?-1:dp[i-1])+1);
    20. i--;
    21. j++;
    22. }
    23. }
    24. }

93. 复原 IP 地址

题目:93. 复原 IP 地址
image.png
这个答案的效率不太好,但是是我自己想出来的,所以记录一下思路
有几个要点:
(1)当前的数字最短能有多短
比如:输入的数据为“25525511135”,一共是11位,在不知道具体数字的情况下,我们也能知道,最短可以是2位,如果4个数字中有一个是1位,那么就会出现数字多余的情况。
(2)当前的数字最长能有多长
比如:输入的数据为“1111”,一共是11位,在不知道具体数字的情况下,我们也能知道,最长只可以是1位,如果4个数字中有一个是2位,那么就会出现数字不够用的情况。
(3)转换成数字之前的格式校验
当时没注意到这一点,导致漏了很多0
比如”01”这个字符串是不符合要求的,虽然转换成int之后是1。转换之前需要进行校验。
(4)把结果加入resList的方法
一开始path我是用的List类型,把每个位置的数字存起来然后用下面这种愚蠢的办法进行拼接。之后看了大佬的答案,首先做了改进resList.add(String.join(“.”, path));

  1. if(nums.size()==4){
  2. String str=nums.get(0)+"."+nums.get(1)+"."+nums.get(2)+"."+nums.get(3);
  3. resList.add(str);
  4. return;
  5. }

可以看到优化了很多很多
image.png

  1. class Solution {
  2. public List<String> restoreIpAddresses(String s) {
  3. int len=s.length();
  4. if(len>12||len<4) return new ArrayList<>();
  5. List<String> resList=new ArrayList<>();
  6. List<String> path=new ArrayList<>();
  7. dfs(0,path,resList,s);
  8. return resList;
  9. }
  10. private void dfs(int start,List<String> path,List<String> resList,String s){
  11. if(path.size()==4){
  12. resList.add(String.join(".", path)); //优化了很多!!!
  13. return;
  14. }
  15. int len=minLen(start,path,s);
  16. int limit=maxLen(start, path, s);
  17. for(;len<=limit&&(start+len)<=s.length();len++){
  18. String str=s.substring(start,start+len);
  19. if(str.length()>1&&str.charAt(0)=='0') continue;
  20. int num=Integer.parseInt(str);
  21. if(num>=0&&num<=255){
  22. path.add(str);
  23. dfs(start+len,path,resList,s);
  24. path.remove(path.size()-1);
  25. }
  26. }
  27. }
  28. private int minLen(int start,List<String> path,String s){
  29. int used=path.size();
  30. int remainNumber=s.length()-start;
  31. int remainPos=4-used;
  32. if((remainNumber/3)==(remainPos-1)){
  33. return remainNumber%3==0?1:remainNumber%3;
  34. }else if((remainNumber/3)==remainPos){
  35. return 3;
  36. }else{
  37. return 1;
  38. }
  39. }
  40. private int maxLen(int start,List<String> path,String s){
  41. int used=path.size();
  42. int remainNumber=s.length()-start;
  43. int remainPos=4-used;
  44. int len=remainNumber-remainPos+1;
  45. return Math.min(3,len);
  46. }
  47. }

大佬的思路:
代码更简洁,但是效率没提升

  1. class Solution {
  2. public List<String> restoreIpAddresses(String s) {
  3. int len=s.length();
  4. if(len>12||len<4) return new ArrayList<>();
  5. List<String> resList=new ArrayList<>();
  6. List<String> path=new ArrayList<>();
  7. dfs(0,path,resList,s);
  8. return resList;
  9. }
  10. private void dfs(int start,List<String> path,List<String> resList,String s){
  11. if(path.size()==4){
  12. resList.add(String.join(".", path));
  13. return;
  14. }
  15. for(int len=1;len<=3&&(start+len)<=s.length();len++){
  16. String str=s.substring(start,start+len);
  17. if(str.length()>1&&str.charAt(0)=='0') continue;
  18. //不够了或有多余的
  19. //int remainNumber=s.length()-start-len;
  20. //int remainPos=4-path.size()-1;
  21. if((s.length()-start-len)<(3-path.size())||(s.length()-start-len)>(3-path.size())*3) continue;
  22. int num=Integer.parseInt(str);
  23. if(num>=0&&num<=255){
  24. path.add(str);
  25. dfs(start+len,path,resList,s);
  26. path.remove(path.size()-1);
  27. }
  28. }
  29. }
  30. }

剑指 Offer 12. 矩阵中的路径

链接:剑指 Offer 12. 矩阵中的路径
写的有点丑陋

  1. class Solution {
  2. int m,n;
  3. char[] c;
  4. boolean[][] visited;
  5. char[][] board;
  6. int[][] directions=new int[][]{{0,1},{0,-1},{1,0},{-1,0}};
  7. public boolean exist(char[][] board, String word) {
  8. this.board=board;
  9. m=board.length;
  10. n=board[0].length;
  11. c=word.toCharArray();
  12. visited=new boolean[m][n];
  13. for(int i=0;i<m;i++){
  14. for(int j=0;j<n;j++){
  15. if(board[i][j]==c[0]){
  16. visited[i][j]=true;
  17. if(backtrack(i,j,1)) return true;
  18. visited[i][j]=false;;
  19. }
  20. }
  21. }
  22. return false;
  23. }
  24. private boolean backtrack(int i,int j,int next){
  25. if(next==c.length) return true;
  26. for(int[] dir:directions){
  27. int x=i+dir[0];
  28. int y=j+dir[1];
  29. if(x>=0&&y>=0&&x<m&&y<n&&c[next]==board[x][y]&&!visited[x][y]){
  30. visited[x][y]=true;
  31. if(backtrack(x, y, next+1)) return true;
  32. visited[x][y]=false;
  33. }
  34. }
  35. return false;
  36. }
  37. }