1. 程式人生 > 其它 >儲存載入模型model.save()

儲存載入模型model.save()

本文參考:程式碼隨想錄

回溯演算法能解決如下問題:

  • 組合問題:N個數裡面按一定規則找出k個數的集合
  • 排列問題:N個數按一定規則全排列,有幾種排列方式
  • 切割問題:一個字串按一定規則有幾種切割方式
  • 子集問題:一個N個數的集合裡有多少符合條件的子集
  • 棋盤問題:N皇后,解數獨等等

回溯演算法的模板:

void backtracking(引數) {
    if (終止條件) {
        存放結果;
        return;
    }

    for (選擇:本層集合中元素(樹中節點孩子的數量就是集合的大小)) {
        處理節點;
        backtracking(路徑,選擇列表); 
// 遞迴 回溯,撤銷處理結果 } }

型別一:組合問題

1. 給定兩個整數 n 和 k,返回 1 ... n 中所有可能的 k 個數的組合。

示例:
輸入:n = 4, k = 2
輸出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

本題這是回溯法的經典題目。

直接的解法當然是使用for迴圈,例如示例中k為2,很容易想到 用兩個for迴圈,這樣就可以輸出 和示例中一樣的結果。

int n = 4;
        for (int i = 1; i <= n; i++) {
            for (int j = i + 1; j <= n; j++) {
                System.out.println( i 
+ " " + j); } }

k=2 需要兩層迴圈,但k=50,那這。。。

使用回溯演算法:

結合上圖說下大體思想,回溯與DFS遞迴演算法結合,dfs有引數begin表示開始的位置,也就是選中的值,從1到 n 。每次遞迴執行dfs就會把新的begin儲存到 Deque<Integer> path 也就是雙向佇列之中。因為數字不能重複,第一個數字選擇1,下一次dfs就要從2開始。在本題中取完[1,2],path長度與k相等,我們的得到第一種結果[1,2]。這是我們想得到下一種情況,就需要在遞迴語句執行後(本次遞迴中已經把這種情況寫入ans中)把2吐出來,方便下次得到[1,3]這就是回溯。

public class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> res = new ArrayList<>();
        if (k <= 0 || n < k) {
            return res;
        }
        // 從 1 開始是題目的設定
        Deque<Integer> path = new ArrayDeque<>();
        dfs(n, k, 1, path, res);
        return res;
    }
    private void dfs(int n, int k, int begin, Deque<Integer> path, List<List<Integer>> res) {
        // 遞迴終止條件是:path 的長度等於 k
        if (path.size() == k) {
            res.add(new ArrayList<>(path));
            return;
        }
        // 遍歷可能的搜尋起點
        for (yinweiwomenint i = begin; i <= n; i++) {
            // 向路徑變數裡新增一個數
            path.addLast(i);
            // 下一輪搜尋,設定的搜尋起點要加 1,因為組合數理不允許出現重複的元素
            dfs(n, k, i + 1, path, res);
            // 重點理解這裡:深度優先遍歷有回頭的過程,因此遞迴之前做了什麼,遞迴之後需要做相同操作的逆向操作
            path.removeLast();
        }
    }
}

組合問題型別二:

給定一個無重複元素的陣列 candidates 和一個目標數 target ,找出 candidates 中所有可以使數字和為 target 的組合。

candidates 中的數字可以無限制重複被選取。

說明:

  • 所有數字(包括 target)都是正整數。
  • 解集不能包含重複的組合。

示例 1:
輸入:candidates = [2,3,6,7], target = 7,
所求解集為:
[
[7],
[2,2,3]
]

示例 2:
輸入:candidates = [2,3,5], target = 8,
所求解集為:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

本題沒有數量要求,元素可以重複,所以推出條件target<=0即可。同時執行下次遞迴式,從本次元素開始表示可重複。

public class Solution {

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        int len = candidates.length;
        List<List<Integer>> res = new ArrayList<>();
        if (len == 0) {
            return res;
        }

