[Leetcode][DP]动态规划相关题目汇总/分析/总结

  1. Unified Dimensional

  2. Two-dimensional

  3. Three-dimensional


[Unified Dimensional]

#70 Climbing Stairs
  • Sol
    public int climbStairs(int n) {
        if(n <= 2) return n;
        int i1 = 1, i2 = 2;
        for(int i = 3; i <= n; i++){
            int tmp = i1;
            i1 = i2;
            i2 = tmp + i1;
        }
        return i2;
    }
#91 Decode Ways
  • Sol
    case: 含有10
public int numDecodings(String s) {
        char[] ch = s.toCharArray();
        if(ch[0] == '0') return 0;
        if(ch.length < 2) return 1;
        int n = s.length();
        int i1 = 1, i2 = (ch[1] == 0 ? 0 : 1);
        int var = (ch[0]-'0') * 10 + (ch[1] - '0');
        if(var%10==0) i2 = 0;
        if(var >= 10 && var <= 26) i2++;
        for(int i = 2; i < n; i++){
            int tmp = (ch[i] == 0 ? 0 : i2);
            var = (ch[i-1]-'0') * 10 + (ch[i]-'0');
            if(var%10==0) tmp = 0;
            if(var >= 10 && var <= 26) tmp += i1;
            i1 = i2;
            i2 = tmp;
         }
        return i2;
    }
#120 Triangle
  • Sol Bottom-up
    public int minimumTotal(List<List<Integer>> triangle) {
        int row = triangle.size();
        int last_row_col = triangle.get(row-1).size();
        int[] dp = new int[last_row_col];
        for(int i = 0; i < last_row_col; i++){
            dp[i] = triangle.get(row-1).get(i);
        }
        for(int i = row-2; i >= 0; i--){
            for(int j = 0; j <= i; j++){
                dp[j] = triangle.get(i).get(j) + Math.min(dp[j], dp[j+1]);
            }
        }
        return dp[0];
        
    }
