《老程序员的快乐刷题时代》题一:找单独的数

一、写在开头

哈喽,兄弟们!最近Build哥不是在搞那个年度人气创作者嘛(随便搞搞,嘿嘿,好心人给投下票呗),然后有个活动是刷算法题可以获得额外投票机会,于是乎,每天早上开工前的20分钟,俺就开始整上算法了,遥想上一次正儿八经的刷这种题还要追溯到五六年前,但是!现在又回首再刷,突然找到了年少轻狂时的感觉,记得那时候在实验室里,刷hard级别的算法题,一晚上至少脱发10根(哈哈哈)。

既然开始刷上了,俺就做了一个大胆的决定,整个刷算法题的专题,名为:《老程序员的快乐刷题时代》,更新自己每次刷题的过程和感悟。话不多说,直接开干!

二、《老程序员的快乐刷题时代》题一:找单独的数

2.1 问题描述

在一个班级中,每位同学都拿到了一张卡片,上面有一个整数。有趣的是,除了一个数字之外,所有的数字都恰好出现了两次。现在需要你帮助班长小明快速找到那个拿了独特数字卡片的同学手上的数字是什么。

要求:

设计一个算法,使其时间复杂度为 O(n),其中 n 是班级的人数。 尽量减少额外空间的使用,以体现你的算法优化能力。

2.2 测试样例

样例1:

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

输出:4

解释:拿到数字 4 的同学是唯一一个没有配对的。

样例2:

输入:cards = [0, 1, 0, 1, 2]

输出:2

解释:数字 2 只出现一次,是独特的卡片。

样例3:

输入:cards = [7, 3, 3, 7, 10]

输出:10

解释:10 是班级中唯一一个不重复的数字卡片。

2.3 约束条件

  • 1 ≤ cards.length ≤ 1001
  • 0 ≤ cards[i] ≤ 1000
  • 班级人数为奇数
  • 除了一个数字卡片只出现一次外,其余每个数字卡片都恰好出现两次

三、解题过程

3.1 分析与解决(源码)

首先,咱们拿到这样的算法题后,通读一遍后进行题干分析,这里给的所有条件看下来,题目意思还是比较清晰明确的,稍微有点难点的就是时间复杂度要求为O(n),然后空间复杂度尽可能小。

拿到这种题目的话,咱们可以暂时不管时间复杂度与空间复杂度,先想一个最简单粗暴的去把问题解决了,这个和日后的工作也是,先把问题解决了,再去考虑优化办法,空想会浪费太多的时间。

一个数组中,元素个数为奇数,有且仅有一个元素出现一次,其他元素均只出现两次。

思路1:

第一个能想到的笨方法就是用遍历数组的方式去比较,两个for循环,比较出仅出现一次的那个数,这样可以把数找到,但是!这种方式的时间复杂度是n^2,并不满足要求。所以咱们在这里也就不去贴它的源码了。

思路2:

如果我们不想过多的借助其他的数据结构的话,咱们还可以通过排序之后去做处理,根据样例,在排序之后,那个单数要么在数组的两头,要么在中间的某个位置,且与前后元素都不相同。

「【源码】」

java 复制代码
public class Test  {

    public static int solution(int[] arrays){

        //将数组进行排序

        Arrays.sort(arrays);

        //处理单数出现在头部或者数组中仅有1个元素情况

        if (arrays.length == 1 || arrays[0] != arrays[1]) {

            return arrays[0];

        }

        //处理单数出现在尾部的情况

        if (arrays[arrays.length - 1] != arrays[arrays.length - 2]) {

            return arrays[arrays.length - 1];

        }

        // 处理单数在中间时的情况

        for (int i = 1; i < arrays.length - 1; i++) {

            if (arrays[i] != arrays[i - 1] && arrays[i] != arrays[i + 1]) {

                return arrays[i];

            }

        }

        throw new IllegalArgumentException("No unique value found");

    }

    public static void main(String[] args) {

        //测试用例

        System.out.println(solution(new int[]{1, 1, 2, 2, 3, 3, 4, 5, 5}) == 4);

        System.out.println(solution(new int[]{0, 1, 0, 1, 2}) == 2);

    }

}

思路3:

以上是不借助其他的数据结构的,其实在开发中,我们几乎都会借助各种数据结构,去快速解决问题,这里我们可以采用哈希表去处理,将每个数字作为Key存入,然后将数字出现的次数当做Vaule值存入,然后再去遍历这个哈希表,找到Value=1的那个Key,即为要找的单数。

「【源码】」

java 复制代码
public class Test  {

    public static int solution(int[] arrays){

        //创建一个hashmap计数

        HashMap<Integer, Integer> map = new HashMap<>();

        //计数元素出现的次数

        for (int array : arrays) {

            map.put(array, map.getOrDefault(array,0)+1);

        }

        //查找出现次数1的数字

        for (Integer integer : map.keySet()) {

            if(map.get(integer) ==1){

               return integer;

            }

        }

        throw new IllegalArgumentException("No unique value found");

    }

    public static void main(String[] args) {

        //测试用例

        System.out.println(solution(new int[]{1, 1, 2, 2, 3, 3, 4, 5, 5}) == 4);

        System.out.println(solution(new int[]{0, 1, 0, 1, 2}) == 2);

    }

}

在这里其实还可以稍微改造一下,把map.put(array, map.getOrDefault(array,0)+1); 换为map.merge(array, 1, Integer::sum); 也能达到同样的效果。

思路4:

虽然借助了HashMap满足时间复杂度为O(n)的要求,但空间复杂度上并非最优,对于这种求单数的情况,咱们可以采用位运算去处理,代码简洁,效率空间都满足,这里我们就可以采用异或运算来解决问题。

🔖异或运算性质:

  • 异或运算是一个位运算;
  • 相同的数字异或结果为0;
  • 0与任何数字异或结果为该数字本身

「【源码】」

java 复制代码
public class Test  {

    public static int solution(int[] arrays){

        int num = 0;

        for(int i=0;i<arrays.length;i++){

            //异或运算

            num =  num^arrays[i];

        }

        return num;

    }

    public static void main(String[] args) {

        //测试用例

        System.out.println(solution(new int[]{1, 1, 2, 2, 3, 3, 4, 5, 5}) == 4);

        System.out.println(solution(new int[]{0, 1, 0, 1, 2}) == 2);

    }

}

四、总结

好啦,以上就是针对这种比较简单的题型的不同解决方法啦,这种问题非常适合初学者,应用自己所学过的数据结构特征以及位运算去解决实际问题啦。