leetcode-day14

本文介绍了动态规划在多种编程问题中的应用,如股票交易、硬币组合、数组元素计数、最值计算等,并展示了如何通过空间优化来减少算法复杂性。同时,还探讨了树形动态规划在解决二叉树问题中的方法,包括小偷盗窃问题和二叉树的最大金额。最后,文章提到了如何解决编码字符串解码和队列重建等挑战性问题,涉及递归和辅助栈等策略。

309.给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。​
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
动态规划:

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        if(n <= 1) //n=0或n=1均没有利润
            return 0;
        int [][] dp = new int[n][4];
        dp[0][0]=0;//不持有股票,没卖出的
        dp[0][1]=0;//不持有股票,卖出去了
        dp[0][2]=-1*prices[0];//持有股票,今天买入;
        dp[0][3]=-1*prices[0];//持有股票,非今天买入的;
        for(int i = 1; i < n; i++){
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]);//前一天不持有股票的两种情况的最大值
            dp[i][1] = Math.max(dp[i-1][2], dp[i-1][3])+prices[i];//今天卖出股票,前一天持有股票的最大值+pr
            dp[i][2] = dp[i-1][0]-prices[i];//今天买入股票,这前一天一定没有卖出股票
            dp[i][3] = Math.max(dp[i-1][2], dp[i-1][3]);//今天没买股票,却持有股票,前一天继承来的,有两种情况
        }
        return Math.max(dp[n-1][0], dp[n-1][1]);
    }
}

空间优化:

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        if(n <= 1) //n=0或n=1均没有利润
            return 0;
        int f0=0;//不持有股票,没卖出的
        int f1=0;//不持有股票,卖出去了
        int f2=-1*prices[0];//持有股票,今天买入;
        int f3=-1*prices[0];//持有股票,非今天买入的;
        for(int i = 1; i < n; i++){
            int a = Math.max(f0, f1);//前一天不持有股票的两种情况的最大值
            int b = Math.max(f2, f3)+prices[i];//今天卖出股票,前一天持有股票的最大值+pr
            int c = f0-prices[i];//今天买入股票,这前一天一定没有卖出股票
            int d = Math.max(f2, f3);//今天没买股票,却持有股票,前一天继承来的,有两种情况
            f0 = a;
            f1 = b;
            f2 = c;
            f3 = d;
        }
        return Math.max(f0, f1);
    }
}

312.有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。
求所能获得硬币的最大数量。

322.给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
动态规划(类似于300题):

public class Solution {
    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, max);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                if (coins[j] <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

记忆化搜索:

public class Solution {
    public int coinChange(int[] coins, int amount) {
        if (amount == 0) { //不需要硬币
            return 0;
        }
        return coinChange(coins, amount, new int[amount]);
    }

    private int coinChange(int[] coins, int rem, int[] count) {
        if (rem < 0) { //该硬币组合凑不成
            return -1;
        }
        if (rem == 0) { //不再需要硬币
            return 0;
        }
        if (count[rem - 1] != 0) { //表示已经有了结果,因为数组从0开始所以是rem-1
            return count[rem - 1];
        }
        int min = Integer.MAX_VALUE; //求最少,所以初始化为最大值
        for (int coin : coins) {
            int res = coinChange(coins, rem - coin, count);
            if (res >= 0 && (res + 1 < min)) { //如果res+1更小则更新min
                min = 1 + res;
            }
        }
        count[rem - 1] = (min == Integer.MAX_VALUE) ? -1 : min;
        return count[rem - 1];
    }
}

337.小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
三种方法解决树形动态规划问题-从入门级代码到高效树形动态规划代码实现@房建斌学算法⭐
动态规划:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int rob(TreeNode root) {
        int[] result = robInternal(root);
        return Math.max(result[0], result[1]);//0代表不偷,1代表偷
    }

    public int[] robInternal(TreeNode root) {
        int[] result = new int[2];
        if (root == null) 
        return result;

        int[] left = robInternal(root.left);
        int[] right = robInternal(root.right); 

        //任何一个节点能偷到的最大钱的状态:(重点在于偷的钱数大小,而非偷不偷)
        //当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
        result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        //当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数
        result[1] = left[0] + right[0] + root.val;

        return result;
    }
}

338.给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。
Brian Kernighan 算法:

class Solution {
    public int[] countBits(int n) {
        int[] bits = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            bits[i] = countOnes(i);
        }
        return bits;
    }
    //1 & 1 = 1;  1 & 0 = 0;  0 & 0 = 0
    //https://blog.youkuaiyun.com/weixin_43688483/article/details/106349982
    public int countOnes(int x) {
        int ans = 0;
        while (x > 0) {
            x &= (x - 1);
            ans++;
        }
        return ans;
    }
}

动态规划——最高有效位:

class Solution {
    public int[] countBits(int n) {
        int[] bits = new int[n + 1];
        int highBit = 0;
        for (int i = 1; i <= n; i++) {
            if ((i & (i - 1)) == 0) {//如果i&(i-1))=0,i为2的n次方
                highBit = i;
            }
            //bits[i]比bits[i - highBit]多一位
            bits[i] = bits[i - highBit] + 1;
        }
        return bits;
    }
}

动态规划——最低有效位:(类似于斐波那契数列)

class Solution {
    public int[] countBits(int n) {
        int[] bits = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            bits[i] = bits[i & (i - 1)] + 1;
        }
        return bits;
    }
}

