leetcode150题-字符串

字符串

多数元素

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

我的求解:

Java 复制代码
class Solution {
    public int majorityElement(int[] nums) {
        // 构建一个辅助map

        // 如果数组小于等于2,又假定一定有多数元素,则直接返回第一个元素即可
        if(nums.length == 0){
            return -1;//这里应该抛出错误,-1假定都为正
        }
        if(nums.length <= 2){
            return nums[0];
        }
        Map<Integer,Integer> map = new HashMap<Integer,Integer>();
        for(int i = 0; i < nums.length; i++){
            if(map.containsKey(nums[i])){
                map.put(nums[i],map.get(nums[i])+1);
            }else{
                map.put(nums[i],1);
            }
        }
        // 遍历map,找到多数元素
        int maxKey = 0;
        int maxValue = 0;
        for(Map.Entry<Integer,Integer> entry : map.entrySet()){
            if(entry.getValue() > maxValue){
                maxKey = entry.getKey();
                maxValue = entry.getValue();
            }
        }
        return maxKey;
    }
}

中规中矩,时间复杂度o(n),空间复杂度o(n)

但是还有更强大的摩尔投票算法可以达到o(1)的空间复杂度

Java 复制代码
class Solution {
    public int majorityElement(int[] nums) {
        // 莫尔投票算法
        // 或称之为六大门派围攻光明顶
        // 明教的势力大于所有的势力
        // 按照如下规则:
        // 车轮战依次对决
        // 遇到自己教的人,生命值+1
        // 遇到其他教的人,同归于尽,生命值-1
        // 若当前被挑战者死了,下一个上去的人成为挑战者
        // 由于明教太强了,最后留下来的一定是明教的人
        if(nums.length == 0){
            return -1;
        }
        int master = nums[0];
        int masterHP = 1;
        for (int i= 1; i < nums.length; i++){
            if(masterHP == 0){
                master = nums[i];
                masterHP = 1;
                continue;
            }
            if(master == nums[i]){
                masterHP+=1;
            }else{
                masterHP -=1;
            }
        }
        return master;
    }
}

轮转数组

问题描述

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3

输出: [5,6,7,1,2,3,4]

我的解答:

Java 复制代码
class Solution {
    public void rotate(int[] nums, int k) {
        // 构建一个辅助数组即可 -> 空间复杂度o(n)
        int[] result = new int[nums.length];
        for(int i = 0; i < nums.length; i++){
            result[(i+k)%nums.length] = nums[i];
        }
        System.arraycopy(result,0,nums,0,nums.length);
    }
}

官方解答:

Java 复制代码
class Solution {
    public void rotate(int[] nums, int k) {
        // 使用再反转法
        // 将数组分为两部分:n-k,k
        // 最终我们想得到:k,n-k这样的结果
        // 1. 先将数组整体进行翻转,得到 Rk,Rn-k
        // 2. 然后将Rk和Rn-k分别反转
        // 即R(Rk) = k,R(Rn-k) = n-k,由此得到了k,n-k
        k %= nums.length;
        reverse(nums,0,nums.length-1);
        reverse(nums,0,k-1);
        reverse(nums,k,nums.length-1);
    }
    private void reverse(int[] nums, int start, int end){
        while(start < end){
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start++;
            end--;
        }
    }
}

买卖股票的最佳时机

题目描述:

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

输入:[7,1,5,3,6,4]

输出:5

解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。

注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

我的代码:

Java 复制代码
class Solution {
    public int maxProfit(int[] prices) {
        // 在股票最低的时候买入,然后在买入后最高点卖出
        int minIndex  = 0;
        for(int i = 0; i < prices.length; i++){
            if(prices[i] < prices[minIndex]){
                minIndex = i;
            }
        }
        // 从买入的数组找到最大值
        int maxIndex = minIndex;
        for (int i = minIndex; i < prices.length; i++){
            if(prices[maxIndex] < prices[i]){
                maxIndex = i;
            }
        }
        return prices[maxIndex] - prices[minIndex];
    }
}

存在问题,例如对于[4,2,1]这样的输入,不能得到正确答案,实际上不应该维护一个价格全局最低点,而是应该维护一个当前能看到的全局价格最低点

