算法实战:五道经典数组问题详解与思维拓展

引言:数组问题的艺术

数组是算法竞赛和面试中最基础也是最常见的数据结构。掌握数组问题的解决技巧,不仅能够帮助我们在面试中脱颖而出,更能培养我们的计算思维和问题分析能力。本文将深入解析五道经典的数组问题,从简单到中等难度,涵盖统计、搜索、排序、位运算和贪心算法等多个重要领域。

1. 去掉最低工资和最高工资后的工资平均值

问题描述

给定一个唯一整数数组表示员工工资,去掉最低工资和最高工资后,计算剩余工资的平均值。

示例分析:

输入:salary = [4000, 3000, 1000, 2000]

输出:2500.00000

解释:去掉1000(最低)和4000(最高)后,(2000+3000)/2 = 2500

解法一:单次遍历法

class Solution {
    public double average(int[] salary) {
        double sum = 0;
        double maxValue = Integer.MIN_VALUE;
        double minValue = Integer.MAX_VALUE;
        
        for (int num : salary) {
            sum += num;
            maxValue = Math.max(maxValue, num);
            minValue = Math.min(minValue, num);
        }
        
        return (sum - maxValue - minValue) / (salary.length - 2);
    }
}

解法二:排序法

class Solution {
    public double average(int[] salary) {
        Arrays.sort(salary);
        double sum = 0;
        for (int i = 1; i < salary.length - 1; i++) {
            sum += salary[i];
        }
        return sum / (salary.length - 2);
    }
}

算法对比

方法

时间复杂度

空间复杂度

适用场景

单次遍历

O(n)

O(1)

推荐

,效率最高

排序法

O(n log n)

O(1)

代码简单,小数据量

思维拓展

这个问题教会我们如何在单次遍历中同时维护多个统计量,这是处理实时数据流的常用技巧。

2. 两个数组的最小公共整数

问题描述

给定两个非降序数组,找到它们的最小公共整数。如果没有公共整数,返回-1。

示例分析:

输入:nums1 = [1,2,3], nums2 = [2,4]

输出:2

解释:公共整数是2

解法一:双指针法

class Solution {
    public int getCommon(int[] nums1, int[] nums2) {
        int i = 0, j = 0;
        
        while (i < nums1.length && j < nums2.length) {
            if (nums1[i] == nums2[j]) {
                return nums1[i];
            } else if (nums1[i] < nums2[j]) {
                i++;
            } else {
                j++;
            }
        }
        
        return -1;
    }
}

解法二:二分搜索法

class Solution {
    public int getCommon(int[] nums1, int[] nums2) {
        // 对较短的数组进行遍历,在较长的数组中二分搜索
        for (int num : nums1) {
            if (binarySearch(nums2, num)) {
                return num;
            }
        }
        return -1;
    }
    
    private boolean binarySearch(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] == target) {
                return true;
            } else if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return false;
    }
}

算法对比

方法

时间复杂度

空间复杂度

适用场景

双指针

O(m+n)

O(1)

推荐

,利用有序特性

二分搜索

O(m log n)

O(1)

一个数组远大于另一个

思维拓展

这个问题展示了如何利用"有序"这一特性来优化算法,双指针技巧在合并有序数组、寻找交集等问题中非常有用。

3. 判断能否形成等差数列

问题描述

判断数组能否重新排列形成等差数列。

示例分析:

输入:arr = [3,5,1]

输出:true

解释:可重排为[1,3,5],差值为2

解法:排序验证法

class Solution {
    public boolean canMakeArithmeticProgression(int[] arr) {
        if (arr.length < 2) return true;
        
        Arrays.sort(arr);
        int diff = arr[1] - arr[0];
        
        for (int i = 2; i < arr.length; i++) {
            if (arr[i] - arr[i - 1] != diff) {
                return false;
            }
        }
        
        return true;
    }
}

边界情况处理

class Solution {
    public boolean canMakeArithmeticProgression(int[] arr) {
        if (arr.length <= 2) return true;
        
        Arrays.sort(arr);
        int expectedDiff = arr[1] - arr[0];
        
        for (int i = 1; i < arr.length - 1; i++) {
            int actualDiff = arr[i + 1] - arr[i];
            if (actualDiff != expectedDiff) {
                return false;
            }
        }
        
        return true;
    }
}