347.给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
在这里插入图片描述

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
        Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
        for (int num : nums) {//getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。
            occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
        }

        //int[]的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        //建立一个小顶堆,自动排序
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] m, int[] n) {
                return m[1] - n[1];
            }
        });
        /** 
        遍历「出现次数数组」:
        如果堆的元素个数小于 k,就可以直接插入堆中。
        如果堆的元素个数等于 k,则检查堆顶与当前出现次数的大小。如果堆顶更大,说明至少有k个数字的出现次数比当前值大,故舍弃当前值;否则,就弹出堆顶,并将当前值插入堆中。
        */
        for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
            int num = entry.getKey(), count = entry.getValue();
            if (queue.size() == k) {
                if (queue.peek()[1] < count) {
                    queue.poll();
                    queue.offer(new int[]{num, count});
                }
            } else {
                queue.offer(new int[]{num, count});
            }
        }

        //取出最小堆中的元素
        int[] res = new int[k];
        for (int i = 0; i < k; i++) {
            res[i] = queue.poll()[0];
        }
        return res;
    }
}

快排:(略)
394.给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
字符串解码(辅助栈法 / 递归法,清晰图解)@Krahets⭐
辅助栈法:

class Solution {
    public String decodeString(String s) {
        StringBuilder res = new StringBuilder();
        int multi = 0;
        LinkedList<Integer> stack_multi = new LinkedList<>();
        LinkedList<String> stack_res = new LinkedList<>();
        for(Character c : s.toCharArray()) {
            if(c == '[') {
                stack_multi.addLast(multi);
                stack_res.addLast(res.toString());
                multi = 0;
                res = new StringBuilder();
            }
            else if(c == ']') {
                StringBuilder tmp = new StringBuilder();
                int cur_multi = stack_multi.removeLast();
                for(int i = 0; i < cur_multi; i++) 
                    tmp.append(res);
                res = new StringBuilder(stack_res.removeLast() + tmp);
            }
            else if(c >= '0' && c <= '9') 
                multi = multi * 10 + Integer.parseInt(c + "");// Character cannot be converted to String
            else res.append(c);
        }
        return res.toString();
    }
}

递归:

class Solution {
    public String decodeString(String s) {
        return dfs(s, 0)[0];
    }
    private String[] dfs(String s, int i) {
        StringBuilder res = new StringBuilder();
        int multi = 0;
        while(i < s.length()) {
            if(s.charAt(i) >= '0' && s.charAt(i) <= '9') 
                multi = multi * 10 + Integer.parseInt(String.valueOf(s.charAt(i))); 
            else if(s.charAt(i) == '[') {
                String[] tmp = dfs(s, i + 1);
                i = Integer.parseInt(tmp[0]);
                while(multi > 0) {
                    res.append(tmp[1]);
                    multi--;
                }
            }
            else if(s.charAt(i) == ']') 
                return new String[] { String.valueOf(i), res.toString() };
            else 
                res.append(String.valueOf(s.charAt(i)));
            i++;
        }
        return new String[] { res.toString() };
    } 
}

399.给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件,其中 equations[i] = [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi = values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。
另有一些以数组 queries 表示的问题,其中 queries[j] = [Cj, Dj] 表示第 j 个问题,请你根据已知条件找出 Cj / Dj = ? 的结果作为答案。
返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -1.0 替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0 替代这个答案。
注意:输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果。

406.假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
406. 根据身高重建队列动画演示@Sunny⭐
从高到低考虑:

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        //按数组第一个元素进行降序:对于每个元素,在其之前的元素的个数,就是大于等于他的元素的数量
        //按数组第二个元素升序:k大的尽量在后面,减少后面一步插入操作的次数
        Arrays.sort(people, new Comparator<int[]>() {
            @Override
            public int compare(int[] person1, int[] person2){
                if (person1[0] != person2[0]){
                    //第一个元素不相等时,第一个元素降序
                    return person2[0] - person1[0];
                }else{
                    //第一个元素相等时,第二个元素升序
                    return person1[1] - person2[1];
                }
            }
        });
        //新建一个list,用于保存结果集
        List<int[]> list = new LinkedList<>();
        for (int i = 0; i < people.length; i++) {
            if (people[i][1] < list.size()){
                //第i个人前面应有的人数小于结果集中元素个数时,将第i个人插入到结果集的第
                //people[i][1]个位置
                list.add(people[i][1],people[i]);
            }else{
                //第i个人前面应有的人数大于等于结果集中元素个数时,将第i个人追加到结果集的后面
                list.add(list.size(),people[i]);
            }
        }
        //将list转化为数组,然后返回
        return list.toArray(new int[list.size()][]);
    }
}

416.给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
动态规划(转换为 0-1 背包问题)@liweiwei1419⭐
在这里插入图片描述

class Solution {
    public boolean canPartition(int[] nums) {
        int n = nums.length;
        if (n < 2) { //n=0或n=1时均不能进行分割
            return false;
        }
        int sum = 0, maxNum = 0;
        for (int num : nums) {
            sum += num;
            maxNum = Math.max(maxNum, num); //得到最大元素的数值
        }
        if (sum % 2 != 0) { //和为奇数不符合要求,不能均分成两个子集
            return false;
        }
        int target = sum / 2;
        if (maxNum > target) { //如果最大元素已经大于和的一半则不能满足题目要求
            return false;
        }
        boolean[] dp = new boolean[target + 1];
        //dp[j] = dp[j] | dp[j - num]因为要么已经有一条路径达到j要么即将形成一条路径达到j
        //“||”当有一个条件成立时,就不再往下执行判断条件而直接执行if的内容;“|”从左到右无论是否有条件成立,都会将所有的判断语句执行。
        dp[0] = true; //初始化
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            for (int j = target; j >= num; j--) {
                dp[j] = dp[j] | dp[j - num];//dp[j]和dp[j-num]有一个为真,dp[j]为真
            }
        }
        return dp[target];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值