#139 Word Break
  • Sol
    public boolean wordBreak(String s, List<String> wordDict) {
        int n = s.length();
        boolean[] dp = new boolean[n+1];
        dp[0] = true;
        for(int i = 1; i <= n; i++){
            for(int j = 0; j < i; j++){
                String sub = s.substring(j, i);
                if(wordDict.contains(sub) && dp[j]){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[n];
    }
#140 Word Break II
  • Sol right-left
Map<Integer, List<String>> map; //index, res
    Set<String> dict;
    public List<String> wordBreak(String s, List<String> wordDict) {
        map = new HashMap<>();
        dict = new HashSet<>();
        for(String sh: wordDict) dict.add(sh);
        return help(s, s.length());
    }
    public List<String> help(String s, int end){
        List<String> res = new ArrayList<>();
        if(end == 0){
            res.add("");
            return res;
        }
        if(map.containsKey(end)) return map.get(end);
        for(int i = 0; i < end; i++){
            String sub = s.substring(i, end);
            if(dict.contains(sub)){
                List<String> next = help(s, i);
                for(String str: next){
                    res.add((str.length()==0)?sub : str + " " + sub);
                }
            }
        }
        map.put(end, res);
        return res;
    }
#198 House Robber
  • Sol
public int rob(int[] nums) {
        if(nums.length == 0) return 0;
        if(nums.length == 1) return nums[0];
        if(nums.length == 2) return Math.max(nums[0], nums[1]);
        int i1 = nums[0], i2 = Math.max(nums[0], nums[1]);
        int n = nums.length;
        for(int i = 2; i < n; i++){
            int tmp = Math.max(i1 + nums[i], i2);
            i1 = i2; 
            i2 = tmp;
        }
        return i2;
    }
#213 House Robber II
  • Sol
public int rob(int[] nums) {
        if(nums.length == 0) return 0;
        if(nums.length == 1) return nums[0];
        if(nums.length == 2) return Math.max(nums[0], nums[1]);
        int a = help(nums, 0, nums.length-1);
        int b = help(nums, 1, nums.length-1);
        return Math.max(a, b);
    }
    public int help(int[] nums, int s, int len) {
        if(len == 2) return Math.max(nums[s], nums[s+1]);
        int i1 = nums[s], i2 = Math.max(nums[s], nums[s+1]);
        for(int i = s+2; i < s+len; i++){
            int tmp = Math.max(i1 + nums[i], i2);
            i1 = i2; 
            i2 = tmp;
        }
        return i2;
    }
#279 Perfect Squares
  • Sol DP
    dp[0] = {0} = 1
    dp[1] = {1} = 1
    dp[2] = {1, 1} = 2
    dp[3] = {1, 1, 1} = 3
    dp[4] = {4} = 1
    dp[5] = {4, 1} = 2
    dp[n] = Min(1+ dp[n - i * i]) for i * i <= n && i = 1,2...n
    public int numSquares(int n) {
        int[] dp = new int[n+1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j*j <= i; j++){
                dp[i] = Math.min(dp[i], 1 + dp[i-j*j]);
            }
        }
        return dp[n];
    }
#263 Ugly Number

easy

  • Sol
public boolean isUgly(int num) {
        if(num <= 0) return false;
        if(num == 1)  return true;
        while(num % 2 == 0) num /= 2;
        while(num % 3 == 0) num /= 3;
        while(num % 5 == 0) num /= 5;
        return num == 1;
    }
#264 Ugly Number II
  • Sol
    x=y=z=1; k=1
    k is ugly number: k = 2x + 3y + 5z;
    dp[++k] = min(2x, 3y, 5z)
    更新xyz
public int nthUglyNumber(int n) {
        int p2 = 1, p3 = 1, p5 = 1;
        int[] dp = new int[n+1];
        dp[1] = 1;
        for(int i = 2; i <= n; i++){
            dp[i] = Math.min(Math.min(2 * dp[p2], 3 * dp[p3]), 5 * dp[p5]);
            if(dp[i] == 2 * dp[p2]) p2++;
            if(dp[i] == 3 * dp[p3]) p3++;
            if(dp[i] == 5 * dp[p5]) p5++;
        }
        return dp[n];
    }

[Two-dimensional]

#516 Longest Palindromic Subsequence
  • Sol
    dp[i][j] denotes s[i...j] longest length of the Palindromic Subsequence.
    base case: dp[i][i] = 1
    if s.charAt(i) == s.charAt(j)
    dp[i][j] = 2 + dp[i+1][j-1]
    else dp[i][j] = max(dp[i + 1][j], dp[i][j-1])
    return dp[0][len-1]

i 从后往前,j从i往后

public int longestPalindromeSubseq(String s) {
        int n = s.length();
        int[][] dp = new int[n][n];
        for(int i = 0; i < n; i++) dp[i][i] = 1;
        for(int i = n-1; i >= 0; i--){
            for(int j = i+1; j < n; j++){
                if(s.charAt(i) == s.charAt(j))
                    dp[i][j] = 2 + dp[i+1][j-1];
                else
                    dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
            }
        }
        return dp[0][n-1];
    }
#97 Interleaving String
  • Sol
    boolean dp[i][j] denotes whether s1[0..i] and s2[0...j] make the string s3[0..i+j]
    base case:
    dp[0][0] = T
    dp[0][j] = (s2[j-1] == s3[i+j-1] && dp[0][j-1])
    dp[i][0] = (s1[i-1] == s3[i+j-1] && dp[i-1][0])
    Iterative:
    if(s1[i] != s3[i+j] && s2[j] != s3[i+j]) return false
    else if(s1[i-1] == s3[i+j-1]) dp[i][j] = dp[i-1][j]
    else if(s2[j-1] == s3[i+j-1]) dp[i][j] = dp[i][j-1]
public boolean isInterleave(String s1, String s2, String s3) {
        if (s3.length() != s1.length() + s2.length()) {
            return false;
        }
        int n1 = s1.length(), n2 = s2.length();
        boolean[][] dp = new boolean[n1+1][n2+1];
        //dp[0][0] = true;
        for(int i = 0; i <= n1; i++){
            for(int j = 0; j <= n2; j++){
                if(i == 0 && j == 0)
                    dp[0][0] = true;
                else if(i == 0)
                    dp[0][j] = dp[0][j-1] && s2.charAt(j-1) == s3.charAt(j-1);
                else if(j == 0)
                    dp[i][0] = dp[i-1][0] && s1.charAt(i-1) == s3.charAt(i-1);
                else
                    dp[i][j] = dp[i][j-1] && s2.charAt(j-1) == s3.charAt(i + j-1)
                    || dp[i-1][j] && s1.charAt(i-1) == s3.charAt(i + j-1);
            }
        }
        return dp[n1][n2];
    }
#62 Unique Paths
  • Sol
    dp[i][j] denotes the unique path from (0,0) to (i,j)
    --base case:
    dp[0][0] = 1;
    dp[0][j] = 1;
    dp[i][0] =1;
    --Iterative:
    dp[i][j] = dp[i-1][j] + dp[i][j-1]
    --return
    dp[n-1][m-1]
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[n][m];
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(i == 0 || j == 0) dp[i][j] = 1;
                else dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[n-1][m-1];
    }
#63 Unique Paths II
  • Sol
    dp[i][j] denotes the unique path from (0,0) to (i,j)
    --base case:
    dp[0][0] = obstacleGrid[0][j] == 1 ? 0 : 1;
    dp[0][j] = (obstacleGrid[0][j] == 1 ? 0 : dp[0][j-1]);
    dp[i][0] = (obstacleGrid[i][0] == 1 ? 0 : dp[i-1][0]);
    --Iterative:
    dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : dp[i-1][j] + dp[i][j-1]
    --return
    dp[n-1][m-1]
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int n = obstacleGrid.length;
        int m = obstacleGrid[0].length;
        int[][] dp = new int[n][m];
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(i==0 && j == 0) dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : 1;
                else if(i == 0) dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : dp[i][j-1];
                else if(j == 0) dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : dp[i-1][j];
                else dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[n-1][m-1];
    }
