博客记录-day150-力扣(数位dp)

一、力扣

1、分发糖果

135. 分发糖果

先考虑一边贪心,再考虑另一边。

java 复制代码
class Solution {
    public int candy(int[] ratings) {
        int n=ratings.length;
        int[] left=new int[n];
        left[0]=1;
        for(int i=1;i<n;i++){
            if(ratings[i]>ratings[i-1]){
                left[i]=left[i-1]+1;
            }else{
                left[i]=1;
            }
        }
        int[] right=new int[n];
        right[n-1]=1;
        for(int i=n-2;i>=0;i--){
            if(ratings[i]>ratings[i+1]){
                right[i]=right[i+1]+1;
            }else{
                right[i]=1;
            }
        }
        int res=0;
        for(int i=0;i<n;i++){
            res+=Math.max(left[i],right[i]);
        }
        return res;
    }
}

2、加油站

134. 加油站

java 复制代码
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int curSum = 0;       // 当前油箱剩余油量(从当前起点出发到当前站的累计盈余)
        int totalSum = 0;     // 总油箱剩余油量(整个环路的累计盈余)
        int index = 0;        // 可能的起点索引
        
        for (int i = 0; i < gas.length; i++) {
            // 计算从当前起点到i站的油量盈余(gas[i] - cost[i])
            curSum += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            
            // 如果当前剩余油量为负,说明从当前起点无法到达下一站
            if (curSum < 0) {
                // 将起点更新为下一站(i+1取模实现环形)
                index = (i + 1) % gas.length;
                // 重置当前剩余油量,从新起点重新开始累积
                curSum = 0;
            }
        }
        
        // 总剩余油量小于0,说明无法绕环路一周
        return totalSum < 0 ? -1 : index;
    }
}

3、K 次取反后最大化的数组和

1005. K 次取反后最大化的数组和

java 复制代码
class Solution {
    public int largestSumAfterKNegations(int[] nums, int k) {
        Arrays.sort(nums);
        int sum = 0;
        // 遍历排序后的数组,有负值且还有转换次数就转正
        for(int i = 0; i < nums.length; i++) {
            if(nums[i] < 0 && k > 0) {
                nums[i] = -1 * nums[i];
                k--;
            }
            sum += nums[i];
        }
        // 再排序有三种情况 1. 转换次数已经用完 此时直接返回即可
        //                 2.转换次数没用完 还剩偶数次,此时没有负数了,直接返回即可
        //                 3.转换次数没用完 还剩偶数次,此时没有负数了,返回sum-2*最小数
        Arrays.sort(nums);
        return sum - (k % 2 == 0? 0 : 2 * nums[0]);
    }
}

4、数位dp-数字 1 的个数

233. 数字 1 的个数

因为这个是统计每个生成的数字中1的个数,所以有第二个维度,每次dfs都是与前面怎么填无关的。

算法解析:

  1. 数位DP思想:将数字拆解为各个位,逐位处理并统计符合条件的情况
  2. 记忆化剪枝:存储中间状态避免重复计算,时间复杂度优化至O(log n)
  3. 状态定义:memo[i][cnt1] 表示从第i位开始,已统计cnt1个1时后续的可能情况
  4. 两种状态转移:
    • 受限状态(isLimit=true):必须小于等于原数字对应位,后续状态继承限制
    • 自由状态(isLimit=false):可以取0-9,后续状态不受限 关键点:只有自由状态的结果可以被记忆化存储,受限状态的结果具有唯一性无法复用
java 复制代码
class Solution {
    public int countDigitOne(int n) {
        // 将数字转换为字符数组便于逐位处理
        char[] s = Integer.toString(n).toCharArray();
        int m = s.length;
        // 记忆化数组:memo[i][cnt1] 表示从第i位开始,已统计cnt1个1时后续的1的总数
        int[][] memo = new int[m][m];
        for (int[] row : memo) {
            Arrays.fill(row, -1); // -1 表示未计算状态
        }
        // 启动DFS:从最高位开始,初始统计0个1,处于受限状态
        return dfs(0, 0, true, s, memo);
    }

    private int dfs(int i, int cnt1, boolean isLimit, char[] s, int[][] memo) {
        // 递归终止:处理完所有位数时返回累计的1的个数
        if (i == s.length) {
            return cnt1;
        }
        // 记忆化检查:非限制状态下且已计算过的状态直接返回
        if (!isLimit && memo[i][cnt1] >= 0) {
            return memo[i][cnt1];
        }
        
        int res = 0;
        // 确定当前位的上界:受限于原数字时取当前位值,否则取9
        int up = isLimit ? s[i] - '0' : 9;
        
        // 枚举当前位可能的所有数字(0~up)
        for (int d = 0; d <= up; d++) {
            // 递归处理下一位:
            // i+1:移动到下一位
            // cnt1 + (d==1 ? 1:0):累计当前位的1的数量
            // isLimit && d==up:只有当前位达到上限时,下一位才继续受限
            res += dfs(i + 1, cnt1 + (d == 1 ? 1 : 0), isLimit && d == up, s, memo);
        }
        
        // 仅当不受限时记录结果,因为受限状态的结果无法被复用
        if (!isLimit) {
            memo[i][cnt1] = res;
        }
        return res;
    }
}