买卖股票的最佳时机 II

题目描述

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。然而,你可以在 同一天 多次买卖该股票,但要确保你持有的股票不超过一股。

返回 你能获得的 最大 利润 。

我的解答:

Java 复制代码
class Solution {
    public int maxProfit(int[] prices) {
        // 吃每个谷 - 峰的利润
        int allProfit = 0;
        for(int i = 0; i < prices.length -1; i++){
            if(prices[i] < prices[i+1]){
                // 如果下一天为增,则加上下一天的收益
                allProfit += prices[i+1] - prices[i];
            }
        }
        return allProfit;
    }
}

贪心最优,没问题

跳跃游戏

题目描述:

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。

示例 1:

输入:nums = [2,3,1,1,4]

输出:true

解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

我的解答:

Java 复制代码
class Solution {
    public boolean canJump(int[] nums) {
        // 维护一个辅助数组记录能到达的位置
        boolean[] canGo = new boolean[nums.length];
        Arrays.fill(canGo,false);
        canGo[0] = true;
        for(int i = 0; i < nums.length; i++){
            if(canGo[i]){
                // 原来是这样写的,最多走n步我就只走了n步,这不对
                // canGo[Math.min(i+nums[i],nums.length-1)] = true;

                // 需要注意,fill是[startIndex,endIndex)
                Arrays.fill(canGo,i,Math.min(i+nums[i]+1,nums.length),true);
            }
        }
        return canGo[nums.length-1];
    }
}

看了思路后我的解答

Java 复制代码
class Solution {
    public boolean canJump(int[] nums) {
        // 遍历数组,记录能到达的最大的位置
        int maxLocation = 0;
        for(int i = 0; i < nums.length; i++){
            // 还是有问题,应该先看能不能到这里,再更新
            // 对于[3,2,1,0,4],会错误的得到true
            if(nums[i]+ i > maxLocation){
                maxLocation = nums[i] + i;
            }
            if(i > maxLocation){
                return false;
            }
        }
        return maxLocation >= nums.length ? true : false;
    }
}

跳跃游戏 II

题目描述:

给定一个长度为 n 的 0 索引整数数组 nums。初始位置在下标 0。

每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在索引 i 处,你可以跳转到任意 (i + j) 处:

0 <= j <= nums[i] 且

i + j < n

返回到达 n - 1 的最小跳跃次数。测试用例保证可以到达 n - 1。

示例 1:

输入: nums = [2,3,1,1,4]

输出: 2

解释: 跳到最后一个位置的最小跳跃数是 2。

从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

我的解答:

Java 复制代码
class Solution {
    public int jump(int[] nums) {
        // dp[i] 表示跳跃到i需要的最小步数
        // dp[0] = 0
        // 对nums进行遍历:
        // 对于j <= i+nums[i]且j<nums.length
        // dp[j] = min(dp[j],dp[i]+1);
        int[] dp = new int[nums.length];
        Arrays.fill(dp,Integer.MAX_VALUE);
        dp[0] = 0;
        for(int i = 0; i < nums.length; i++){
            for(int j = i+1 ; j < nums.length && j <= i + nums[i]; j++){
                dp[j] = Math.min(dp[j],dp[i]+1);
            }
        }
        return dp[nums.length-1];
    }
}

采用动态规划算法,时间复杂度o(n^2)

官方解答:

Java 复制代码
class Solution {
    public int jump(int[] nums) {
        // 采用贪心算法,
        // 现在在第i个位置,从i能跳到第j个位置
        // 不是直接跳到第j个位置,而是遍历i到j
        // 找到k属于(i,j],而k+nums[i]最大
        // 即从最有潜力的位置跳
        int nextJump = 0; // 下一次能跳到的最远的位置
        int currentJump = 0; // 当前能跳到的最远位置
        int jumpCount = 0;// 跳了多少次

        // 由于题目确保必定可以到达n - 1
        // 则在n-1的时候并不需要建桥,故只到n-2即可
        // 遍历到n-2要么已经可以到n-1了,要么构建一个从n-2到n-1的桥
        for(int i = 0; i < nums.length-1; i++){
            if(i+nums[i] > nextJump){
                nextJump = i+nums[i];
            }
            if(i == currentJump){
                // 当前已经走到了能到的边界,则遍历完了需要跳了
                jumpCount++;
                currentJump = nextJump;
            }
        }
        return jumpCount;
    }
}