#64 Minimum Path Sum
  • Sol
    dp[i][j] denotes the Minimum Path Sum from (0,0) to (i,j)
    --base case:
    dp[0][0] = grid[i][j];
    dp[0][j] = dp[0][j-1] + grid[0][j];
    dp[i][0] = dp[i-1][0] + + grid[i][0];
    --Iterative:
    dp[i][j] = Min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
    --return
    dp[n-1][m-1]
public int minPathSum(int[][] grid) {
        int n = grid.length;
        int m = grid[0].length;
        int[][] dp = new int[n][m];
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(i==0 && j==0) dp[i][j] = grid[i][j];
                else if(i == 0) dp[i][j] = dp[0][j-1] + grid[0][j];
                else if(j == 0) dp[i][j] = dp[i-1][0] + grid[i][0];
                else dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
            }
        }
        return dp[n-1][m-1];
    }
#72 Edit Distance
  • Sol
    dp[i][j] denotes the edit distance from word1[0...i) and word2[0...j)
    --base case:
    dp[0][0] = 0;
    dp[0][j] = j;
    dp[i][0] = i;
    --Iterative:
    if(word1[i-1] == word2[j-1])
    dp[i][j] = dp[i-1][j-1]
    else
    dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1;
    --return
    dp[n][m]
public int minDistance(String word1, String word2) {
        int n = word1.length();
        int m = word2.length();
        int[][] dp = new int[n+1][m+1];
        for(int i = 0; i <= n; i++){
            for(int j = 0; j <= m; j++){
                if(i == 0) dp[i][j] = j;
                else if(j == 0) dp[i][j] = i;
                else if(word1.charAt(i-1) == word2.charAt(j-1))
                    dp[i][j] = dp[i-1][j-1];
                else
                    dp[i][j] = 1 + Math.min(Math.min(dp[i-1][j-1], dp[i][j-1]), dp[i-1][j]);
            }
        }
        return dp[n][m];
    }