1. 数位dp模版

2999. 统计强大整数的数目

java 复制代码
class Solution {
    public long numberOfPowerfulInt(long start, long finish, int limit, String s) {
        String low = Long.toString(start);
        String high = Long.toString(finish);
        int n = high.length();
        low = "0".repeat(n - low.length()) + low; // 补前导零,和 high 对齐
        long[] memo = new long[n];
        Arrays.fill(memo, -1);
        return dfs(0, true, true, low.toCharArray(), high.toCharArray(), limit, s.toCharArray(), memo);
    }

    private long dfs(int i, boolean limitLow, boolean limitHigh, char[] low, char[] high, int limit, char[] s, long[] memo) {
        if (i == high.length) {
            return 1;
        }

        if (!limitLow && !limitHigh && memo[i] != -1) {
            return memo[i]; // 之前计算过
        }

        // 第 i 个数位可以从 lo 枚举到 hi
        // 如果对数位还有其它约束,应当只在下面的 for 循环做限制,不应修改 lo 或 hi
        int lo = limitLow ? low[i] - '0' : 0;
        int hi = limitHigh ? high[i] - '0' : 9;

        long res = 0;
        if (i < high.length - s.length) { // 枚举这个数位填什么
            for (int d = lo; d <= Math.min(hi, limit); d++) {
                res += dfs(i + 1, limitLow && d == lo, limitHigh && d == hi, low, high, limit, s, memo);
            }
        } else { // 这个数位只能填 s[i-diff]
            int x = s[i - (high.length - s.length)] - '0';
            if (lo <= x && x <= hi) { // 题目保证 x <= limit,无需判断
                res = dfs(i + 1, limitLow && x == lo, limitHigh && x == hi, low, high, limit, s, memo);
            }
        }

        if (!limitLow && !limitHigh) {
            memo[i] = res; // 记忆化 (i,false,false)
        }
        return res;
    }
}

2. 前导零

2376. 统计特殊整数

java 复制代码
class Solution {
    public int countSpecialNumbers(int n) {
        char[] s = Integer.toString(n).toCharArray();
        int[][] memo = new int[s.length][1 << 10];
        for (int[] row : memo) {
            Arrays.fill(row, -1); // -1 表示没有计算过
        }
        return dfs(0, 0, true, false, s, memo);
    }

    private int dfs(int i, int mask, boolean isLimit, boolean isNum, char[] s, int[][] memo) {
        if (i == s.length) {
            return isNum ? 1 : 0; // isNum 为 true 表示得到了一个合法数字
        }
        if (!isLimit && isNum && memo[i][mask] != -1) {
            return memo[i][mask]; // 之前计算过
        }
        int res = 0;
        if (!isNum) { // 可以跳过当前数位
            res = dfs(i + 1, mask, false, false, s, memo);
        }
        // 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i](否则就超过 n 啦)
        int up = isLimit ? s[i] - '0' : 9;
        // 枚举要填入的数字 d
        // 如果前面没有填数字,则必须从 1 开始(因为不能有前导零)
        for (int d = isNum ? 0 : 1; d <= up; d++) {
            if ((mask >> d & 1) == 0) { // d 不在 mask 中,说明之前没有填过 d
                res += dfs(i + 1, mask | (1 << d), isLimit && d == up, true, s, memo);
            }
        }
        if (!isLimit && isNum) {
            memo[i][mask] = res; // 记忆化
        }
        return res;
    }
}

5、灌溉花园的最少水龙头数目

1326. 灌溉花园的最少水龙头数目

java 复制代码
class Solution {
    public int minTaps(int n, int[] ranges) {
        int[] rightMost = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            int r = ranges[i];
            if (i > r) rightMost[i - r] = i + r; // 由于 i 在不断变大,对于 i-r 来说,i+r 必然是它目前的最大值
            else rightMost[0] = Math.max(rightMost[0], i + r);
        }

        int ans = 0;
        int curRight = 0; // 已建造的桥的右端点
        int nextRight = 0; // 下一座桥的右端点的最大值
        for (int i = 0; i < n; i++) { // 如果走到 n-1 时没有返回 -1,那么必然可以到达 n
            nextRight = Math.max(nextRight, rightMost[i]);
            if (i == curRight) { // 到达已建造的桥的右端点
                if (i == nextRight) return -1; // 无论怎么造桥,都无法从 i 到 i+1
                curRight = nextRight; // 造一座桥
                ans++;
            }
        }
        return ans;
    }
}

6、回文链表

234. 回文链表

java 复制代码
class Solution {
    public boolean isPalindrome(ListNode head) {
        int n=0;
        for(ListNode temp=head;temp!=null;temp=temp.next) n++;
        ListNode mid=find(head);
        if(n%2==1) mid=mid.next;
        ListNode sec=reverse(mid);
        
        while(sec!=null){
            if(sec.val!=head.val){
                return false;
            }
            sec=sec.next;
            head=head.next;
        }
        return true;
    }
    public ListNode find(ListNode head){
        ListNode fast=head;
        ListNode slow=head;
        ListNode pre=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            pre=slow;
            slow=slow.next;
        }
        pre.next=null;
        return slow;
    }
    public ListNode reverse(ListNode head){
        ListNode pre=null;
        while(head!=null){
            ListNode nexthead=head.next;
            head.next=pre;
            pre=head;
            head=nexthead;
        }
        return pre;
    }
}