H 指数

题目描述

给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。

根据维基百科上 h 指数的定义:h 代表"高引用次数" ,一名科研人员的 h 指数 是指他(她)至少发表了 h 篇论文,并且 至少 有 h 篇论文被引用次数大于等于 h 。如果 h 有多种可能的值,h 指数 是其中最大的那个。

示例 1:

输入:citations = [3,0,6,1,5]

输出:3

解释:给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 3, 0, 6, 1, 5 次。

由于研究者有 3 篇论文每篇 至少 被引用了 3 次,其余两篇论文每篇被引用 不多于 3 次,所以她的 h 指数是 3。

我的解答:

Java 复制代码
class Solution {
    public int hIndex(int[] citations) {
        int sum = Arrays.stream(citations).sum();
        // h小于等于sum值的1/2次方
        int h = (int)Math.sqrt(sum) + 1; 

        // h小于数组length
        
        h = Math.min(h,citations.length);
        for(; h >=0; h--){
            int hCount = 0;
            for(int j = 0; j < citations.length; j++){
                if(citations[j] >= h){
                    hCount++;
                }
            }
            if(hCount >= h){
                return h;
            }
        }
        return 0;
    }
}

虽然我的方法控制了h的初值不至于太大,但是仍旧是o(n^2)的算法

核心优化为:先排序 ,这样就不用每次都进行遍历去数hCount了

排序之后从后往前进行查找,如果第i个大于h,那么第i到第n个都一定大于h

则h不断增大,i不断减小,,则此时为最大h。

因为h增大了多少次,i就减小了多少次,也就是说如果遇到了某个citations[i] 大于h,也其后面的数也肯定大于h

Java 复制代码
class Solution {
    public int hIndex(int[] citations) {
        Arrays.sort(citations);
        int h = 0;
        int i = citations.length -1;
        // 这里不能写citations[i] >= h
        // 因为如果现在是citations[i] == h
        // 则h已经是最大了,不能再增大了
        // 对于h+1,不能满足citations[i] > h+1
        while(i >=0 && citations[i] > h){
            h++;
            i--;
        }
        return h;
    }
}

由于排序导致时间复杂度为o(nlgn),而这里由于h不会超过citations的长度,则可以进行基数排序

Java 复制代码
class Solution {
    public int hIndex(int[] citations) {
        // 进行基数排序
        int arrayLength = citations.length;
        int[] sortedCitations = new int[arrayLength+1];
        Arrays.fill(sortedCitations,0);
        for(int i = 0; i < arrayLength; i++){
            sortedCitations[Math.min(citations[i],arrayLength)] += 1;
        }

        // 现在sortedCitations[i] = j表示数字i有j个
        
        // 记录当前遍历到的数字后面有多少数字
        int afterCount = 0;

        int i = arrayLength;

        while(i >= 0 && sortedCitations[i] + afterCount < i){
            // 当前i不能满足
            
            afterCount += sortedCitations[i];
            i--;


            // 我最开始的代码这里犯了一个重大错误:
            // 我原来是这样写的:
            // i--;
            // afterCount += sortedCitations[i];
        }

        return i;
    }
}

O(1) 时间插入、删除和获取随机元素

实现RandomizedSet 类:

RandomizedSet() 初始化 RandomizedSet 对象

bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。

bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。

int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。

你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1) 。

做不来,直接看官方题解:

Java 复制代码
class RandomizedSet {
    //变长数组 + 哈希表

    // 存储数据
    List<Integer> nums;
    // 存储每个元素在nums中的下标
    Map<Integer,Integer> indices;

    Random random;

    public RandomizedSet() {
        nums = new ArrayList<Integer>();
        indices = new HashMap<Integer,Integer>();
        random = new Random();
    }
    