        Deque<Integer> path = new ArrayDeque<>();
        dfs(candidates, 0, len, target, path, res);
        return res;
    }

    /**
     * @param candidates 候選陣列
     * @param begin      搜尋起點
     * @param len        冗餘變數,是 candidates 裡的屬性,可以不傳
     * @param target     每減去一個元素,目標值變小
     * @param path       從根結點到葉子結點的路徑,是一個棧
     * @param res        結果集列表
     */
    private void dfs(int[] candidates, int begin, int len, int target, Deque<Integer> path, List<List<Integer>> res) {
        // target 為負數和 0 的時候不再產生新的孩子結點
        if (target < 0) {
            return;
        }
        if (target == 0) {
            res.add(new ArrayList<>(path));
            return;
        }

        // 重點理解這裡從 begin 開始搜尋的語意
        for (int i = begin; i < len; i++) {
            path.addLast(candidates[i]);

            // 注意:由於每一個元素可以重複使用,下一輪搜尋的起點依然是 i,這裡非常容易弄錯
            dfs(candidates, i, len, target - candidates[i], path, res);

            // 狀態重置
            path.removeLast();
        }
    }
}

組合總和型別三:

給定一個數組 candidates 和一個目標數 target ,找出 candidates 中所有可以使數字和為 target 的組合。

candidates 中的每個數字在每個組合中只能使用一次。

說明:
所有數字(包括目標數)都是正整數。
解集不能包含重複的組合。

示例1:
輸入: candidates =[10,1,2,7,6,1,5], target =8,
所求解集為:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

示例2:
輸入: candidates =[2,5,2,1,2], target =5,
所求解集為:
[
[1,2,2],
[5]
]

本題不能有重複的結果,比如示例二有三個二,不能說結果裡面有三組[1,2,2],完了你告訴我說這三個二不一樣。

我們要往遞迴樹那裡考慮,先把元素排序,三個二連著,這三組[1,2,2]對應什麼樣的樹呢,

通過上圖分析,同排集合相同,最左側相同集合([1,2])的子樹會完全包含右側相同集合([1,2])的子樹,最左側[1,2]及右側子節點形成的子樹與中間[1,2]生成子樹完全一致。

所以如果遞迴到元素2這裡時,只要判斷2是不是所有2中開頭的2就可以了,不是的話直接continue;這種情況叫做“樹層去重”,會經常遇到。

public class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> res=new ArrayList<>();
        if(target<=0||candidates.length<=0)return res;
        Deque<Integer> path=new ArrayDeque<>();
        Arrays.sort(candidates);
        dfs(candidates,target,0,path,res);
        return res;

    }

    private void dfs(int[] candidates, int target, int begin, Deque<Integer> path, List<List<Integer>> res) {
        if(target<=0||begin==candidates.length){
            if(target==0){
                res.add(new ArrayList(path));
            }
            return;
        }
        for(int i=begin;i<candidates.length;i++){
            if(i>begin&&candidates[i]==candidates[i-1])
                continue;
            path.addLast(candidates[i]);
            dfs(candidates,target-candidates[i],i+1,path,res);
            path.removeLast();
        }
    }

}

切割問題:

給定一個字串 s,將 s 分割成一些子串,使每個子串都是迴文串。

返回 s 所有可能的分割方案。

示例:
輸入:"aab"
輸出:
[
["aa","b"],
["a","a","b"]
]

切割問題與組合問題十分類似,對比一下:

  • 組合問題:選取一個a之後,在bcdef中再去選取第二個,選取b之後在cdef中在選取第三個.....。
  • 切割問題:切割一個a之後,在bcdef中再去切割第二段,切割b之後在cdef中在切割第三段.....。

切割線的體現:

在處理組合問題的時候,遞迴引數需要傳入startIndex,表示下一輪遞迴遍歷的起始位置,這個startIndex就是切割線。

我們需要單獨寫個方法判斷現在切出來的是否是迴文串,就是把最前和最後比較,然後依次向內來一位。不一致就false。

public class Solution {
    private boolean checkPalindrome(String s,int left,int right){
        while(left<right){
            if(s.charAt(left)!=s.charAt(right)){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

    public List<List<String>> partition(String s) {
        List<List<String>> ans=new ArrayList<>();
        if(s.length()==0)return ans;
        Deque<String> path=new ArrayDeque<>();
        dfs(s,0,path,ans);
        return ans;

    }

    private void dfs(String s, int begin, Deque<String> path, List<List<String>> ans) {
        if(begin==s.length()){
            ans.add(new ArrayList<>(path));
            return;
        }
        for(int i=begin;i<s.length();i++){
            if(!checkPalindrome(s,begin,i))
                continue;
            path.addLast(s.substring(begin,i+1));
            dfs(s,i+1,path,ans);
            path.removeLast();
        }
    }
}

子集問題:

子集型別一:

給定一組不含重複元素的整數陣列 nums,返回該陣列所有可能的子集(冪集)。

說明:解集不能包含重複的子集。

示例: 輸入: nums = [1,2,3]
輸出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]

如果子集問題和切割問題是要遞迴樹的葉節點,那麼子集問題就是要所有節點(去重)

普通的去重就是再for迴圈中從startIndex開始就可以了。

public class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> ans=new ArrayList<>();
        if(nums.length==0)return ans;
        Deque<Integer> path=new ArrayDeque<>();
        dfs(nums,0,path,ans);
        return ans;
    }