#10 Regular Expression Matching
  • Sol
    dp[i][j] denotes whether there is a Regular Expression Matching between s[i...) and word2[j...)
    --base case:
    dp[n][m] = true
    --Iterative:
    pre_check = word2[j] == word1[i] || word2[j] == "."
    if(word2[j+1] != "*") dp[i][j] = pre_check && dp[i+1][j+1];
    else dp[i][j] = pre_check && dp[i+1][j] || dp[i][j+2];
    --return:
    dp[0][0]
public boolean isMatch(String s, String p) {
        int n = s.length();
        int m = p.length();
        boolean[][] dp = new boolean[n+1][m+1];
        dp[n][m] = true;
        for(int i = n; i >= 0; i--){
            for(int j = m-1; j >= 0; j--){
                boolean pre = i < n && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.');
                if(j+1 < m && p.charAt(j+1) == '*')
                    dp[i][j] = dp[i][j+2] || pre && dp[i+1][j];
                else
                    dp[i][j] = pre && dp[i+1][j+1];
            }
        }
        return dp[0][0];
    }
#221 Maximal Square
  • Sol
    dp[i][j] denotes the side length of the maximum square which the right bottom corner is(i,j)
    --base case:
    dp[0][0] = matrix[i][j]
    dp[0][j] = matrix[i][j]
    dp[i][0] =matrix[i][j]
    --Iterative:
    dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
    max_area = max(max_area, dp[i][j] ^2)
    --return:
    renew the largest square while iterative
    max_area
public int maximalSquare(char[][] matrix) {
        int n = matrix.length;
        int m = n == 0 ? 0 : matrix[0].length;
        int[][] dp = new int[n][m];
        int max_area = 0;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(i == 0 || j == 0) dp[i][j] = matrix[i][j] - 48;
                else if(matrix[i][j] == '1'){
                    dp[i][j] = Math.min(Math.min(dp[i-1][j], dp[i][j-1]), dp[i-1][j-1]) + 1;
                }
                max_area = Math.max(dp[i][j] * dp[i][j], max_area);
            }
        }
        return max_area;
    }
#322 Coin Change
  • Sol
    dp[i] denotes the minimum number of coins that make up number i
    --base case:
    dp[0] = 0;
    --iterative:
    dp[i] = min(dp[i-c]) + 1 for c in coin[];
    --return
    dp[n-1]