    public boolean insert(int val) {
        if(indices.containsKey(val)){
            return false;
        }
        int index = nums.size();
        nums.add(val);
        indices.put(val,index);
        return true;
    }
    
    public boolean remove(int val) {
        if(!indices.containsKey(val)){
            return false;
        }
        int index = indices.get(val);

        // 将最后一个元素移动到remove的位置
        int last = nums.get(nums.size() -1);
        nums.set(index,last);
        indices.put(last,index);
        // 执行元素删除
        nums.remove(nums.size()-1);
        indices.remove(val);
        return true;
    }
    
    public int getRandom() {
        int randomIndex = random.nextInt(nums.size());
        return nums.get(randomIndex);
    }
}

对hashmap的初探

hashmap实际上是数组+链表+红黑树

  • 使用数组来进行hash桶的模拟
  • 如果产生碰撞,则用链表连起来
  • 如果链表过长,则转化为红黑树。这确保了在极端哈希碰撞情况下,也会得到n(lgn)的时间复杂度

一些实现细节:

  1. 哈希值的生成
    不直接对hashCode进行取余,而是整合了高低位的信息
Java 复制代码
static final int hash(Object key) {
    int h;
    // 无符号右移16位,即得到高位信息
    // 然后将高位信息和低位信息进行异或操作
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  1. 索引值的生成

使用位运算"交"来替换取余操作(n - 1) & hash

->需要确保n的值为2的整数幂,这样n-1就全是11111

否则会造成某些位用不了

除自身以外数组的乘积

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

请** 不要使用除法**,且在 O(n) 时间复杂度内完成此题。

示例 1:

输入: nums = [1,2,3,4]

输出: [24,12,8,6]

示例 2:

输入: nums = [-1,1,0,-3,3]

输出: [0,0,9,0,0]

我的解答:

Java 复制代码
class Solution {
    public int[] productExceptSelf(int[] nums) {
        // 构造两个辅助数组
        // preMul[i]代表[0,i)的元素乘起来是多少
        // nextMul[i]代表[i,n)的元素乘起来是多少
        // 然后将这两个数组里面对应元素相乘即可
        int arrayLength = nums.length;
        int[] preMul = new int[arrayLength];
        int[] nextMul = new int[arrayLength];

        // 从前往后逐渐累积,记录前缀乘积
        // 初始化第一个元素
        preMul[0] = 1;
        for(int i = 1; i < arrayLength; i++){
            preMul[i] = preMul[i-1] * nums[i-1];
        }


        // 从后往前,记录后缀乘积
        // 同理,最后一个元素也应该初始化为1
        nextMul[arrayLength-1] = 1;
        for(int i = arrayLength - 2; i >= 0; i--){
            nextMul[i] = nextMul[i+1] * nums[i+1];
        }

        // 相乘得到最终结果
        int[] result = new int[arrayLength];
        for(int i = 0; i < arrayLength; i++){
            result[i] = preMul[i] * nextMul[i];
        }
        return result;
        
    }
}

还可以进一步优化

Java 复制代码
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int arrayLength = nums.length;
        int[] result = new int[arrayLength];
        int accumulator = 1; // 累加变量
        //初始化头尾
        result[0] = 1;
        result[arrayLength-1]=1;
        for(int i = 1; i < arrayLength; i++){
            result[i] = accumulator * nums[i-1];
            accumulator*=nums[i-1];
        }

        // 从后往前,重置累加变量
        accumulator = 1;
        for(int i = arrayLength - 2; i >= 0; i--){
            result[i] *= nums[i+1] * accumulator;
            accumulator*=nums[i+1];
        }
        return result;
        
    }
}

加油站

题目描述:

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]

输出: 3

解释:

从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油

开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油

开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油

开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油

开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油

开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。

因此,3 可为起始索引。

我的解答:

