LintCode数组题总结

本文总结了LintCode上关于数组的算法题目,包括交集查找、数组合并、排序颜色、合并有序数组、两数之和、最接近目标的两数之和等经典问题。讲解了双指针法、动态规划、前缀和等解题技巧,适合算法学习者参考。
摘要由CSDN通过智能技术生成

做算法题的时候,几乎不可避免要跟数组打交道。在LintCode上数组那一章有这么一些题目:




1)547. Intersection of Two Arrays

比较简单。要求找到2个数组的交集,简单点的方法就是用2个hashSet,第一个HashSet存第一个数组的元素。然后扫描第二个数组,如果第二个数组中的元素在第一个HashSet中出现了,那么就把它加到第二个HashSet中。最后第二个HashSet就是两个数组的交集了。


2)6. Merge Two Sorted Arrays

比较简单。对两个数组进行merge,新开一个数组,从后往前扫描。两两比较,较大的加到尾部。最后如果2个数组有剩余,则把剩余的元素也加进去。


3) 64. Merge Sorted Array

比较简单。对2个有序数组A和B进行合并,要求把B合并进A,因为A数组尾部有足够的空间可以容纳B。建议从后往前扫描,这样的代码更加简洁。扫描完了后看B数组有没有剩余,若有剩余则把剩余的元素也添加到A数组头部。代码如下:

    public void mergeSortedArray(int[] A, int m, int[] B, int n) {
        
        while (m >= 1 && n >= 1) {
            if (A[m - 1] >= B[n - 1]) {
                A[m + n - 1] = A[m - 1];
                m--;
            } else {
                A[m + n - 1] = B[n - 1];
                n--;
            }
        }
        
        while (n > 0) {
            A[m + n - 1] = B[n - 1];
            n--;
        }
        
        return;
    }


4)148. Sort Colors

数组中的元素只有0、1、2这三个类型的数字,要求对他排序。Given [1, 0, 1, 2], sort it in-place to [0, 1, 1, 2]。官网提示了一种naive的做法:A rather straight forward solution is a two-pass algorithm using counting sort. First, iterate the array counting number of 0's, 1's, and 2's, then overwrite array with total number of 0's, then 1's and followed by 2's. Could you come up with an one-pass algorithm using only constant space?

但是得循环2次,有没有办法只用一次循环搞定呢?那就得用双指针法了。

从数组两端向中间遍历,前面放0,后面放2。把前面出现的2放到后面,后面出现的0放到前面,这样中间剩下的就是1

我的解法是定义指针start = 0,指针end = n - 1;一次遍历,如果遇到0,交换给start,遇到2,交换给end,遇到1别管。代码如下:

    public void sortColors(int[] nums) {
        int start = 0, end = nums.length - 1;
        int index = 0;
        while (index <= end) {
            if (nums[index] == 0) {
                swap(nums, index, start);
                index++;
                start++;
            } else if (nums[index] == 1) {
                index++;
            } else {
                swap(nums, index, end);
                end--;
            }
        }
    }
    private void swap(int[] nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }

算法的关键在于,当从前往后扫描数组的时候,把0往最左边放,把2往最右边放。

当index对应的元素是0时,就把它和start交换,往前面放。因为是从左往右扫描的,所以每次遇到0都往左边放,这样能够保证左边都是0。

当index对应的元素是1时,直接继续扫描,不用管它。(因为在之后的扫描会把这个1变为0或者维持1不变)

当index对应的元素是2时,我们把它与end交换后,只需要把end指针往左边挪一位,而不用给index++。但是当index对应的元素是0时,我们却要给index++。这是为啥呢。

原因在于,index对应的值为2时,end对应的值也可能为2,在这种情况下,交换完了之后,index自己还是2,所以不行,index还的继续留在这个位置;


5)143. Sort Colors II

新开一个数组,用于记录1到k出现的次数,然后再根据出现的次数把这些数字写回原数组。时间复杂度为O(n)。以空间换时间。

    public void sortColors2(int[] colors, int k) {
        int[] kArray = new int[k + 1];
        for (int i = 0; i < colors.length; i++) {
            kArray[colors[i]]++;
        }
        
        int index = 0;
        for (int i = 1; i < kArray.length; i++) {
            while (kArray[i] > 0) {
                colors[index++] = i;
                kArray[i]--;
            }
        }

    }

