力扣初级算法(一)— 数组(2)

初级算法
LeetCode 官方推出的经典面试题目清单 —— 「初级算法 - 帮助入门」
通过力扣的这个卡片 ,入门算法。
下面是个人刷题的记录与总结,这里会记录比较有代表性和比较好的题目

第一题,两个数组的交集
给定两个数组,编写一个函数来计算它们的交集。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2,2] 示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出:[4,9]

说明:

输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。 我们可以不考虑输出结果的顺序。 进阶:

如果给定的数组已经排好序呢?你将如何优化你的算法? 如果 nums1 的大小比 nums2 小很多,哪种方法更优? 如果 nums2
的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

这题我刚看到思考了一下,第一印象是用集合解决,因为交集肯定小于等于长度较短的数组,所以我直接把较长的数组加入到List集合中,然后遍历较短的数组,查询数字是否在较长的集合中,这种方法效率比较低,因为每次都要在List集合中查找指定元素。

    public static int[] intersect(int[] nums1, int[] nums2) {

        int l1=nums1.length,l2=nums2.length;
        List<Integer> center = new ArrayList<>();
        int length=(l1<l2?nums1:nums2).length;
        //如果两者相等
        int numMin[]=l1<=l2?nums1:nums2;
        int numMax[]=l1>l2?nums1:nums2;
        ArrayList<Integer> rs=new ArrayList<>();
        addList(numMax,center);
        for(int i=0;i<length;i++){
            if(center.contains(numMin[i])){
                rs.add(numMin[i]);
                //这里转为integer对象是为了不错误的调用重载方法(int index) 删除第一个元素
                center.remove(new Integer(numMin[i]));
            }
        }
        return rs.stream().mapToInt(Integer::valueOf).toArray();
    }
    public static void addList(int[] nums,List<Integer> css){
        for (int c:nums) { css.add(c); }
    }

哈希表解法
由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。
为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集。

public static int[] intersectHash(int[] nums1, int[] nums2) {
        if (nums1.length > nums2.length) {
            return intersectHash(nums2, nums1);
        }
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int num : nums1) {
            int count = map.getOrDefault(num, 0) + 1;
            map.put(num, count);
        }
        int[] intersection = new int[nums1.length];
        int index = 0;
        for (int num : nums2) {
            int count = map.getOrDefault(num, 0);
            if (count > 0) {
                intersection[index++] = num;
                count--;
                if (count > 0) {
                    map.put(num, count);
                } else {
                    map.remove(num);
                }
            }
        }
        return Arrays.copyOfRange(intersection, 0, index);
    }

第三个方法是排序加双指针,把两个数组排序,定义两个指针指向两个数组的首部,开始进行遍历,如果两个元素不相等,则将较小的元素的指针向右移一位,如果相等,则加入到结果集,遍历到任意一个指针到尾为止。

    public int[] intersectSort(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int length1 = nums1.length, length2 = nums2.length;
        int[] intersection = new int[Math.min(length1, length2)];
        int index1 = 0, index2 = 0, index = 0;
        while (index1 < length1 && index2 < length2) {
            if (nums1[index1] < nums2[index2]) {
                index1++;
            } else if (nums1[index1] > nums2[index2]) {
                index2++;
            } else {
                intersection[index] = nums1[index1];
                index1++;
                index2++;
                index++;
            }
        }
        return Arrays.copyOfRange(intersection, 0, index);
    }

第二题, 加一

给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

示例 1:

输入:digits = [1,2,3] 输出:[1,2,4] 解释:输入数组表示数字 123。 示例 2:

输入:digits = [4,3,2,1] 输出:[4,3,2,2] 解释:输入数组表示数字 4321。 示例 3:

输入:digits = [0] 输出:[1]

一看题目感觉很简单,仔细看了一下,发现有两个关键点要考虑到,一是要考虑9加一后的进位问题,二是考虑进位进到最后一位时,要对数组进行扩容。
个人比较繁琐的递归解

    public static int[] plusOne(int[] digits) {
        int length=digits.length-1;
        if(digits[length]<9){
            digits[length]=digits[length]+1;
            return digits;
        }else{
            digits[length]=0;
            return turnPlace(digits, length-1);
        }

    }
    public static int[] turnPlace(int[] dight,int end){
        if(end<0){//说明进位到了最后一位 需要扩容数组
            int rs[]=new int[dight.length+1];
            for(int i=0;i<dight.length;i++){
                rs[i+1]=dight[i];
            }
            rs[0]=1;
            return rs;
        }else{
            dight[end]=dight[end]+1;
            if(dight[end]>9){
                dight[end]=0;
                return turnPlace(dight, end-1);
            }else{
                return dight;
            }
        }
    }

仔细看了一下上面的递归,其实可以把递归变成迭代,从数组最后一位开始迭代,可以精简很多。

    public int[] plusOneOthers(int[] digits) {
        for (int i = digits.length - 1; i >= 0; i--) {
            digits[i]++;
            digits[i] = digits[i] % 10;
            if (digits[i] != 0) {
                return digits;
            }
        }
        digits = new int[digits.length + 1];
        digits[0] = 1;
        return digits;
    }

第三题,移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12] 输出: [1,3,12,0,0] 说明:

必须在原数组上操作,不能拷贝额外的数组。 尽量减少操作次数。