Java 复制代码
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        // 暂时只想到o(n^2)的算法
        // 将value[i]计算为gas[i] - cost[i]
        // value[i]的意思为到节点i值不值
        // 例如value[i] = 2,表示到这里会多两升油,值
        // value[i] = -3,表示到这里会少三升油,不值
        // 第一次肯定要从非负的开始,然后依次遍历

        // 保证有解我就不写异常值判断了
        int arrayLength = gas.length;
        int[] value = new int[arrayLength];
        for(int i = 0; i < arrayLength; i++){
            value[i] = gas[i] - cost[i];
        }
        for(int i = 0; i < arrayLength; i++){
            if(value[i] >= 0){
                int acc = value[i];//累加器,已经走第一步了,加上i的价值
                int j = (i+1)%arrayLength;//循环走
                for(; j != i; j = (j+1)%arrayLength){
                    acc += value[j];
                    if(acc <=0 ){
                        break;
                    }
                }
                // 前者表示走回来了还有油
                // 后者表示走回来刚好没油
                if((j ==i) || ((j+1)%arrayLength == i && acc == 0)){
                    return i;
                }
            }
        }
        return -1;



    }
}

官方解答:

Java 复制代码
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        // "已经在谷底了,怎么走都是向上"
        // 首先判断能不能走回去
        // 如果gas的总和小于cost的总和,那么一定走不回去
        // 否则一定可以找到某种路径
        // value的定义不变,但是从最小value的下一个开始即可
        // 核心在于集中所有力量,克服最大的困难
        // 如果能回去,最大困难也是大于等于0的
        // 那么集中力量的过程中也不会中道崩殂
        int acc = 0; //累加和
        int minIndex = 0;
        int minAcc = Integer.MAX_VALUE;
        for(int i = 0 ; i < gas.length; i++){
            acc += gas[i] - cost[i];
            if(acc < minAcc){
                minAcc = acc;
                minIndex = i;
            }
        }
        if(acc >= 0){
            return (minIndex+1)%gas.length;
        }else{
            return -1;
        }
    }
}

分发糖果

题目描述:

你需要按照以下要求,给这些孩子分发糖果:

每个孩子至少分配到 1 个糖果。

相邻两个孩子中,评分更高的那个会获得更多的糖果。

请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

我最开始的做法

Java 复制代码
class Solution {
    public int candy(int[] ratings) {
        // 数组是由无数段 连增、连减 构成的
        // 对于连增段,例如从i到j为增
        // 则i要1个糖果,i+1要2个糖果,一直到j要j-i+1个糖果
        // 对于连减段,例如从p到q为减
        // 则q要q-p+1个糖果,p+1要2个糖果,一直到p要1个糖果
        // 对于平滑段,糖果数相同即可

        // 计算趋势
        int arrayLength = ratings.length;
        // 如果只有一个孩子,直接返回1
        if(arrayLength == 1){
            return 1;
        }

        // 不包括最后一个元素
        int[] r  = new int[arrayLength-1];
        for(int i = 0; i < arrayLength-1; i ++){
            r[i] = ratings[i+1] - ratings[i];
        }
        // 计算初始趋势
        int trend = 0;
        if(r[0] > 0){
            trend = 1;
        }
        if(r[0] > 0){
            trend =-1;
        }

        // 初始化糖果
        int candyCount = 1;
        // 总计需要的糖果
        int acc = 0;
        for(int i = 0; i < arrayLength -1; i++){
            if(r[i] == 0){
                // 进入"平"阶段
                trend = 0;
                acc += candyCount;
            }
            if(r[i] > 0){
                if(trend == 1){
                    // 持续上升
                    acc += candyCount;
                    candyCount++;
                }else if(trend == -1){
                    // 谷底
                    acc += candyCount;
                    candyCount++;
                    trend = 1;
                }else{
                    // trend == 0
                    // "平"到增
                    acc += candyCount;
                    candyCount++;
                    trend = 1;
                }
            }
            if(r[i] < 0){
                if(trend == 1){
                    // 峰
                    acc += candyCount;
                    candyCount--;
                    trend = -1;
                }else if(trend == -1){
                    // 持续下降
                    acc += candyCount;
                    candyCount--;
                }else{
                    // trend == 0
                    // 平到减
                    acc += candyCount;
                    candyCount --;
                    trend = -1;
                }
            }
        }
        // 处理最后
        acc += candyCount;

        return acc;

        
    }
}

