回溯算法思想

回溯思想的总概

在这里插入图片描述

在这里插入图片描述

  • 另外在回溯算法思想过程中,要着重注意优化减枝,这是提高回溯算法效率的主要方式。
  • 常见的剪枝方法:改变树形结构、限制长度、限制数量、有时剪枝前需要进行排序

三种容易混淆的去重方式:

去重方式:牢记递归树的同一层上不能存在相同节点分支.

  • 全排序2:排序+isvisited数组—>从0开始for循环(i>0因为是for起始位置)(i>0&&nums[i]==nums[i-1]&&isvisited[i-1]==false)isvisited[i-1]==false是用来去掉同层相同节点分支点条件
  • 子集2:排序—>for从start开始for循环(i>start&&nums[i]==nums[i-1]) i>start是用来去掉同层相同分支,因为可以start时定可以取到,取到start后进行下一次for循环就会判断是否相同
  • 组合总和2:(存在重复元素):排序–>,for从start开始,(i>start&&nums[i]==nums[i-1]))i>start是用来去掉同层相同分支

leetcode22括号生成

在这里插入图片描述

在这里插入图片描述

  • 时间效率都差不多,不用咋剪枝。
class Solution {
	List<String> list = new ArrayList<String>();
	int N;
	StringBuffer s = new StringBuffer("");

	public List<String> generateParenthesis(int n) {
		N = n;
		process(0, 0);
		return list;
	}

	private void process(int i, int j) {
		if (j == N) {
			list.add(s.toString());
            return;
		}
		if (i < N) {
			s.append("(");
			process(i + 1, j);
			s.deleteCharAt(i + j);
		}
		if (j < i) {
			s.append(")");
			process(i, j + 1);
			s.deleteCharAt(i + j);
		}
	}
}

leetcode46全排列

在这里插入图片描述
在这里插入图片描述

  • 注意这是不含重复的
class Solution {
    List<List<Integer>> res;
    LinkedList<Integer> output;
    int n;
    boolean[] visited;

    public List<List<Integer>> permute(int[] nums) {
        res = new ArrayList<>();
        output = new LinkedList<>();
        n = nums.length;
        visited = new boolean[n];
        backtrack(nums, 0);
        return res;
    }

    public void backtrack(int[] nums, int first) {
        // 所有数都填完了
        if (first == n) {
            res.add(new ArrayList<>(output));
        }
        for (int i = 0; i < n; i++) {
            if (!visited[i]){
                visited[i]=true;
                // 动态维护数组
                output.addLast(nums[i]);
                // 继续递归填下一个数
                backtrack(nums, first + 1);
                // 撤销操作
                output.removeLast();
                visited[i]=false;
            }
            
        }
    }
}

leetcode47全排序2🔐

在这里插入图片描述
在这里插入图片描述
代码讲解戳这里

class Solution {
    //存放结果
    List<List<Integer>> result = new ArrayList<>();
    //暂存结果
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean[] used = new boolean[nums.length];
        Arrays.sort(nums);
        backTrack(nums, used);
        return result;
    }

    private void backTrack(int[] nums, boolean[] used) {
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }
        // 注意是从0开始的
        for (int i = 0; i < nums.length; i++) {
            // used[i - 1] == true,说明同⼀树⽀nums[i - 1]使⽤过
            // used[i - 1] == false,说明同⼀树层nums[i - 1]使⽤过
            // 如果同⼀树层nums[i - 1]使⽤过则直接跳过,满足跳过前定已经深搜过nums[i]了,只不过已经撤销了置为false了
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            //如果同⼀树⽀nums[i]没使⽤过开始处理
            if (used[i] == false) {
                used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树支重复使用
                path.addLast(nums[i]);
                backTrack(nums, used);
                path.removeLast();//回溯,说明同⼀树层nums[i]使⽤过,防止下一树层重复
                used[i] = false;//回溯
            }
        }
    }
}

leetcode39组合总和🔐

在这里插入图片描述
在这里插入图片描述

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    int N;
    int[] arr;

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        N = candidates.length;
        arr = candidates;
        process(0, target);
        return res;
    }

    private void process(int i, int target) {
        if (target == 0) {
            res.add(new ArrayList<>(list));
            return;
        }
        if (i == N || target < 0) return;
        int num = 0;
        for (; num * arr[i] <= target; ++num) {
            process(i + 1, target - num * arr[i]);
            list.add(arr[i]);
        }
        for (; num > 0; num--) {
            list.remove(list.size() - 1);
        }
    }
}