6)56. Two Sum

给定一个target,要求在数组中找到两个数之和等于target,并返回这两个数的下标。最简单的是2层循环嵌套暴力求解O(n^2),稍微好一点的解法是先排序,然后再在排好序的数组中从两边往中间扫描,如果找到2个数之和等于target,就在原数组中扫描找到它们原来的下标并返回,时间复杂度是O(nlog(n)),代码如下:

    public int[] twoSum(int[] nums, int target) {
        int[] res = new int[]{-1, -1};
        int[] oldNums = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            oldNums[i] = nums[i];
        }
        Arrays.sort(nums);
        int left = 0, right = nums.length - 1;
        while (left < right) {
            if (target - nums[left] == nums[right]) {
                res[0] = nums[left];
                res[1] = nums[right];
                break;
            } else if (target - nums[left] < nums[right]) {
                right--;
            } else {
                left++;
            }
        }
        int[] toReturn = new int[2];
        for (int i = 0; i < oldNums.length; i++) {
            if (oldNums[i] == res[0]) {
                toReturn[0] = i + 1;
                break;
            }
        }
        for (int i = 0; i < oldNums.length; i++) {
            if (oldNums[i] == res[1] && i != toReturn[0] - 1) {
                toReturn[1] = i + 1;
                break;
            }
        }
        Arrays.sort(toReturn);
        return toReturn;
    }
有没有O(n)的解法呢?有的,那就是利用HashMap,如下所示:

我的目标是要找到a+b=target中的a和b,从左往右扫描数组,每次都把target-a加进map中,同时每次都在map中寻找看target-a存不存在:

    public int[] twoSum(int[] nums, int target) {
        int[] res = new int[2];
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums.length; i++) {
            if (map.get(nums[i]) != null) {
                res[0] = map.get(nums[i]) + 1;
                res[1] = i + 1;
            }
            map.put(target - nums[i], i);
        }
        return res;
    }

要求在数组中找到2个数,使得这两个数之和最接近给定的target。Given array nums = [-1, 2, 1, -4], and target = 4. The minimum difference is 1. (4 - (2 + 1) = 1).

一开始我用的是2层循环,暴力的对数组中每两个数求和。不过后来在youtube上看到了geeksforgeeks的一个老印的解法,是O(nlog)的。第一遍就排序。然后再设置2个指针,分别从两边往中间扫描。2个指针所对应的数之和如果大于target,那么就把右指针往左边移动;反之则把左指针往右边移动。这样能够保证找到最接近于target的两数之和。(具体怎么证明我还没想清楚,但这个思路绝对是对的,求大神给个清晰地数学证明)。

算法流程如下:

1) Sort all the elements of the array. 

2) Keep 2 index variables for left and right index. 

3) Initialize: left index l = 0. r = n-1. 

4) sum = a[l]+a[r] 

5) If sum is negative than l++ else r — . 

6) Keep track of absolute min sum. 

7) Repeat steps 4,5,6 until l < r

代码如下:

    public int twoSumCloset(int[] nums, int target) {
        Arrays.sort(nums);
        int left = 0, right = nums.length - 1;
        int min = Integer.MAX_VALUE;
        
        while (left < right) {
            int sum = nums[left] + nums[right];
            int diff = Math.abs(sum - target);
            min = Math.min(min, diff);
            if (sum > target) {
                right--;
            } else {
                left++;
            }
        }
        
        return min;
    }

8)138. Subarray Sum

要求在一个数组中找到一个子数组,使得这个子数组的和为0。就是比如一个数组是[12, -3, 1, 2, -3, 4] ,[-3, 1, 2]  这一段的和是0。这道题有个技巧,我用sum函数代表前面n个元素的和.

sum(1) = 12

sum(2) = 9

sum(3) = 10

sum(4) = 12

发现规律木有?那就是sum(1) 等于 sum(4) ,而第1+1个元素到第4个元素的字数组和恰好就为0。