存在问题的,没有全局视野,陷入局部矛盾。如果先是一个小谷底,再来一个大谷底,那么小谷底那里为1,大谷底就只能是负的了

借鉴加油站的思路?

Java 复制代码
class Solution {
    public int candy(int[] ratings) {
        // 求全局最小点然后赋为1,接着从左至右再遍历一次
        // 遇到增则candyCount值加一,减则candyCount值减一
        // 考虑可能会存在多个全局最小点,则记录全局最小的值
        // 如果又遇到了全局最小值,则给他一个糖果,candyCount值赋为1,继续向右遍历即可
        int arrayLength = ratings.length;
        int minValue = Integer.MAX_VALUE;
        int minIndex = 0;
        for(int i = 0; i < arrayLength; i++){
            if(minValue > ratings[i]){
                minValue = ratings[i];
                minIndex = i;
            }
        }
        int acc = 0;
        int candyCount = 1;
        // 从找到的一个全局最小点往左边
        for(int i = minIndex; i > 0; i--){
            acc += candyCount;
            // 如果又遇到了全局最小值则右边的约束满足了,
            // 左边从1开始也可以满足约束
            // 故,重置candyCount为1;
            if(ratings[i-1] == minValue){
                candyCount = 1;
            }
            if(ratings[i-1] > ratings[i]){
                candyCount++;
            }else if(ratings[i-1] < ratings[i]){
                candyCount--;
            } // 如果相等则不变
        }
        acc += candyCount; // 加上最左边的值

        // 从找到的一个全局最小点往右边
        
        // 这里超级容易错,不能i = minIndex,否则多算了依次这个全局最低点
        // 其次因为从minIndex+1开始,candyCount就应该为2而不是1了
        // 不对,还是需要进行初值判断,不应该为2,有可能下一个也是最小值
        if(minIndex < arrayLength-1){
            // 如果有下一个
            candyCount = ratings[minIndex+1] == minValue ? 1: 2;
        }else{
            candyCount = 1;
        }
        
        for(int i = minIndex+1; i < arrayLength-1; i++){
            acc += candyCount;
            // 如果又遇到了全局最小值则左边的约束满足了,
            // 右边从1开始也可以满足约束
            // 故,重置candyCount为1;
            if(ratings[i+1] == minValue){
                candyCount = 1;
            }
            if(ratings[i+1] > ratings[i]){
                candyCount++;
            }else if(ratings[i+1] < ratings[i]){
                candyCount--;
            } // 如果相等则不变
        }
        acc += candyCount; // 加上最右边的值
        return acc;
    }
}

还是存在问题,无法处理水平段,例如对于[1,2,2]会得到6([1,2,2])而非5([1,2,1])

官方解答:

Java 复制代码
class Solution {
    public int candy(int[] ratings) {
        // 这其实是局部约束,不应该像加油一样使用全局来进行处理
        // 实际上就是两个规则
        // 左规则:如果大于左边,则比左边的糖多
        // 右规则:如果大于右边,则比右边的糖多
        // 然后取最大的值即可
        int arrayLength = ratings.length;
        int[] left = new int[arrayLength];
        int[] right = new int[arrayLength];
        Arrays.fill(left,1);
        Arrays.fill(right,1);
        for(int i = 1; i < arrayLength; i++){
            if(ratings[i-1] < ratings[i]){
                left[i] = left[i-1]+1;
            }
        }
        // 右规则
        for(int i = arrayLength -2 ; i >= 0; i--){
            if(ratings[i] > ratings[i+1]){
                right[i] = right[i+1]+1;
            }
        }
        // 合并
        int acc = 0;
        for(int i = 0; i < arrayLength; i++){
            acc += Math.max(left[i],right[i]);
        }
        return acc;
    }
}

还可以就使用一个数组:

