面试经典150题0423

本文解析了LeetCode中的多个经典问题,涉及数组操作(多数元素、轮转数组)、股票交易策略(买卖股票最佳时机)、动态规划、哈希表优化以及特殊条件下的数组乘积计算,展示了算法在解决实际问题中的应用。
摘要由CSDN通过智能技术生成

面试经典150题

Leetcode169 多数元素

数组排序法:直接将数组排序,因为多数元素出现的次数大于n/2,所以在低n/2个位置一定是结果。

class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length / 2];
    }
}

摩尔投票法:核心理念是票数的正负抵消,时间和空间复杂度分别为O(N)O(1),最佳解法。

摩尔投票解决问题:如果在任意多的候选人中(选票无序),选出获得票数最多的那个。

算法分为两个阶段:

  • 对抗阶段:分属两个候选人的票数进行两两对抗抵消。
  • 计数阶段:计算对抗结果中最后留下的候选人票数是否有效。

遍历投票数组,将当前票数最多的候选人与其获得的(抵消后)票数分别存储在candidatecount中。

  • count==0代表candidate空缺,直接使用当前数作为候选人,并令count++
  • count!=0代表当前candidate的票数还没有被完全抵消,count--
public static int majority(int[] nums){
    int candidate = 0, count = 0;
    for(int i = 0; i < nums.length; i++){
        if(count == 0){
            candidate = nums[i];
            count++;
        }
        else {
            if(candidate == nums[i]){
                count++;
            }
            else {
                count--;
            }
        }
    }
    return candidate;
}

题目说明“给定数组总是存在多数元素”,故不需要考虑数组不存在众数的情况。否则需要加上验证环节,遍历一遍数组,统计candidate的数量是否超过数组长度的一半。

摩尔投票法升级:

  • 如果至多选一个代表,那么他的票数至少要超过1/2的票数
  • 如果至多选两个代表,那么他的票数至少要超过1/3的票数
  • 如果至多选m个代表,那么他的票数至少要超过1/(m+1)的票数
Leetcode189 轮转数组
  • 首先对整个数组进行反转,这样原来数组中需要反转的子数组就会跑到最前面。
  • 选择前k个元素作为一个子数组进行反转,再将剩余的n-k个数组进行反转
public static void rotate(int[] nums, int k){
    k = k % nums.length;
    // 整体反转
    rotateArrays(nums, 0, nums.length-1);
    // 划分:[0,k-1],[k,nums.length-1]
    // 各自反转
    rotateArrays(nums, 0, k-1);
    rotateArrays(nums, k, nums.length-1);
}

public static void rotateArrays(int[] nums, int left, int right){
    while (left < right){
        int tmp = nums[left];
        nums[left] = nums[right];
        nums[right] = tmp;
        left++;
        right--;
    }
}
Leetcode121 买卖股票的最佳时机

使用minPrice维护前面i天的最小值,作为买入价格。由于只能买卖一次,在遍历过程中,维护prices[i]-minPrice的最大值。

public static int maxProfit(int[] prices){
    int minPrice = prices[0];
    int maxValue = 0;
    for(int i = 1; i < prices.length; i++){
        minPrice = Math.min(minPrice, prices[i]);
        maxValue = Math.max(maxValue, prices[i] - minPrice);
    }
    return maxValue;
}
Leetcode122 买卖股票的最佳时机Ⅱ

动态规划:

  • dp[i][0]表示下标为i的这一天,持股状态为买入状态,手上拥有的最大现金数。
  • dp[i][1]表示下标为i的这一天,持股状态为卖出状态,手上拥有的最大现金数。

状态转移过程:

  • 状态从持有现金开始,到最后一天仍然是持有现金状态(卖出股票的状态)
  • 每一天状态可以转移,也可以不同。

由于不限制交易次数,除了最后一天,每一天的状态可能不变化,也可能转移。

确定初始值:

  • 起始这一天持有股票,dp[0][0]=-prices[i]
  • 起始这一天什么都不做,dp[0][1]=0
public static int maxProfit(int[] prices){
    int[][] dp = new int[prices.length][2];
    // dp[i][0]:第i天买入状态
    // dp[i][1]:第i天卖出状态
    dp[0][0] = -prices[0];
    for(int i = 1; i < prices.length; i++){
        dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] - prices[i]);
        dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i]);
    }
    return Math.max(dp[prices.length-1][0], dp[prices.length-1][1]);
}
Leetcode055 跳跃游戏

遍历数组,记录当前位置能够到达的最远位置,并更新最远到达位置rightBoundary

当前遍历元素下标应该小于等于rightBoundary且小于数组长度。

最后判断rightBoundary是否大于等于nums.length - 1.