在这里插入图片描述

  • 宽度优先搜索,一次剪枝(必要),一次优化(提高效率)
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(candidates); // 先进行排序,优化:若当前+sum超过了target,后面的必然不可能
        backtracking(res, new ArrayList<>(), candidates, target, 0, 0);
        return res;
    }

    public void backtracking(List<List<Integer>> res, List<Integer> path, int[] candidates, int target, int sum, int idx) {
        // 找到了数字和为 target 的组合
        if (sum == target) {
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = idx; i < candidates.length; i++) {
            // 如果 sum + candidates[i] > target 就终止遍历
            if (sum + candidates[i] > target) break;
            path.add(candidates[i]);
            backtracking(res, path, candidates, target, sum + candidates[i], i);// i传值进行了剪枝,避免重复值出现
            path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
        }
    }
}

leetcode40组合总和 II🔐

在这里插入图片描述
在这里插入图片描述

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        backtracking(candidates, target, 0, 0);
        return res;
    }

    public void backtracking(int[] candidates, int target, int sum, int idx) {
        if (sum == target) {
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = idx; i < candidates.length; i++) {
            if (i > idx && candidates[i] == candidates[i - 1]) continue;
            if (sum + candidates[i] > target) break;
            path.addLast(candidates[i]);
            backtracking(candidates, target, sum + candidates[i], i + 1);
            path.removeLast();
        }
    }
}

leetcode216. 组合总和 III

在这里插入图片描述
在这里插入图片描述

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    int target, tarNum;

    public List<List<Integer>> combinationSum3(int k, int n) {
        if (n <= 0) return res;
        target = n;
        tarNum = k;
        backtrack(1, 0, 0);
        return res;
    }

    private void backtrack(int start, int num, int cur) {
        if (num == tarNum && cur == target) {
            res.add(new ArrayList<>(path));
            return;
        }
        if (num >= 3 || cur > target) return;
        for (int i = start; i <= 9; i++) {
            if (i * (tarNum - num) > target - cur) return;// 可能性剪枝
            path.addLast(i);
            backtrack(i + 1, num + 1, cur + i);
            path.removeLast();
        }
    }
}

leetcode78子集🔐

在这里插入图片描述

在这里插入图片描述

class Solution {
    List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
    LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
    public List<List<Integer>> subsets(int[] nums) {
        subsetsHelper(nums, 0);
        return result;
    }

    private void subsetsHelper(int[] nums, int startIndex){
        result.add(new ArrayList<>(path));//「遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合」。
        for (int i = startIndex; i < nums.length; i++){
            path.add(nums[i]);
            subsetsHelper(nums, i + 1);
            path.removeLast();
        }
    }
}

在这里插入图片描述

  • 一般深搜
  • 这种方法叶子节点为2^N
class Solution {
    List<List<Integer>> res = new LinkedList<>();
    LinkedList<Integer> list=new LinkedList<>();
    
    public List<List<Integer>> subsets(int[] nums) {
        process(nums, 0);
        return res;
    }

    private void process(int[] nums, int i) {
        if (i == nums.length) {
            res.add(new ArrayList<>(list));
            return;
        }
        process(nums, i + 1);
        list.addLast(nums[i]);
        process(nums, i + 1);
        list.removeLast();
    }
}

leetcode90子集2🔐

在这里插入图片描述
在这里插入图片描述

  • 相对于1,这个要进行排序去重
class Solution {
    List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
    LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);// 排序,准备去重
        subsetsHelper(nums, 0);
        return result;
    }

    private void subsetsHelper(int[] nums, int startIndex) {
        result.add(new ArrayList<>(path));//「遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合」。
        for (int i = startIndex; i < nums.length; i++) {
            if (i > startIndex && nums[i] == nums[i - 1]) continue;// 去重
            path.add(nums[i]);
            subsetsHelper(nums, i + 1);
            path.removeLast();
        }
    }
}

leetcode79单词搜索

在这里插入图片描述
在这里插入图片描述

class Solution {
    int M, N;

    public boolean exist(char[][] board, String word) {
        M = board.length;
        N = board[0].length;
        for (int i = 0; i < M; i++) {
            for (int j = 0; j < N; j++) {
                if (dfs(board, i, j, word, 0)) return true;
            }
        }
        return false;
    }