所以方法就是求连续子数组的和,然后看哪两个sum相等,相等的下标区间就是答案了。代码如下:

    public ArrayList<Integer> subarraySum(int[] nums) {
        ArrayList<Integer> res = new ArrayList<Integer>();
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        map.put(0, -1);
        
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
            if (map.containsKey(sum)) {
                res.add(map.get(sum) + 1);
                res.add(i);
                break;
            }
            map.put(sum, i);
        }
        
        return res;
    }


405. Submatrix Sum

是上道题的变种,在矩阵中找到一个子矩阵的和为0。

sum[i][j]表示matrix[0][0]到matrix[i-1][j-1]所有元素的和。 

建立sum矩阵,为n+1行,m+1列。将第0行和第0列都初始化为0。 

遍历matrix,根据公式 sum[i][j] = matrix[i - 1][j - 1] + sum[i][j - 1] + sum[i - 1][j] -sum[i - 1][j - 1] 计算所有sum。 

然后取两个row:row1, row2。用一个线col从左到右扫过row1和row2,每次都用subArraySum=sum[row2][col]-sum[row1][col]来表示row1-row2和0-col这个矩形元素的sum。如果在同一个row1和row2中,有两条线(col1,col2)的subArraySum相等,则表示row1-row2和col1-col2这个矩形中的元素和为0。 

比如矩阵是:

[1 ,5  ,7],
[3 ,7  ,-8],
[4 ,-8 ,9],
那么对应的sum矩阵就是:

[1 ,6  ,13],
[4 ,16 ,15],
[8 ,12 ,20],
20-13=7,8-1=7,说明右下角的2*2的子矩阵的和就是0

代码如下:

    public int[][] submatrixSum(int[][] matrix) {
        int[][] res = new int[2][2];
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return res;
        }
        int m = matrix.length;
        int n = matrix[0].length;
        int[][] sum = new int[m + 1][n + 1];
        // initialize
        for (int i = 0; i <= m; i++) {
            sum[i][0] = 0;
        }
        for (int i = 0; i <= n; i++) {
            sum[0][i] = 0;
        }
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                sum[i][j] = sum[i-1][j] + sum[i][j-1] + matrix[i-1][j-1] - sum[i-1][j-1];
            }
        }
        
        for (int row1 = 0; row1 < m; row1++) {
            for (int row2 = row1 + 1; row2 <= m; row2++) {
                HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
                for (int col = 0; col <= n; col++) {
                    int subArraySum = sum[row2][col] - sum[row1][col];
                    if (map.containsKey(subArraySum)) {
                        res[1][0] = row2 - 1;
                        res[1][1] = col - 1;
                        res[0][0] = row1;
                        res[0][1] = map.get(subArraySum);
                        return res;
                    } else {
                        map.put(subArraySum, col);
                    }
                }
            }
        }
        
        return res;
    }

404. Subarray Sum II

给定一个正整数数组,找到所有和在区间[start, end]的子数组,返回符合要求的子数组个数即可。比如Given [1,2,3,4] and interval = [1,3], return 4。

我们一开始会想到先预处理数组,求出preSum数组,比如sum[5]就代表前5个数之和,sum[2]代表前2个数之和,那么如果sum[5]-sum[2]的差在给定区间范围内的话,那么arr[3], arr[4], arr[5]组成的子数组之和就是满足条件的,result++。这样的话,写出代码就是两层for循环,只要判断sum[i]-sum[j]的值落在区间内就统计自增。但是这样的代码最后会超时。所以我们需要用二分查找优化一下,我们外面用一层for循环,对于遍历到的每个sum[j],我只需要查看他前面有多少个数是落在区间[sum[j]-end, sum[j]-start]内就行,所以就是在它前面找对应的range内有多少个数字。

非负整数 Subarray 相关题目的一个常用解法就是通过计算前缀和数组,然后利用二分搜索来找到目标。在这里,类似的思路同样适用。举个例子,假设给定一个目标区间[low, high],当我们遍历到prefixSum[i]时,我们的搜索范围为prefixSum[0,i-1],而搜索目标有两个:

  1. argminj (prefixSum[i]prefixSum[j]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值