思维拓展

这个问题体现了排序在问题求解中的威力。很多时候,将数据重新组织可以大大简化问题的复杂度。

4. 根据前缀异或构造数组

问题描述

根据前缀异或数组还原原数组。
异或运算性质复习:
  • 交换律:a ^ b = b ^ a
  • 结合律:(a ^ b) ^ c = a ^ (b ^ c)
  • 自反性:a ^ b ^ b = a
示例分析:
输入:pref = [5,2,0,3,1]
输出:[5,7,2,3,2]
解释:
pref[0] = 5 = arr[0]
pref[1] = 2 = 5 ^ arr[1] ⇒ arr[1] = 5 ^ 2 = 7

解法:异或性质应用

class Solution {
    public int[] findArray(int[] pref) {
        int[] res = new int[pref.length];
        res[0] = pref[0];
        
        for (int i = 1; i < pref.length; i++) {
            res[i] = pref[i - 1] ^ pref[i];
        }
        
        return res;
    }
}

数学推导

已知:pref[i] = arr[0] ^ arr[1] ^ ... ^ arr[i]

可得:pref[i-1] = arr[0] ^ arr[1] ^ ... ^ arr[i-1]

因此:arr[i] = pref[i-1] ^ pref[i]

因为:pref[i-1] ^ pref[i] = pref[i-1] ^ (pref[i-1] ^ arr[i]) = arr[i]

思维拓展

这个问题展示了位运算在算法中的巧妙应用。理解异或运算的性质对于解决这类问题至关重要。

5. 最大硬币数目

问题描述

3n堆硬币,每轮选3堆,你总是拿第二多的,求能拿到的最大硬币数。

示例分析:

输入:piles = [2,4,1,2,7,8]

输出:9

策略:配对(2,7,8)和(1,2,4),得到7+2=9

解法:贪心算法

class Solution {
    public int maxCoins(int[] piles) {
        Arrays.sort(piles);
        int n = piles.length;
        int rounds = n / 3;
        int result = 0;
        
        // 每次取倒数第二大的数
        for (int i = n - 2; i >= rounds; i -= 2) {
            result += piles[i];
        }
        
        return result;
    }
}

策略分析

public class CoinStrategy {
    public void explainStrategy(int[] piles) {
        Arrays.sort(piles);
        // 排序后: [1, 2, 2, 4, 7, 8]
        
        // 最优策略:
        // 让Alice拿最大的(8),Bob拿最小的(1),我拿第二大的(7)
        // 然后Alice拿次大的(4),Bob拿次小的(2),我拿第三大的(2)
        // 总计:7 + 2 = 9
    }
}

算法证明

每次我们选择:
  • 当前最大的(给Alice)
  • 当前次大的(我们自己拿)
  • 当前最小的(给Bob)
这样能确保我们每次都能拿到尽可能大的第二值。

思维拓展

这个问题是典型的贪心算法应用。通过数学分析,我们发现排序后每隔一个取值的策略是最优的。

总结:算法思维模式

1. 统计思维

  • 问题1:如何在单次遍历中维护多个统计量
  • 关键技巧:同时跟踪最大值、最小值和总和

2. 双指针技巧

  • 问题2:利用有序特性优化搜索
  • 应用场景:有序数组的合并、交集、子数组问题

3. 排序预处理

  • 问题3:通过排序简化问题
  • 适用情况:当顺序不重要,但相对关系重要时

4. 位运算魔法

  • 问题4:利用异或性质进行高效计算
  • 重要性质:自反性、交换律、结合律

5. 贪心策略

  • 问题5:通过局部最优达到全局最优
  • 证明方法:数学归纳法或反证法

实战建议

  1. 理解问题本质:每个问题都有其核心的数学模型
  2. 利用数据特性:有序、唯一等特性可以大大优化算法
  3. 边界情况考虑:空数组、单元素、极值等情况
  4. 复杂度分析:根据数据规模选择合适的算法
通过这五个问题的学习,我们不仅掌握了具体的解题技巧,更重要的是培养了分析问题和设计算法的思维能力。这些技能将在解决更复杂的算法问题时发挥重要作用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值