    private boolean dfs(char[][] board, int i, int j, String word, int index) {
        if (index == word.length()) return true;
        if (i < 0 || i >= M || j < 0 || j >= N || board[i][j] != word.charAt(index) || board[i][j] == ' ') return false;
        char temp = board[i][j];
        board[i][j] = ' ';// 防止回头
        boolean flag = false;
        flag |= dfs(board, i + 1, j, word, index + 1);
        flag |= dfs(board, i - 1, j, word, index + 1);
        flag |= dfs(board, i, j + 1, word, index + 1);
        flag |= dfs(board, i, j - 1, word, index + 1);
        board[i][j] = temp;
        return flag;
    }
}

leetcode212单词搜索2

在这里插入图片描述
在这里插入图片描述

class Solution {
    Set<String> set = new HashSet<>();
    List<String> ans = new ArrayList<>();
    char[][] board;
    int[][] dirs = new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    int n, m;
    boolean[][] vis = new boolean[15][15];

    public List<String> findWords(char[][] _board, String[] words) {
        board = _board;
        m = board.length;
        n = board[0].length;
        set.addAll(Arrays.asList(words));
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                vis[i][j] = true;
                sb.append(board[i][j]);
                dfs(i, j, sb);
                vis[i][j] = false;
                sb.deleteCharAt(sb.length() - 1);
            }
        }
        return ans;
    }

    void dfs(int i, int j, StringBuilder sb) {
        if (sb.length() > 10) return;
        if (set.contains(sb.toString())) {
            ans.add(sb.toString());
            set.remove(sb.toString());
        }
        for (int[] d : dirs) {
            int dx = i + d[0], dy = j + d[1];
            if (dx < 0 || dx >= m || dy < 0 || dy >= n) continue;
            if (vis[dx][dy]) continue;
            vis[dx][dy] = true;
            sb.append(board[dx][dy]);
            dfs(dx, dy, sb);
            vis[dx][dy] = false;
            sb.deleteCharAt(sb.length() - 1);
        }
    }
}

leetcode93复原 IP 地址🔐

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

class Solution {
    List<String> result = new ArrayList<>();

    public List<String> restoreIpAddresses(String s) {
        if (s.length() > 12) return result; // 算是剪枝了
        backTrack(s, 0, 0);
        return result;
    }

    // startIndex: 搜索的起始位置, pointNum:添加逗点的数量
    private void backTrack(String s, int startIndex, int pointNum) {
        if (pointNum == 3) {// 逗点数量为3时,分隔结束
            // 判断第四段⼦字符串是否合法,如果合法就放进result中
            if (isValid(s, startIndex, s.length() - 1)) {
                result.add(s);
            }
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            if (isValid(s, startIndex, i)) {
                if (s.length() - i - 1 <= (3 - pointNum) * 3)// 后面是否太长了
                    backTrack(s.substring(0, i + 1) + "." + s.substring(i + 1), i + 2, pointNum + 1);// 插⼊逗点之后下⼀个⼦串的起始位置为i+2
            } else {
                break;
            }
        }
    }


    // 判断字符串s在左闭⼜闭区间[start, end]所组成的数字是否合法
    private Boolean isValid(String s, int start, int end) {
        if (start > end || end - start > 2) {
            return false;
        }
        if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法
            return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法
                return false;
            }
            num = num * 10 + (s.charAt(i) - '0');
            if (num > 255) { // 如果⼤于255了不合法
                return false;
            }
        }
        return true;
    }
}

leetcode89格雷编码🔐

在这里插入图片描述
在这里插入图片描述

  • 经典回溯思想
class Solution {
    LinkedList<Integer> res = new LinkedList<>();
    boolean[] visited;

    public List<Integer> grayCode(int n) {
        visited = new boolean[1 << n];
        res.add(0);
        visited[0] = true;
        dfs(0, n);
        return res;
    }

    boolean dfs(int cur, int n) {
        if (res.size() == 1 << n) return true;
        for (int i = 0; i < n; i++) {
            int next = cur ^ (1 << i);
            if (!visited[next]) {
                res.addLast(next);
                visited[next] = true;
                if (dfs(next, n)) return true;
                res.removeLast();
                visited[next] = false;
            }
        }
        return false;
    }
}

在这里插入图片描述

  • 根据循环码变化规律编写代码