    private void dfs(int[] nums, int begin, Deque<Integer> path, List<List<Integer>> ans) {
        ans.add(new ArrayList(path));
        for(int i=begin;i<nums.length;i++){
            path.addLast(nums[i]);
            dfs(nums,i+1,path,ans);
            path.removeLast();
        }
    }
}

子集型別二:

給定一個可能包含重複元素的整數陣列 nums,返回該陣列所有可能的子集(冪集)。

說明:解集不能包含重複的子集。

示例:
輸入: [1,2,2]
輸出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

集合本身有重複元素,要求不同有重複子集,就是之前遇到的普通降重+樹層去重就可以滿足了,不要忘了先把結合排序。

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> ans=new ArrayList<>();
        if(nums.length==0)return ans;
        Deque<Integer> path=new ArrayDeque<>();
        Arrays.sort(nums);
        dfs(nums,0,path,ans);
        return ans;
    }

    private void dfs(int[] nums, int begin, Deque<Integer> path, List<List<Integer>> ans) {
        ans.add(new ArrayList(path));
        for(int i=begin;i<nums.length;i++){
            if(i>begin&&nums[i]==nums[i-1])
                continue;
            path.addLast(nums[i]);
            dfs(nums,i+1,path,ans);
            path.removeLast();
        }
    }
}

遞增子序列:

示例:

輸入: [4, 6, 7, 7] 輸出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

說明:

  • 給定陣列的長度不會超過15。
  • 陣列中的整數範圍是[-100,100]。
  • 給定陣列中可能包含重複數字,相等的數字應該被視為遞增的一種情況。

本體最大的問題在於給的陣列是沒有排序的,我們之前的排序後樹層去重解決不了相同元素不在一起的情況,比如[1,2,3,1,1,1]

所以在每一層都設定一個set,專門去除重複元素。

class Solution {
    // 定義全域性變數儲存結果
    List<List<Integer>> res = new ArrayList<>();
    
    public List<List<Integer>> findSubsequences(int[] nums) {
        // idx 初始化為 -1,開始 dfs 搜尋。
        dfs(nums, -1, new ArrayList<>());
        return res;
    }

    private void dfs(int[] nums, int idx, List<Integer> curList) {
        // 只要當前的遞增序列長度大於 1,就加入到結果 res 中,然後繼續搜尋遞增序列的下一個值。
        if (curList.size() > 1) {
            res.add(new ArrayList<>(curList));
        }

        // 在 [idx + 1, nums.length - 1] 範圍內遍歷搜尋遞增序列的下一個值。
        // 藉助 set 對 [idx + 1, nums.length - 1] 範圍內的數去重。
        Set<Integer> set = new HashSet<>();
        for (int i = idx + 1; i < nums.length; i++) {
            // 1. 如果 set 中已經有與 nums[i] 相同的值了,說明加上 nums[i] 後的所有可能的遞增序列之前已經被搜過一遍了,因此停止繼續搜尋。
            if (set.contains(nums[i])) { 
                continue;
            }
            set.add(nums[i]);
            // 2. 如果 nums[i] >= nums[idx] 的話,說明出現了新的遞增序列,因此繼續 dfs 搜尋(因為 curList 在這裡是複用的,因此別忘了 remove 哦)
            if (idx == -1 || nums[i] >= nums[idx]) {
                curList.add(nums[i]);
                dfs(nums, i, curList);
                curList.remove(curList.size() - 1);
            }
        }
    }
}

排序問題:

排序而言,[1,2]和[2,1]是兩種情況,此時遞迴樹長這樣:

排序問題一:

給定一個 沒有重複 數字的序列,返回其所有可能的全排列。

示例:
輸入: [1,2,3]
輸出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]