7、寻找峰值

162. 寻找峰值

java 复制代码
class Solution {
    public int findPeakElement(int[] nums) {
        int n = nums.length;
        // 初始化左右指针。由于峰值至少需要比较一个邻居,右边界设为n-2(最后一个元素无需右邻居)
        int left = 0, right = n - 2;
        
        while (left <= right) {
            int mid = (left + right) / 2;  // 计算中间位置
            
            // 比较中间元素与其右侧元素
            if (nums[mid] < nums[mid + 1]) {
                // 右侧元素更大,说明峰值可能在[mid+1, right]区间
                left = mid + 1;
            } else {
                // 中间元素不小于右侧,峰值可能在[left, mid-1]区间或mid本身
                right = mid - 1;
            }
        }
        
        // 循环结束时,left指向潜在峰值。由于每次移动都保证向更高方向搜索,最终left即为峰值位置
        return left;
    }
}

8、路径总和 II

113. 路径总和 II

不带回溯dfs:

java 复制代码
class Solution {
    private List<List<Integer>> ans = new ArrayList<>();

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<Integer> path = new ArrayList<>();
        dfs(root, targetSum, path);
        return ans;
    }

    // targetSum和path是上面的节点传递下来的信息,ans是全局的
    private void dfs(TreeNode root, int targetSum, List<Integer> path) {
        if (root == null) {
            return;
        }
        targetSum -= root.val;
        path.add(root.val);
        if (root.left == root.right) {  // 当前节点是叶子节点
            if (targetSum == 0)
                ans.add(path);
            return;
        }
        // 这题好坑啊,这里递归的话要写成new ArrayList<>(path),
        // 不然path是pathSum中传入的全局变量了
        dfs(root.left, targetSum, new ArrayList<>(path));
        dfs(root.right, targetSum, new ArrayList<>(path));
    }
}

带回溯dfs:

java 复制代码
class Solution {
    private List<List<Integer>> ans = new ArrayList<>();

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<Integer> path = new ArrayList<>();
        dfs(root, targetSum, path);
        return ans;
    }

    // targetSum和path是上面的节点传递下来的信息,ans是全局的
    private void dfs(TreeNode root, int targetSum, List<Integer> path) {
        if (root == null) {
            return;
        }
        targetSum -= root.val;
        path.add(root.val);
        if (root.left == root.right) {  // 当前节点是叶子节点
            if (targetSum == 0)
                ans.add(new ArrayList<>(path));  // 这里要new一个新的保存path的对象!!!
            // return;  回溯的话这里就不能return了,return会导致remove不了
        }

        dfs(root.left, targetSum, path);
        dfs(root.right, targetSum, path);
        // 恢复现场
        path.remove(path.size() - 1);
    }
}

9、岛屿的最大面积

695. 岛屿的最大面积

java 复制代码
class Solution {
    int[][] direct={{1,0},{-1,0},{0,1},{0,-1}};
    int m,n;
    int[][] grid;
    public int maxAreaOfIsland(int[][] grid) {
        int res=0;
        this.grid=grid;
        m=grid.length;
        n=grid[0].length;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(grid[i][j]==1){
                    res=Math.max(res,dfs(i,j)) ;

                }
            }
        }
        return res;
    }
    public int dfs(int x,int y){
        grid[x][y]=0;
        int res=1;
        for(var dir:direct){
            int nx=x+dir[0];
            int ny=y+dir[1];
            if(nx>=0&&nx<m&&ny>=0&&ny<n&&grid[nx][ny]==1){
                res+=dfs(nx,ny);
                
            }
        }
        return res;
    }
}
相关推荐
福尔摩东34 分钟前
RAG全流程冠军思路! 实习or跳槽or项目实战
面试·github
DarrenPig1 小时前
【新能源科学与技术】MATALB/Simulink小白教程(一)实验文档【新能源电力转换与控制仿真】
matlab·开源·github·simulink·交流
码流怪侠3 小时前
arnis 在 GitHub 上短短三月暴增 7k star 🔥
github
Space-oddity-fang3 小时前
Ubuntu启动SMB(Samba)服务步骤
linux·服务器·github
2301_766469563 小时前
本地项目上传到 GitHub流程
github
梓羽玩Python8 小时前
开源TTS领域迎来重磅新星!Dia-1.6B:超逼真对话生成,开源2天斩获6.5K Star!
人工智能·python·github
寻月隐君8 小时前
如何高效学习一门技术:从知到行的飞轮效应
后端·github
qianmoQ8 小时前
GitHub 趋势日报 (2025年04月15日)
github
uhakadotcom11 小时前
「Boli」职业规划助手:AI赋能你的求职之路,轻松拿下理想offer!
后端·面试·github