class Solution {
    List<Integer> res = new ArrayList<>();
    boolean[] visited;
    public List<Integer> grayCode(int n) {
        visited = new boolean[1 << n];
        dfs(0, n);
        return res;
    }
    boolean dfs(int cur, int n) {
        if (res.size() == (1 << n))
            return true;
        res.add(cur);
        visited[cur] = true;
        for (int i = 0; i < n; i++) {
            int next = cur ^ (1 << i); //这里改变cur的某一位,用异或
            if (!visited[next] && dfs(next, n))
                return true;
        }
        visited[cur] = false;
        return false;
    }
}

在这里插入图片描述

  • 格雷码生成原则
class Solution {
    public List<Integer> grayCode(int n) {
        /**
        关键是搞清楚格雷编码的生成过程, G(i) = i ^ (i/2);
        如 n = 3: 
        G(0) = 000, 
        G(1) = 1 ^ 0 = 001 ^ 000 = 001
        G(2) = 2 ^ 1 = 010 ^ 001 = 011 
        G(3) = 3 ^ 1 = 011 ^ 001 = 010
        G(4) = 4 ^ 2 = 100 ^ 010 = 110
        G(5) = 5 ^ 2 = 101 ^ 010 = 111
        G(6) = 6 ^ 3 = 110 ^ 011 = 101
        G(7) = 7 ^ 3 = 111 ^ 011 = 100
        **/
        List<Integer> ret = new ArrayList<>();
        for(int i = 0; i < 1<<n; ++i)
            ret.add(i ^ i>>1);
        return ret;
    }
}

leecode357计算各个位数不同的数字个数

在这里插入图片描述
在这里插入图片描述

class Solution {
    public int countNumbersWithUniqueDigits(int n) {
        //各位数字都不同。
        //来详解一下
        //dp[i]=dp[i-1]+(dp[i-1]-dp[i-2])*(10-(i-1));
        //  - 加上dp[i-1]没什么可说的,加上之前的数字
        //  - dp[i-1]-dp[i-2]的意思是我们上一次较上上一次多出来的各位不重复的数字。
        //              以n=3为例,n=2已经计算了0-99之间不重复的数字了,
        //          我们需要判断的是100-999之间不重复的数字,
        //          那也就只能用10-99之间的不重复的数在后面加上一位去组成三位数,
        //          而不能使用0-9之间的不重复的数,因为他们加上一位也组成不了3位数。
        //          而10-99之间不重复的数等于dp[2]-dp[1]。
        //  -  (10 - (i - 1))
        //      当i=2时,说明之前选取的数字只有1位,
        //      那么对于每一个数均有9种数字能与之匹配,
        //      dp[i-1]-dp[i-2]作为高位,我们只要与这些高位不重复即可,
        //      所以其实有9(10-1)种情况(比如十位是1,后面可以跟0,2,3,4,5,6,7,8,9)。
        //      当i=3时,说明之前选取的数字有2位,那么我们需要与2位不重复
        //      所以剩余的有8(10-2)种(比如12,后面可以跟0,3,4,5,6,7,8,9)
        if (n == 0)return 1;
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 10;
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + (dp[i - 1] - dp[i - 2]) * (10 - (i - 1));
        }
        return dp[n];
    }
}

在这里插入图片描述
在这里插入图片描述

class Solution {
    public int countNumbersWithUniqueDigits(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 1;
        int sum = 1;
        for (int i = 1; i <= n; i++) {
            dp[i] = 9 * sum + dp[i - 1];
            sum *= (10 - i);
        }
        return dp[n];
    }
}

leetcode131分割回文串

在这里插入图片描述
在这里插入图片描述

class Solution {
    List<List<String>> res = new ArrayList<>();
    LinkedList<String> path = new LinkedList<>();

    public List<List<String>> partition(String s) {
        dfs(s, 0);
        return res;
    }

    private void dfs(String s, int start) {
        if (start == s.length()) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = start; i < s.length(); i++) {
            String str = s.substring(start, i + 1);
            if (!isReserve(str)) continue;
            path.addLast(str);
            dfs(s, i + 1);
            path.removeLast();
        }
    }

    private boolean isReserve(String s) {
        if (s.length() <= 1) return true;
        int i = 0, j = s.length() - 1;
        while (i < j) {
            if (s.charAt(i++) != s.charAt(j--)) {
                return false;
            }
        }
        return true;
    }
}

leetcode140单词差分2