这题比较直观的解法是双指针,一个指针用于遍历整个数组,发现不为零的数字,就保存在另一个指针指向的位置,然后结果指针位置++。

    public static void moveZeroes(int[] nums) {
        // 储存结果的指针
        int numsIndex=0;
        for(int i=0;i<nums.length;i++){
            if(nums[i]!=0){
                nums[numsIndex++]=nums[i];
            }
        }
        for(int i=numsIndex;i<nums.length;i++){//最后把结果指针以及后面所有元素赋0
            nums[i]=0;
        }
    }

这里还介绍一种我看到的一种很优秀的双指针解法,只要遍历一次数组即可解决问题。 开始定义一个记数指针,用于记算遍历到的0的个数,如果遍历到非0元素,计算出第一个0的位置:当前下标-0的个数 = 这个数字应该放的位置的下标 ,只要把当前数字和刚才计算出来的第一个0的位置的下标进行交换位置即可。

    public void moveZeroesTrue(int[] nums) {
        int i = 0;//统计前面0的个数
        for (int j = 0; j < nums.length; j++) {
            if (nums[j] == 0) {//如果当前数字是0就不操作
                i++;
            } else if (i != 0) {
                //否则,把当前数字放到最前面那个0的位置,然后再把当前位置设为0
                nums[j - i] = nums[j];
                nums[j] = 0;
            }
        }
    }

第四题,两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]

刚开始我没看清楚题目,以为要找的是两个值,不是两个值的索引, 然后我用两个指针一个指向数组头部,一个指向数组尾部,开始遍历,如果和大于target,则右指针左移,和小于target,左指针右移。条件是 left<right。
但最后发现是要返回索引。。。。。。

回到正题,最容易想到的是枚举法,先定死一个元素,再遍历剩余的元素,看是否有两元素之和为target

    public int[] twoSumSlow(int[] nums, int target) {
        int n = nums.length;
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                if (nums[i] + nums[j] == target) {
                    return new int[]{i, j};
                }
            }
        }
        return new int[0];
    }

然而看到两个for循环就知道这个方法的效率很低,所以可以对上面的枚举法改进一下,哈希表是一种很好的解决方法,遍历一遍数组 ,每次查看哈希表中是否有 target-nums[i] 的这个结果,如果有则直接返回结果,如果没有则将当前指针指向的数字加入到哈希表中。

    public static int[] twoSumHash(int[] nums, int target) {
        Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums.length; ++i) {
            if (hashtable.containsKey(target - nums[i])) {
                return new int[]{hashtable.get(target - nums[i]), i};
            }
            hashtable.put(nums[i], i);
        }
        return new int[0];
    }

第五题,有效的数独
这题懂的都懂,主要是找出每个元素应该存放在9个盒子当中的哪一个的规律。怎么找到盒子索引。
box_index = (i / 3 ) * 3 + j / 3; i为横坐标,j为纵坐标。
先创建27个Map集合(题解的解法!),分别对应每行,每列,以及九个方块。
然后遍历数独数组,把每个元素加入到对应map中。map中储存的是数字以及出现次数。
这题就不给出代码了。

第六题,旋转图像

给定一个 n × n 的二维矩阵表示一个图像。

将图像顺时针旋转 90 度。

说明:

你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

示例 1:

给定 matrix = [ [1,2,3], [4,5,6], [7,8,9] ],

原地旋转输入矩阵,使其变为: [ [7,4,1], [8,5,2], [9,6,3] ] 示例 2:

给定 matrix = [ [ 5, 1, 9,11], [ 2, 4, 8,10], [13, 3, 6, 7],
[15,14,12,16] ],

原地旋转输入矩阵,使其变为: [ [15,13, 2, 5], [14, 3, 4, 1], [12, 6, 8, 9],
[16, 7,10,11] ]

这题一眼看过去感觉只能用比较暴力的解法,但是如果直接对图像进行旋转,很容易被边界问题所困扰,先看一下题解的解法

最直接的想法是先转置矩阵,然后翻转每一行。这个简单的方法已经能达到最优的时间复杂度O(N^2)O(N)

我当场打出???? 对于线性代数都忘的差不多的我来说,半天才明白这句话的意思,这种解法很好但很难想到,所以我直接介绍另外一种解法。
自外向内顺时针循环
将一圈矩形化为4个 边长-1的小矩形, 分别对矩形对应位置上的元素进行交换位置操作。举个例子,

 *      *  [ 5, 1, 9,11],               [15,13, 2, 5],
 *      *   [ 2, 4, 8,10], -》          [14, 3, 4, 1],
 *      *   [13, 3, 6, 7],            	[12, 6, 8, 9],
 *         [15,14,12,16]  ,            [16, 7,10,11]

其中[5 11 16 15] 为一组 [1 10 12 13 ]为一组 [9 7 14 2 ]为一组 换位。划分为4个矩形 对每个矩形中的值旋转。
代码

public void rotate2(int[][] matrix) {
        if(matrix.length == 0 || matrix.length != matrix[0].length) {
            return;
        }
        int nums = matrix.length;
        //第几个矩形
        int times = 0;
        while(times <= (nums >> 1)){
            // 边长=size-i*2
            int len = nums - (times << 1);

            for(int i = 0; i < len - 1; ++i){
                //第一个元素
                int temp = matrix[times][times + i];
                //
                matrix[times][times + i] = matrix[times + len - i - 1][times];
                matrix[times + len - i - 1][times] = matrix[times + len - 1][times + len - i - 1];
                matrix[times + len - 1][times + len - i - 1] = matrix[times + i][times + len - 1];
                matrix[times + i][times + len - 1] = temp;
            }
            ++times;
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值