Java 复制代码
class Solution {
    public int candy(int[] ratings) {
        // 这其实是局部约束,不应该像加油一样使用全局来进行处理
        // 实际上就是两个规则
        // 左规则:如果大于左边,则比左边的糖多
        // 右规则:如果大于右边,则比右边的糖多
        // 然后取最大的值即可
        int arrayLength = ratings.length;
        int[] result = new int[arrayLength];
        Arrays.fill(result,1);
        for(int i = 1; i < arrayLength; i++){
            if(ratings[i-1] < ratings[i]){
                result[i] = result[i-1]+1;
            }
        }

        int acc = result[arrayLength-1];
        // 右规则
        for(int i = arrayLength -2 ; i >= 0; i--){
            if(ratings[i] > ratings[i+1]){
                result[i] = Math.max(result[i+1]+1,result[i]);
            }
            acc += result[i];
        }
        return acc;
    }
}

七个不同的符号代表罗马数字,其值如下:

符号
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则:

如果该值不是以 4 或 9 开头,请选择可以从输入中减去的最大值的符号,将该符号附加到结果,减去其值,然后将其余部分转换为罗马数字。

如果该值以 4 或 9 开头,使用 减法形式,表示从以下符号中减去一个符号,例如 4 是 5 (V) 减 1 (I): IV ,9 是 10 (X) 减 1 (I):IX。仅使用以下减法形式:4 (IV),9 (IX),40 (XL),90 (XC),400 (CD) 和 900 (CM)。

只有 10 的次方(I, X, C, M)最多可以连续附加 3 次以代表 10 的倍数。你不能多次附加 5 (V),50 (L) 或 500 (D)。如果需要将符号附加4次,请使用 减法形式。

给定一个整数,将其转换为罗马数字。

我的代码:

Java 复制代码
class Solution {
    public String intToRoman(int num) {
        // 罗马数字不会超过3999

        if(num > 3999){
            return "error! num should less than 4000";
        }
        // 那么我们就从1000开始除即可
        int division = num / 1000;
        num %= 1000;
        StringBuilder builder = new StringBuilder();
        // 我觉得不用写那么多判断,直接依次处理就好
        // 千位不可能是4或9
        builder.append("M".repeat(division));


        // 处理100
        division = num / 100;
        num %= 100;
        if(division < 4){
            builder.append("C".repeat(division));
        }else if(division == 4){
            builder.append("CD");
        }else if(division == 9){
            builder.append("CM");
        }else{
            builder.append("D").append("C".repeat(division - 5));
        }

        // 处理10
        division = num / 10;
        num %= 10;
        if(division < 4){
            builder.append("X".repeat(division));
        }else if(division == 4){
            builder.append("XL");
        }else if(division == 9){
            builder.append("XC");
        }else{
            builder.append("L").append("X".repeat(division - 5));
        }

        // 处理1
        division = num / 1;
        num %= 1;
        if(division < 4){
            builder.append("I".repeat(division));
        }else if(division == 4){
            builder.append("IV");
        }else if(division == 9){
            builder.append("IX");
        }else{
            builder.append("V").append("I".repeat(division - 5));
        }

        return builder.toString();

    }
}

有点简单粗暴,实际上罗马数字的核心就是先取大的,再取小的,只是对于4和9有特殊规则而已

Java 复制代码
class Solution {
    private Map<Integer,String> map = new LinkedHashMap<Integer,String>(){{
        put(1000,"M");
        put(900,"CM");
        put(500,"D");
        put(400,"CD");
        put(100,"C");
        put(90,"XC");
        put(50,"L");
        put(40,"XL");
        put(10,"X");
        put(9,"IX");
        put(5,"V");
        put(4,"IV");
        put(1,"I");
    }};


    public String intToRoman(int num) {
        StringBuilder builder = new StringBuilder();
        for(Map.Entry<Integer,String> entry : map.entrySet()){
            int val = entry.getKey();
            while(num >= val){
                builder.append(entry.getValue());
                num -= val;
            }
        }
        return builder.toString();

    
    }
}

但是这玩意儿没我的简单粗暴法快

接雨水

题目描述:

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

我的解法:

