【LeetCode】动归

一维

70 爬楼梯E


198 小偷E

  1. 解法:
dp[i] = Math.max(dp[i-1], dp[i-2]+nums[i-1]);

413 等差数列划分M

  1. 题意:找出数组中等差数列的数量(最少为3个元素算一个等差数列)
  2. 解法:
  • 差分:
        for(int i = 2; i < n; ++i){
            if(nums[i-1] - nums[i] == d){
                t++;
            }else{
                d = nums[i-1] - nums[i];
                t = 0;
            }
            ans += t;
  • 滑动窗口:
        for(int i = 2; i < n; ++i){
            int d = nums[i] - nums[i-1];
            if(d == pred){
                L++; //滑动窗口增加
            }else{
                res += (L-1)*(L-2)/2; //规律
                L = 2; //滑动窗口重新设为2
                pred = d;
            }
        }
        //对最后一组滑动窗口再计算一次
        res += (L-1)*(L-2)/2;
  • 动态规划:
        for(int i = 2; i < n; ++i){
            if(nums[i] - nums[i-1] == nums[i-1] - nums[i-2]){
                dp[i] = dp[i-1] + 1; //等差数列相关的状态转移方程
                res += dp[i];
            }
        }

二维

64 最小路径和M

  1. 题意:非负二维数组,从左上到右下角的最小路径和
  2. 解法:
  • 动态规划:
    状态转移方程:dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
    但是i=0和j=0的时候要判断

  • 动态规划(二维变一维):

        for(int i = 0; i < m; ++i){
            for(int j = 0; j < n; ++j){
                if(i == 0 && j == 0){
                    dp[j] = grid[i][j];
                }else if(i == 0){
                    dp[j] = dp[j-1] + grid[i][j];
                }else if(j == 0){
                    dp[j] = dp[j] + grid[i][j];
                }else{
                    dp[j] = Math.min(dp[j], dp[j-1]) + grid[i][j];
                }
            }
        }

542 01矩阵M

  1. 题意:输出由01组成的矩阵每个元素与0的距离(0是0,1要看离它最近的0)
  2. 解法:
  • bfs:让所有的0入队,遍历一圈找到离它们最近的1入队,并dist++,
        //bfs
        while(!queue.isEmpty()){
            int[] cell = queue.poll();
            int x = cell[0];
            int y = cell[1];

            for(int[] di : dirs){
                int nx = x + di[0];
                int ny = y + di[1];
                if(nx >= 0 && ny >= 0 && nx < m && ny < n && !visited[nx][ny]){
                    if(mat[nx][ny] != 0){
                        dist[nx][ny] = dist[x][y] + 1;
                        visited[nx][ny] = true;
                        queue.offer(new int[]{nx, ny});
                    }
                }
            }
        }
  • 动态规划:从左上右上右下左下四种方向,设置移动方向,比较距离最小值。
//左上。即后来的dist值与左边和上边的dist值进行比较。
        for(int i = 0; i < m; ++i){
            for(int j = 0; j < n; ++j){
                if(i-1 >= 0){
                    dist[i][j] = Math.min(dist[i][j], dist[i-1][j] + 1);
                }
                if(j-1 >= 0){
                    dist[i][j] = Math.min(dist[i][j], dist[i][j-1] + 1);
                }
            }
        }

221 最大正方形M

  1. 题意:找出01矩阵中包含的最大的正方形,输出面积
  2. 思路:
  • 暴力法:找到一个1就当做正方形的左上角,在剩余可围成的最大矩形中,先判断斜线上的元素是否为1,若为1,则判断该元素行和列上的元素是否为1,都为1则记录边长
                //遍历到1将其作为正方形左上角
                if(matrix[i][j] == '1'){
                    res = Math.max(res, 1);

                    //还剩下的最大矩阵边长reMain
                    int reMain = Math.min(m-i, n-j);
                    //遍历斜线
                    for(int k = 1; k < reMain; ++k){
                        boolean flag = true;
                        if(matrix[i+k][j+k] == '0'){
                            break;
                        }

                        //当斜线上的元素符合时,再检查该处行和列上的元素
                        for(int x = 0; x < k; ++x){
                            if(matrix[i+x][j+k] == '0' || matrix[i+k][j+x] == '0'){
                                flag = false;
                                break;
                            }
                        }

                        //每多在斜线上前进一格并判断成功,记录res
                        if(flag == true){
                            res = Math.max(k+1, res);
                        }else{
                            break;
                        }
  • 动态规划:
        for(int i = 0; i < m; ++i){
            for(int j = 0; j < n; ++j){
                //当前位置为‘0’
                if(matrix[i][j] == '0'){
                    dp[i][j] = 0;
                }else{
                    if(i == 0 || j == 0){
                        dp[i][j] = 1;
                    }else{
                    //状态转移方程
                        dp[i][j] = Math.min(Math.min(dp[i-1][j], dp[i-1][j-1]), dp[i][j-1]) + 1;
                    }
                }
                res = Math.max(dp[i][j], res); 
            }

分割类问题

279 完全平方数M

  1. 题意:等价于给定若干个数字,每个数字可以被使用无限次,求能凑出n使用的数字最少个数
  2. 没有限制数字只能用一次,所以是完全背包问题
  • 动归:一维
for(int i = 1; i <= n; ++i){
  for(int j = 1; j*j < i; ++j){
    dp[i] = Math.min(dp[i], dp[i-j*j] + 1);
  }
} 
  • 动归:二维完全背包

91 解码方法M(同剑指offer46)

  1. 题意:对任意数字组成的字符串解码为字母,求能组合出的方式最大有多少种
  2. 解法:难点在于为什么可以题意翻译成状态转移方程。

139 单词拆分M

  1. 题意:给定字符串和字典,判断字典中的单词能否组成字符串
        for(int i = 0; i < n+1; i++){
            for(int j = i; j >= 0 && j >= i-maxw; --j){ //剪枝
                if(dp[j] && set.contains(s.substring(j, i))){
                    dp[i] = true;
                    break;
                }
            }
        }

子序列问题

300 最长递增子序列M

  1. 题意:给定数组找出最长子序列
  2. 解法:dp[i]定义为必须选i元素时的最长子序列
  • 动归
//状态转移方程
for
  int dp[i] = 1;
  for
    if(nums[i] > nums[j])
      dp[i] = Math.max(dp[i], dp[j]+1);
  max = Math.max(max, dp[i]);
  • 动归 + 二分
    d[i]表示以i元素为当前数组最大元素的序列。遍历数组nums,每遇到一个比d[i]大的num,则入队。若小,则从d[i]中找到比num小的,把num放在该处的后一个位置上,更新d[i]

1143 最长公共子序列M

  1. 题意:求两字符串最长公共子序列,abcde 和ace的就是ace
  2. 解法:构建二维数组。分情况讨论
//当前元素相等时,后置dp的值为左上角dp的值+1
dp[i+1][j+1] = dp[i][j] + 1;
//当前元素不同时
dp[i+1][j+1] = Math.max(dp[i+1][j], dp[i][j+1]);

背包问题

416 分割等和子集M

  1. 题意:正整数数组,分割成两个和相等的子数组
  2. 解法:target = sum / 2, 转化为找数组中能否凑出恰好和为target的子数组的01背包问题
        for(int i = 1; i < n; ++i){
            for(int j = 1; j <= target; ++j){
                if(nums[i] <= j){
                    dp[i][j] = dp[i-1][j] | dp[i-1][j-nums[i]];
                }else{
                    dp[i][j] = dp[i-1][j];
                }
            }
        }

474 一和零M

  1. 题意:二进制,字符串数组,找出恰好能凑出m个0,和n个1的子数组个数
  2. 解法:三层嵌套循环,逐个字符串动态规划,dp三维优化至二维
        for(int i = 0; i < len; ++i){
            for(int j = m; j >= count[i][0]; --j){
                for(int k = n; k >= count[i][1]; --k){
                    dp[j][k] = Math.max(dp[j][k], 1 + dp[j-count[i][0]][k-count[i][1]]);
                }
            }
        }

322 零钱兑换M

  1. 题意:完全背包问题,零钱不限,求凑出amount最少硬币数。
  2. 解法:完全背包,外层循环amount,内层遍历硬币
  • 动归(记忆性递归):
//由于是求最小,所以可以将初始值设为最大,用Math.min求最小
        for(int i = 1; i <= amount; ++i){
            for(int j = 0; j < len; ++j){
                if(coins[j] <= i){
                    dp[i] = Math.min(dp[i], 1+dp[i-coins[j]]);
                }
            }
  • bfs:
//对每个零钱减一次,并入队,逐步搜索,一旦恰好减到0,则说明找到了最小值
        while(!queue.isEmpty()){
            int size = queue.size();
            for(int i = 0; i < size; i++){
                Integer head = queue.poll();
                for(int j = 0; j < len; ++j){
                    int next = head - coins[j];
                    if(next == 0){
                        return step;
                    }
                    if(next < 0){
                        break;
                    }
                    if(!visited[next]){
                        queue.offer(next);
                        visited[next] = true;
                    }
                }
            }
            step++;

字符串编辑

72 编辑距离M

  1. 两个字符串,插入、删除、替换执行最少次数,使两个字符串相等
  2. i,j指向各自位置,
//当两位置上字符相等时,
dp[i][j] = dp[i-1][j-1];

//当两位置上字符不相等时,
//1. 替换操作
dp[i][j] = dp[i-1][j-1]+1;
//2. 插入i位置或删除j位置
dp[i][j] = dp[i][j-1]+1;
//3. 插入j位置或删除i位置
dp[i][j] = dp[i-1][j]+1;

650 只有两个键的键盘M

//状态转移方程
dp[i] = Math.min(dp[i], dp[j] + dp[i/j]);
//(内层for循环可以将j < n 优化为j * j <= n)

10 正则表达式H

//状态转移方程
//当前字符相等,或j位置为.时
if(s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') dp[i][j] = dp[i-1][j-1];

//当前j位置字符为*时
//1. j-1位置相等或为.时
if(s.charAt(i) == p.charAt(j-1) || p.charAt(j-1) == '.') dp[i][j] = dp[i-1][j] || dp[i][j-2];
//2. j-1位置不相等且不为.
else dp[i][j] = dp[i][j-2];

//都不符合上述情况
else dp[i][j] = false;

股票交易

121 最合适时间买卖股票E

常规法
动归法:

//当前天数结束时,不持股时手里的现金数
dp[0] = Math.max(dp[0], dp[1]+prices[i]); //昨天也不持股,昨天持股但是今天卖了的最大值

//当前天数结束时,持股时手里的现金数
dp[1] = Math.max(-prices[i], dp[1]); //第一次买入股,和昨天也持股且今天不卖的最大值

309 最佳买卖股票时期含冷冻期M

//第i天结束时手中不持有股票,且不在冷冻期的收益
dp[i][0] = Math.max(dp[i-1][2], dp[i-1][0]);

//第i天结束时手中持有股票的收益
dp[i][1] = Math.max(dp[i-1][0]-prices[i], dp[i-1][1]);

//第i天结束时手中不持有股票,且在冷冻期的收益
dp[i][2] = dp[i-1][1]+prices[i];

241 为运算表达式设计优先值M

//1. 遇到全数字的情况直接返回res

//2. 遍历字符串,遇到运算符,则对运算符左右两边的字符串再进行一次方法调用
        for(int i = 0; i < expression.length(); ++i){
            if(expression.charAt(i) == '+' || expression.charAt(i) == '-' || expression.charAt(i) == '*'){
                List<Integer> left = new ArrayList<Integer>(diffWaysToCompute(expression.substring(0, i)));
                List<Integer> right = new ArrayList<Integer>(diffWaysToCompute(expression.substring(i+1)));

                for(int l : left){
                    for(int r : right){
                        switch(expression.charAt(i)){
                            case '+' : 
                                res.add(l+r);
                                break;
                            case '-' :
                                res.add(l-r);
                                break;
                            case '*' :
                                res.add(l*r);
                                break;

                        }
                    }
                }    
            }
        }
posted @ 2022-03-10 20:07  kisson7  阅读(215)  评论(0)    收藏  举报