算法剩余部分

在准备面试的过程中发现之前漏掉了一些算法的整理,在本文中慢慢补齐

动态规划

太抽象,还是以零钱问题为例吧。

其实凡是涉及到这种重叠子问题的,都可以用动态规划。

重叠子问题是啥意思?就是大问题拆解成的小问题,可以递归+缓存,比如电影院第几排或者斐波拉契数列,自顶向下,肯定会有部分重叠子问题,如果用缓存,drill down的时候缓存起来。缺点是什么:这样其实还是会去遍历,只能会再走缓存,性能强一些。

有时候,可以递归+贪心,比如零钱,先从最大面额的开始递归,提高性能。

有没有更厉害的解法?就是动态规划。听着挺唬人的,其实就那样。先按照零钱问题走一遍。

初始化状态 -> 确定状态参数 -> 设计决策的思路

初始化是什么,就是amount=0,需要的最小硬币数,dp[0]=0 然后其他每个的初始值都是 dp[i]=Integer.MAX_VALUE(只需要比amount大就行)

然后是状态参数。啥意思?子问题和原问题之间发生变化的变量。状态参数是dp[j],它记录了达到金额j所需的最少硬币数量。

然后就是设计决策,状态转移方程,描述了如何从已知状态推导出未知状态的过程。要怎么逐步获取到我们要的结果dp[amount]呢?对于每种硬币coin,考虑将其加入到构成总金额的过程中。对于每一个j >= coin,我们检查如果使用了一个面值为coin的硬币,那么剩余的金额变为j - coin。如果dp[j - coin]已经有了一个有效的值(即不是Integer.MAX_VALUE),说明存在一种方式可以凑成j - coin,那么我们就可以通过增加一个coin硬币来考虑凑成j的方案,即更新dp[j] = min(dp[j], dp[j - coin] + 1)。这里dp[j - coin] + 1意味着在已有凑成j - coin最少硬币数基础上再加一个硬币。

我们从最小的金额开始,逐步向上构建,直到求得目标金额amount所需的最少硬币数。每一步的决策都是基于之前的最优解,确保了最终得到的是全局最优解。

对应代码

public class Solution322 {
    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], dp[i-coin]+1);
                }
            }
        }
        return dp[amount] == amount+1 ? -1:dp[amount];
    }
}

令牌桶算法

在这里插入图片描述
优点:
实现简单,灵活性高,桶里面有令牌的时候,低延迟
缺点:
初始桶容量和令牌添加速度需要谨慎设置,配置不当可能资源浪费

可以参考guava的RateLimiter

public class TokenBucket {
    private final long maxTokens; //桶的最大容量
    private final long refillInterval; //令牌添加的间隔时间
    private final long tokenPerInterval; //每次添加的令牌数量
    private AtomicLong availableTokens; //当前可用的令牌数
    private AtomicLong lastRefillTime; //上次添加令牌的时间

    public TokenBucket(long maxTokens, long refillInterval,long tokensPerInterval){
        this.maxTokens = maxTokens;
        this.refillInterval = refillInterval;
        this.tokenPerInterval = tokensPerInterval;
        this.availableTokens = new AtomicLong(maxTokens);//初始化令牌桶数量
        this.lastRefillTime = new AtomicLong(System.currentTimeMillis());
    }

    //请求指定数量的令牌,成功返回true
    public synchronized boolean tryConsume(long tokens){
        //尝试添加令牌
        refill();
        if (availableTokens.get() >= tokens) {
            availableTokens.addAndGet(-tokens); //消耗令牌
            return true;
        } else {
            return false;
        }
    }

    //尝试添加令牌
    private void refill() {
        long currentTime = System.currentTimeMillis();
        long lastRefill = lastRefillTime.get();

        if (currentTime > lastRefill) {
            long elapsedTime = currentTime - lastRefill; // 计算时间差
            long tokensToAdd = (elapsedTime/refillInterval) * tokenPerInterval;
            if (tokensToAdd > 0) {
                long newTokenCount = Math.min(availableTokens.get() + tokensToAdd, maxTokens);
                availableTokens.set(newTokenCount);
                lastRefillTime.set(currentTime);
            }
        }
    }

    public static void main(String[] args) {
        //每秒添加1个令牌
        TokenBucket tokenBucket = new TokenBucket(10, 1000, 1);
        //模拟请求
        for (int i = 0; i < 20; i++) {
            if (tokenBucket.tryConsume(1)) {
                System.out.println("Request " + i + " was allowed.");
            } else {
                System.out.println("Request "+i+" was denied");
            }

            try {
                Thread.sleep(300); // 每500s尝试一次
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值