public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount+1];
        Arrays.fill(dp, amount+1);
        dp[0] = 0;
        for(int i = 1; i <= amount; i++){
            for(int coin : coins){
                if(coin <= i){
                    dp[i] = Math.min(dp[i-coin] + 1, dp[i]);
                } 
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
#787 Cheapest Flights Within K Stops
  • Sol
    dp[i][j] denotes the Cheapest Flights from start to j within i stops
    --base case:
    dp[i][src] = 0
    --iterative:
    for i in (1...k+1]
    for each flight f
    dp[i][f[1]] = min(dp[i][f[1]], dp[i-1][f[0]] + f[2])
    --return
    dp[k+1][dst]
    public int findCheapestPrice(int n, int[][] flights, int src, int dst, int K) {
        double[][] dp = new double[K+2][n];
        for(double[] row:dp) Arrays.fill(row, 1e9);
        dp[0][src] = 0;
        for(int i = 1; i <= K+1; i++){
            dp[i][src] = 0;
            for(int j = 0; j < flights.length; j++){
                dp[i][flights[j][1]] = Math.min(dp[i][flights[j][1]], dp[i-1][flights[j][0]] + flights[j][2]);
            }
            
        }
        return dp[K+1][dst] >= 1e9? -1 : (int)dp[K+1][dst];
    }

double因为Integer会有错误

#1035 Uncrossed Lines
  • Sol
    dp[i][j] denotes the number of Uncrossed Lines between A[0...i-1] and B[0...j-1]
    --base case
    dp[0][0] = 0
    dp[0][j] = 0
    dp[i][0] = 0
    --iterative:
    if(A[i] == B[j]) dp[i][j] = dp[i-1][j-1] + 1
    else dp[i][j] = MAX(dp[i-1][j], dp[i][j-1])
    --return
    dp[n][m]
public int maxUncrossedLines(int[] A, int[] B) {
        int n = A.length, m = B.length;
        int[][] dp = new int[n+1][m+1];
        for(int i = 0; i <= n; i++){
            for(int j = 0; j <= m; j++){
                if(i == 0 || j == 0) dp[i][j] = 0;
                else{
                    if(A[i-1] == B[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
                    else dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }
        return dp[n][m];
    }

[Three-dimensional]

#87 Scramble String
  • Sol
    dp[i][j][k] denotes whether s2[j...j+k] is the scramble string of s1[i...i+k]. (k is the length)
    --base case:
    dp[i][j][0] = true
    dp[i][j][1] = s1[i] == s2[j]
    --Iterative:
    for i, j, k
    for m in (0...k):
    dp[i][j][k] = dp[i][j][m] && dp[i+m][j+m][k-m]
    || dp[i][j+k-m][m] && dp[i+m][j][k-m]
    --return:
    dp[0][0][len]
public boolean isScramble(String s1, String s2) {
        int len = s1.length();
        boolean[][][] dp = new boolean[len][len][len+1];
        for(int i = 0; i < len; i++){
            for(int j = 0; j < len; j++){
                dp[i][j][1] = s1.charAt(i) == s2.charAt(j);
            }
        }

        for(int k = 2; k <= len; k++){
            for(int i = 0; i <= len-k; i++){
                for(int j = 0; j <= len-k; j++){
                    for(int m = 1; m < k; m++)
                        if(dp[i][j][m] && dp[i+m][j+m][k-m]
||  dp[i][j+k-m][m] && dp[i+m][j][k-m]){
                            dp[i][j][k] = true;
                            break;
                        }
                        
                    
                }
            }
        }
        return dp[0][0][len];
    }

DFS会更快

#980 Unique Paths III
  • Sol
#741 Cherry Pickup
  • Sol
    dp[r1][c1][c2] denotes the max cherry we can pick up from (r1, c1) to (n, n) and from(r1+c1-c2, c2) to (n, n)
    --base case:
    dp[n-1][n-1][n-1] = grid[n-1][n-1]
    --itertive
    dp[r][i][j] = grid[r][i] == 1 ? 1:0 + grid[r+i-j][j] == 1? 1: 0
    +MAX(
    dp[r][i+1][j+1]
    dp[r+1][i][j]
    dp[r+1][i][j+1]
    dp[r][i+1][j]
    )
    --return
    dp[0][0][0]
    int[][][] dp;
    public int cherryPickup(int[][] grid) {
        int n = grid.length;
        dp = new int[n][n][n];
        for (int[][] layer: dp)
            for (int[] row: layer)
                Arrays.fill(row, Integer.MIN_VALUE);
        return Math.max(0, help(grid, 0, 0, 0));
    }
    public int help(int[][] grid, int r1, int c1, int c2){
        int r2 = r1 + c1 - c2;
        int n = grid.length;
        if(r1 == n || r2 == n || c1 == n || c2 == n) return -999999;
        if(grid[r1][c1] == -1 || grid[r2][c2] == -1) return -999999;
        if(r1 == n-1 && c1 == n-1) return grid[r1][c1]; // base case
        if(dp[r1][c1][c2] != Integer.MIN_VALUE) return dp[r1][c1][c2];
        int res = grid[r1][c1] == 1 ? 1 :0;
        if(c1 != c2) res += grid[r2][c2] == 1 ? 1 :0;
        res += Math.max(
            Math.max(help(grid,r1, c1+1, c2+1), help(grid,r1+1, c1, c2+1)), 
            Math.max(help(grid,r1, c1+1, c2), help(grid,r1+1, c1, c2))
        );
        
        dp[r1][c1][c2] = res;
        return res;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容