在这里插入图片描述
在这里插入图片描述

  • maxLen剪枝,在s较长的时候效果明显
class Solution {
    List<String> res = new ArrayList<>();
    int maxLen = 0;

    public List<String> wordBreak(String s, List<String> wordDict) {
        for (String str : wordDict) {
            maxLen = Math.max(maxLen, str.length());
        }
        dfs(s, wordDict, new LinkedList<>(), 0);
        return res;
    }

    public void dfs(String s, List<String> wordDict, LinkedList<String> path, int index) {
        if (index == s.length()) {
            res.add(String.join(" ", path));
            return;
        }
        for (int i = index; i < s.length(); i++) {
            if (wordDict.contains(s.substring(index, i + 1))) {
                path.addLast(s.substring(index, i + 1));
                dfs(s, wordDict, path, i + 1);
                path.removeLast();
            } else if (i - index + 1 >= maxLen) {
                break;
            }
        }
    }
}

leetcode77组合

在这里插入图片描述
在这里插入图片描述

  • 进行了数量限制的剪枝方式
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> combine(int n, int k) {
        dfs(1, 0, n, k);
        return res;
    }

    private void dfs(int start, int num, int n, int k) {
        if (num == k) {
            res.add(new ArrayList<>(path));
            return;
        } else if (start > n) return;
        for (int i = start; i <= n; i++) {
            path.addLast(i);
            dfs(i + 1, num + 1, n, k);
            path.removeLast();
            if (k - num > n - i) {// 数量剪枝
                return;
            }
        }
    }
}

leeetcode10正则表达式匹配

在这里插入图片描述
在这里插入图片描述

class Solution {
    int M, N;
    String s, p;

    public boolean isMatch(String s, String p) {
        M = s.length();
        N = p.length();
        if (N == 0) return false;
        this.s = s;
        this.p = p;
        return process(0, 0);
    }

    private boolean process(int i, int j) {
        if (j == N) return i == M;
        boolean is = !(i == M) && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.');
        if (j < N - 1 && p.charAt(j + 1) == '*') {
            return process(i, j + 2) || (is && process(i + 1, j));
        } else {
            return is && process(i + 1, j + 2);
        }
    }
}

leetcode95. 不同的二叉搜索树 II🔐

在这里插入图片描述
在这里插入图片描述

class Solution {
    public List<TreeNode> generateTrees(int n) {
        if (n == 0) {
            return new LinkedList<>();
        }
        return generateTrees(1, n);
    }

    public List<TreeNode> generateTrees(int start, int end) {
        List<TreeNode> allTrees = new LinkedList<>();
        if (start > end) {
            allTrees.add(null);
            return allTrees;
        }

        // 枚举可行根节点,根据i的取值作为根节点不同,树的形状也随之改变,从而获得所有答案
        for (int i = start; i <= end; i++) {
            // 获得所有可行的左子树集合
            List<TreeNode> leftTrees = generateTrees(start, i - 1);

            // 获得所有可行的右子树集合
            List<TreeNode> rightTrees = generateTrees(i + 1, end);

            // 从左子树集合中选出一棵左子树,从右子树集合中选出一棵右子树,拼接到根节点上
            // 让i作为根节点,左右两个集合可以自由任意组合形成新的组合树。i的取值范围[start,end]
            for (TreeNode left : leftTrees) {
                for (TreeNode right : rightTrees) {
                    TreeNode currTree = new TreeNode(i);
                    currTree.left = left;
                    currTree.right = right;
                    allTrees.add(currTree);
                }
            }
        }
        return allTrees;//根节点集合
    }
}

leetcode113. 路径总和 II

在这里插入图片描述
在这里插入图片描述

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        if (root == null) return res;
        path.addLast(root.val);
        backtrack(root, root.val, targetSum);
        return res;
    }

    private void backtrack(TreeNode root, int cur, int targetSum) {
        if (root.left == null && root.right == null) {// 叶子节点的标志
            if (cur == targetSum) res.add(new ArrayList<>(path));
            return;
        }
        if (root.left != null) {// 不是叶子节点,存在左子节点
            path.addLast(root.left.val);
            backtrack(root.left, cur + root.left.val, targetSum);
            path.removeLast();
        }
        if (root.right != null) {// 不是叶子节点,存在右子节点
            path.addLast(root.right.val);
            backtrack(root.right, cur + root.right.val, targetSum);
            path.removeLast();
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「 25' h 」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值