Java 复制代码
class Solution {
    public int trap(int[] height) {
        // 有一个很简单朴素的"一格一格沉没"的算法
        // 每次先去掉头尾的小于等于0的数,这样水就可以兜住
        // 然后对每个值减一,如果小于等于0,则这里可以存一份水
        // 然后继续去头尾重复直至全被淹没
        
        // 实际采用两个指针来记录那些是能要的
        int left = 0;
        int right = height.length - 1;
        int acc = 0;
        while(left <= right){
            // 去头尾
            while(left <= right && height[left] <= 0 ){
                left++;
            }
            while(left <= right && height[right] <= 0 ){
                right--;
            }
            // 全都减一
            for(int i = left; i <= right; i++){
                height[i]--;
                if(height[i] < 0){
                    acc++;
                }
            }
        }
        return acc;

    }
}

假设数组长度为n,数组的最大数目为M,则时间复杂度为O(nM)

官方做法

Java 复制代码
class Solution {
    public int trap(int[] height) {
        // 采用全后缀算法
        // 使用两个辅助数组记录前面的最大桶高和后面的最大桶高

        int length = height.length;
        int[] preHeight = new int[length];
        Arrays.fill(preHeight,0);
        int[] sufHeight = new int[length];
        Arrays.fill(sufHeight,0);


        // preHeight[i] 记录第i个位置及其之前的 最大高度
        int maxHeight = 0;
        for(int i = 0; i < length; i++){
            if(height[i] > maxHeight){
                maxHeight = height[i];
            }
            preHeight[i] = maxHeight;
        }

        // 同理,sufHeight记录第i个位置及其之后的 最大高度
        maxHeight = 0;
        for(int i = length - 1; i >= 0; i--){
            if(height[i] > maxHeight){
                maxHeight = height[i];
            }
            sufHeight[i] = maxHeight;
        }

        // 从左到右进行遍历,每个格子能装的水
        // 等于min(左边最高,右边最高) - 自己的高度

        int acc = 0;
        for(int i = 0; i < length; i++){
            acc += Math.min(preHeight[i],sufHeight[i]) - height[i];
        }
        return acc;
    }
}

还有去除辅助数组的更优的解法:

Java 复制代码
class Solution {
    public int trap(int[] height) {
        // 还可以进行简化,使用指针代替辅助数组
        // 由于前后缀在遍历过程中是不见减小的
        // 而且存在短板效应
        // 即对于前后缀中较大的部分,比如说现在suf为10,pre为2
        // 还不能计算suf对应的单元格,因为有可能pre遍历到后面变大了
        // 比如说pre遍历成了8,那么就实际可以接更多水
        // 但是对于其中较小的,即pre,即使后面suf变大,也是按照小的pre来算
        // 所有我们使用双向指针,相遇为止,每次算小的那个格子然后移动指针
        int length = height.length;
        int preMax = 0 ,sufMax = 0;
        int acc = 0;
        for(int left = 0, right = length - 1; left <= right;    ){
            preMax = Math.max(preMax,height[left]);
            sufMax = Math.max(sufMax,height[right]);
            if(preMax < sufMax){
                acc += preMax - height[left];
                left++;
            }else{
                acc += sufMax - height[right];
                right--;
            }
        }
        return acc;


    }
}
相关推荐
尋有緣3 小时前
力扣1355-活动参与者
大数据·数据库·leetcode·oracle·数据库开发
姓蔡小朋友3 小时前
算法-滑动窗口
算法
君义_noip3 小时前
信息学奥赛一本通 2134:【25CSPS提高组】道路修复 | 洛谷 P14362 [CSP-S 2025] 道路修复
c++·算法·图论·信息学奥赛·csp-s
kaikaile19954 小时前
基于拥挤距离的多目标粒子群优化算法(MO-PSO-CD)详解
数据结构·算法
不忘不弃4 小时前
求两组数的平均值
数据结构·算法
leaves falling4 小时前
迭代实现 斐波那契数列
数据结构·算法
珂朵莉MM4 小时前
全球校园人工智能算法精英大赛-产业命题赛-算法巅峰赛 2025年度画像
java·人工智能·算法·机器人
Morwit4 小时前
*【力扣hot100】 647. 回文子串
c++·算法·leetcode
DonnyCoy4 小时前
Android性能之数据结构
数据结构
天赐学c语言4 小时前
1.7 - 删除排序链表中的重要元素II && 哈希冲突常用解决冲突方法
数据结构·c++·链表·哈希算法·leecode