算法解析:从糖果分配到平衡二叉搜索树

在编程面试和算法学习中,我们经常会遇到各种有趣的问题。本文将通过五个经典的算法问题,带你深入理解不同的解题思路和技巧。每个问题都有其独特的挑战,我们将逐一解析并提供清晰的解决方案。

1. 糖果分配问题

问题描述

Alice有n枚糖,每枚糖有一个类型。医生建议她只吃掉n/2枚糖(n为偶数)。Alice希望在遵循医生建议的情况下,尽可能吃到最多不同种类的糖。

示例说明:

示例 1:

输入:candyType = [1,1,2,2,3,3]

输出:3

解释:Alice只能吃 6/2 = 3 枚糖,由于只有3种糖,她可以每种吃一枚。

示例 2:

输入:candyType = [1,1,2,3]

输出:2

解释:Alice只能吃 4/2 = 2 枚糖,不管她选择吃的种类是 [1,2]、[1,3] 还是 [2,3],她只能吃到两种不同类的糖。

示例 3:

输入:candyType = [6,6,6,6]

输出:1

解释:Alice只能吃 4/2 = 2 枚糖,尽管她能吃2枚,但只能吃到1种糖。

解题思路

这是一个典型的集合应用问题。关键在于理解:
  • Alice最多能吃n/2枚糖
  • 她能吃到的种类数不会超过总种类数
  • 因此,答案是总种类数和n/2中的较小值

代码实现

class Solution {
    public int distributeCandies(int[] candyType) {
        Set<Integer> set = new HashSet<Integer>();
        for (int candy : candyType) {
            set.add(candy);
        }
        return Math.min(set.size(), candyType.length / 2);
    }
}

复杂度分析

  • 时间复杂度:O(n),需要遍历所有糖果
  • 空间复杂度:O(n),最坏情况下需要存储所有类型的糖果

2. 只出现一次的数字 II

问题描述

给定一个整数数组,除某个元素仅出现一次外,其余每个元素都恰出现三次。找出那个只出现一次的元素。
示例说明:
示例 1:

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

输出:3

示例 2:

输入:nums = [0,1,0,1,0,1,99]

输出:99

解题思路

这个问题有多种解法:
  1. 哈希表法:统计每个数字的出现频率
  2. 位运算法:利用数字的二进制表示,统计每位出现的次数

哈希表实现

class Solution {
    public int singleNumber(int[] nums) {
        Map<Integer, Integer> freq = new HashMap<Integer, Integer>();
        for (int num : nums) {
            freq.put(num, freq.getOrDefault(num, 0) + 1);
        }
        int ans = 0;
        for (Map.Entry<Integer, Integer> entry : freq.entrySet()) {
            int num = entry.getKey(), occ = entry.getValue();
            if (occ == 1) {
                ans = num;
                break;
            }
        }
        return ans;
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

3. 将有序数组转换为二叉搜索树

问题描述

将按升序排列的整数数组转换为一棵高度平衡的二叉搜索树。
示例说明:
示例 1:

解题思路

平衡二叉搜索树的关键特性是:
  • 对于每个节点,左子树的所有值小于节点值
  • 右子树的所有值大于节点值
  • 左右子树的高度差不超过1
由于数组已排序,我们可以使用分治策略:
  1. 选择中间元素作为根节点
  2. 递归构建左子树和右子树

代码实现

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return helper(nums, 0, nums.length - 1);
    }
    
    public TreeNode helper(int[] nums, int left, int right) {
        if (left > right) {
            return null;
        }
        // 总是选择中间位置左边的数字作为根节点
        int mid = (left + right) / 2;
        TreeNode root = new TreeNode(nums[mid]);
        root.left = helper(nums, left, mid - 1);
        root.right = helper(nums, mid + 1, right);
        return root;
    }
}

复杂度分析

  • 时间复杂度:O(n),每个元素只访问一次
  • 空间复杂度:O(log n),递归栈的深度

4. 跳跃游戏

问题描述

给定一个非负整数数组,你最初位于数组的第一个下标。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。
示例说明:
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳1步,从下标0到达下标1,然后再从下标1跳3步到达最后一个下标。

示例 2:

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

输出:false

解释:无论怎样,总会到达下标为3的位置。但该下标的最大跳跃长度是0,所以永远不可能到达最后一个下标。

解题思路

使用贪心算法:
  • 维护一个变量rightmost,表示当前能到达的最远位置
  • 遍历数组,更新rightmost
  • 如果rightmost能覆盖最后一个位置,返回true

代码实现

public class Solution {
    public boolean canJump(int[] nums) {
        int n = nums.length;
        int rightmost = 0;
        for (int i = 0; i < n; i++) {
            if (i <= rightmost) {
                rightmost = Math.max(rightmost, i + nums[i]);
                if (rightmost >= n - 1) {
                    return true;
                }
            }
        }
        return false;
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

5. 第三大的数

问题描述

给定一个非空数组,返回此数组中第三大的数。如果不存在,则返回数组中最大的数。
示例说明:
示例 1:

输入:[3, 2, 1]

输出:1

解释:第三大的数是1。

示例 2:

输入:[1, 2]

输出:2

解释:第三大的数不存在,所以返回最大的数2。

示例 3:

输入:[2, 2, 3, 1]

输出:1

解释:注意,要求返回第三大的数,是指在所有不同数字中排第三大的数。

此例中存在两个值为2的数,它们都排第二。在所有不同数字中排第三大的数为1。

解题思路

使用三个变量来记录前三大数字:
  • 遍历数组,维护三个变量a、b、c,分别表示第一、第二和第三大的数
  • 注意处理重复值和null值的情况

代码实现

class Solution {
    public int thirdMax(int[] nums) {
        Integer a = null, b = null, c = null;
        for (int num : nums) {
            if (a == null || num > a) {
                c = b;
                b = a;
                a = num;
            } else if (a > num && (b == null || num > b)) {
                c = b;
                b = num;
            } else if (b != null && b > num && (c == null || num > c)) {
                c = num;
            }
        }
        return c == null ? a : c;
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

总结

通过这五个算法问题及其具体示例,我们可以看到不同问题需要不同的解决策略:
  1. 集合应用:糖果分配问题展示了如何利用集合特性快速解决问题,示例清晰地说明了不同情况下的输出结果
  2. 频率统计:只出现一次的数字问题展示了哈希表在频率统计中的应用,示例帮助我们理解"出现三次"的具体含义
  3. 分治策略:平衡二叉搜索树问题展示了分治思想在树结构中的应用,多个示例输出说明了平衡BST的不唯一性
  4. 贪心算法:跳跃游戏问题展示了贪心思想在路径寻找中的应用,示例生动地展示了能否到达终点的两种情况
  5. 变量维护:第三大的数问题展示了如何通过有限变量维护状态信息,示例特别强调了重复值的处理
每个问题的示例都帮助我们更好地理解题目要求和算法行为。掌握这些基础算法思想和技巧,对于解决更复杂的算法问题至关重要。每种方法都有其适用场景,理解这些场景能帮助我们在面对新问题时快速找到合适的解决方案。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值