public static boolean canJump(int[] nums){
    int rightBoundary = 0;
    int pos = 0;
    while (pos < nums.length && pos <= rightBoundary){
        rightBoundary = Math.max(rightBoundary, pos + nums[pos]);
        pos++;
    }
    return rightBoundary >= nums.length - 1;
}
Leetcode045 跳跃游戏Ⅱ

贪心:每次在可以到达的范围内选择可以使得跳的更远的位置

如下,开始位置最远可以跳两个单位,可到达范围为橙色。在下一条中,先到达能够跳3个单位的节点,能够保证到达更远的位置。

image-20240423174918009

每一步都是局部最优,最后实现全局最优。

public static int jump(int[] nums){
    int step = 0, cover = 0, end = 0;
    for(int i = 0; i < nums.length; i++){
        // 下一条能够覆盖的最大边界,通过访问本条能够到达的范围进行更新
        cover = Math.max(cover, nums[i] + i);
        if(i == end){
            // 更新边界,增加步数
            end = cover;
            step++;
        }
    }
    return step;
}
Leetcode274 H指数

设n为研究者发表的论文数量。h不可能超过n,多以对于引用次数大于n的论文,在统计的时候可以看成引用次数等于n的论文。

创建一个长为n+1cnt数组,统计min(citations[i],n)的出现次数。

s为引用次数>=i的论文数量,需要算出满足s>=i的最大的i

为了计算多少论文的引用次数>=i,可以从i=n开始倒序循环每次循环,把cnt[i]加入到s中。当s>=i时,此时的i就是满足s>=i的最大的i

public static int hIndex(int[] citations){
    int n = citations.length;
    // count[i]表示引用次数为i的论文数量
    int[] count = new int[n+1];
    for(int c : citations){
        count[Math.min(c, n)]++;
    }
    // 引用次数大于等于i的论文数量
    int s = 0;
    // 从后向前遍历
    for(int i = n; i >= 1; i--){
        s += count[i];
        if(s >= i){
            return i;
        }
    }
    return 0;
}
Leetcode380 O(1)时间插入、删除和获取随机元素

对于insertremove操作使用哈希表实现O(1)时间复杂度。对于getRandom操作,在一个数组内使用随机下标进行返回。

将哈希表设计为:使用value作为键值,使用数组下标loc作为值。

申请一个足够大的数组nums(根据题目要求,数据范围为2*105),使用变量idx记录当前使用到哪一位(即下标在[0,idx]范围内均存在值)

  • insert操作:使用哈希表判断value是否存在,存在的话返回false,否则将其添加到nums中,并更新idx,同时更新哈希表。
  • remove操作:使用哈希表判断值value是否存在,不存在返回false,否则将value从哈希表中删除,同时取出在nums中的下标loc,然后将nums[idx]赋值到loc位置,并更新idx(表示将原本处于loc位置的元素删除),同时更新原本位于idx位置的数在哈希表中的值为loc(若locidx相等,说明删除的是最后一个元素,这一步可以跳过)
  • getRandom操作:确保了[0,idx]均为存活值,因此可以直接在[0,idx+1]范围内进行随机即可。
public A0423RandomizedSet(){

}
private static int[] nums = new int[200001];
Map<Integer, Integer> map = new HashMap<>();
int idx = -1;
Random random = new Random();
public boolean insert(int val){
    if(map.containsKey(val)){
        return false;
    }
    // 插入
    map.put(val, ++idx);
    nums[idx] = val;
    return true;
}

public boolean remove(int val){
    if(!map.containsKey(val)){
        return false;
    }
    // val在nums数组中存放的下标
    int loc = map.remove(val);
    if(idx == loc){
        // 删除的最后一个元素
        idx--;
        return true;
    }
    // 删除数组中的中间元素
    // TODO:将数组最后元素赋值到删除元素在数组中的位置,同时更改map中val对应的loc
    nums[loc] = nums[idx];
    idx--;
    map.put(nums[loc], loc);
    return true;
}

public int getRandom(){
    return nums[random.nextInt(idx+1)];
}
Leetcode238 除自身以外数组的乘积

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

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

原数组2345
前缀122 * 32 * 3 * 4
后缀3 * 4 * 54 * 551
结果60403024
public static int[] productExceptSelf(int[] nums){
    int[] res = new int[nums.length];
    int tmp = 1;
    res[0] = tmp;
    for(int i = 1; i < nums.length; i++){
        tmp *= nums[i-1];
        res[i] = tmp;
    }
    tmp = 1;
    for (int i = nums.length - 2; i >= 0 ; i--) {
        tmp *= nums[i+1];
        res[i] *= tmp;
    }
    return res;
}
  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值