数组的练习

旋转图像

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
在这里插入图片描述
示例 3:
输入:matrix = [[1]]
输出:[[1]]
示例 4:
输入:matrix = [[1,2],[3,4]]
输出:[[3,1],[4,2]]

提示:

matrix.length == n
matrix[i].length == n
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotate-image

方法一:辅助数组
利用辅助数组,我们请看下面的图解过程:
在这里插入图片描述
如果仅仅靠一个例子得出这样的结论,明显没有说服力,因此如果我们将这个结论应用到示例1,发现同样使用。这时候我们提交代码成功通过,证明结论是正确的。

对应代码:

class Solution {
    /*
    利用辅助数组实现:
    通过观察规律可以发现:
    第x行的元素顺时针旋转90°后到了
    matrix[x][j] ---> matrix[j][k],其中x + k = n - 1
    */
    public void rotate(int[][] matrix) {
        if(matrix == null || matrix[0].length <= 1)
           return;
        int col = matrix.length,row = matrix[0].length,i,j,k = row - 1; 
        int[][] tmp = new int[col][row];
        for(i = 0; i < col; ++i){
            for(j = 0; j < row; ++j){
                tmp[j][k] = matrix[i][j];
            }
            k--;
        }
        for(i = 0; i < col; ++i){
            for(j = 0; j < row; ++j){
               matrix[i][j] = tmp[i][j];
            }
        }
    }
   
}

对应的结果:
在这里插入图片描述

方法二:原地修改

本题已经明确要求了原地修改,那么在不借助辅助数组时,我们要怎么办呢?我们再次观察旋转的四个元素之间存在怎样的关系:
在这里插入图片描述

对应代码:

class Solution {
    /*
    规律:matrix[i][j]将会旋转到matrix[j][n - i - 1]的位置
    即旋转前位置为[x][y],旋转后的位置为[x1][y1],则有x1 = y,y1 + x = nums.length - 1
    原地修改,不需要使用辅助数组:
    那么就需要同时修改4个元素
    那么4个元素旋转规律为:
    matrix[i][j] -------------------------> matrix[j][n - i - 1]
        |                                       |
        |                                       |
        |                                       |
    matrix[n - j - 1][i] <------------------ matrix[n - i - 1][n - j - 1]
   */
    public void rotate(int[][] matrix) {
        if(matrix == null || matrix[0].length <= 1)
           return;
        int i,j,n = matrix.length,col,tmp; 
        col = n / 2;
        for(i = 0; i < col; ++i){
            for(j = i; j < n - i - 1; ++j){
               tmp = matrix[j][n - i - 1];
               matrix[j][n - i - 1] = matrix[i][j];
               matrix[i][j] = matrix[n - j - 1][i];
               matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
               matrix[n - i - 1][n - j - 1] = tmp;
            }
        }
    }
   
}

运行结果:
在这里插入图片描述

找到所有数组中消失的数字

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

示例 1:

输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]
示例 2:

输入:nums = [1,1]
输出:[2]

提示:

n == nums.length
1 <= n <= 105
1 <= nums[i] <= n
进阶:你能在不使用额外空间且时间复杂度为 O(n) 的情况下解决这个问题吗? 你可以假定返回的数组不算在额外空间内。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array

本题我们当然可以利用哈希表来统计数组中的元素,然后再遍历[1,n]的元素,判断这个元素是否再哈希表中,如果没有,那么表示数组没有这个数字,将其添加到列表中。

但是我们要求不适用额外空间,那么我们将如何标记哪些数字没有出现呢?我们来看一下题目,长度为n的数组,存放[1,n]的数字,这时候就满足了数字n对应数组下标为n - 1.所以我们将遍历这个数组的元素,每遍历到一个元素nums[ i ],那么就将它对应的下标nums[ i ] - 1的值nums[ nums[ i ] - 1]加上n,遍历数组结束之后,就会发现有一些元素的值nums[ x ]小于等于n,这些元素对应下标加1就是消失的数字,也就是消失的数字为x + 1

对应的代码:

class Solution {
    /*
    方法一:利用哈希表
    遍历这个数组,将对应的元素添加到哈希表中,然后
    遍历数组的元素,判断哈希表中是否存在这个元素,如果不存在,就将其添加到
    列表中
 
    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> list = new ArrayList<Integer>();
        HashMap<Integer,Boolean> map = new HashMap<Integer,Boolean>();
        for(int num: nums){
            map.put(num,true);
        }
        for(int i = 1; i <= nums.length; ++i){
            if(!map.containsKey(i))
              list.add(i);
        }
        return list;
    }
    */
    /*
    以空间换时间:和上面的方法是一样的道理,只是将哈希表变成了大小为nums.length + 1的数组
    从而将数组nums中出现的元素放在了tmp中,然后遍历tmp数组,如果它的值为0,说明这个元素缺失
    了,需要将其放到列表中
    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> list = new ArrayList<Integer>();
        int[] tmp = new int[nums.length + 1];//将对应的数字存放到这个数组中
        for(int num: nums){
            tmp[num] = 1;
        }
        for(int i = 1; i <= nums.length; ++i){
            if(tmp[i] == 0)
              list.add(i);
        }
        return list;
    }
    */
    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> list = new ArrayList<Integer>();
        int i,x,n = nums.length;
        for(i = 0; i < n; ++i){
            /*
            值得注意的是,nums[i]的值可能会大于n,所以不可直接是
            nums[nums[i] - 1],因为这样会导致发生了越界,例如[4,1,3,1,2],那么遍历到
            元素4的时候,它和下标3对应的,那么就会nums[4 - 1]就会加上n,此时nums[4 - 1] = 6
            之后往后遍历,当遍历到i = 3时,对应的值为nums[3] = 6,,如果没有取模的话,那么x = 6 - 
            1 = 5,此时就会发生越界,所以需要取模。
            */
            x = (nums[i] - 1) % n;
            nums[x] += n;

        }
        for(i = 0; i < n; ++i){
        /*
        如果对应的元素小于等于n,那么i + 1就是消失的数字
        之所以要等于,可能存在类似这样的例子[1,1,3,5,2],那么
        再进行加n之后,数组变成了[11,6,8,5,7],消失的数字为4,它对应的下标为3,这时候的nums[3]为5
        所以当nums[ i ]小于等于n的时候,i + 1就是消失的数字。不可以省略等号,否则将会错误。
        */
            if(nums[i] <= n)
              list.add(i + 1);
        }
        return list;
    }
    
}

运行结果:
在这里插入图片描述

两个数组的交集

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

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

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

说明:
输出结果中的每个元素一定是唯一的
我们可以不考虑输出结果的顺序。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/intersection-of-two-arrays

这个题目存在着三种方法:
方法一:利用哈希表
首先利用哈希表存放nums1出现的元素,如果是重复元素,将它对应的值置为true即可,因为这里要求输出结果中的每一个元素是唯一的,所以我们只要知道这个元素再哈希表中出现过了。然后遍历另一个数组nums2,每遍历到num2的一个元素,就判断哈希表中是否存在这个元素,如果存在并且对应的值为true,说明这个元素就是交集,需要将这个元素添加到列表中,然后更新哈希表中这个元素对应的置为false,从而实现了输出结果的每一个元素是唯一的。
方法二:排序
对两个数组进行排序之后,我们将遍历两个数组,如果其中一个数组遍历完毕,退出循环即可。在每次循环中,我们将比较着两个数组当前下标对应的值nums1[ i ]、nums2[ j ],如果两者相等,说明两者是交集,但是考虑到输出结果的元素是唯一的,所以我们将判断列表中是否存在这个交集了,如果没有才可以添加;否则如果nums1[ i ] != nums2[ j ] ,则需要将两者中小的指针后移,比如nums1[ i ] 小于nums2[ j ],则需要将i往后移动。重复上述操作,直到至少一个数组遍历完毕了。
方法三:排序 + 二分查找
在进行排序之后,我们将遍历nums1中的每一个元素,每遍历到一个元素nums1[ i ],我们将在nums2中寻找nums1[ i ]的最后一个下标(因为考虑到了nums2中存在nums1[ i ]的重复元素,从而实现了输出结果中每一个元素的唯一性)。同时为了进一步减少程序运行的时间,如果nums1[ i ]等于了nums1[ i - 1],不需要进行二分查找了,因为nums1[ i - 1]已经在nums2中已经进行过了。

对应代码:

class Solution {
    /*
    对数组进行排序,然后利用遍历即可:
    如果两者相等,那么就说明这个元素是交集,但是为了保证
    最后数组中每一个元素是唯一的,所以需要判断列表中是否存在这个
    元素,如果没有就添加到列表中,然后将两者的下标后移
    否则,如果不相等,那么需要将两者中较小的元素后移
    public int[] intersection(int[] nums1, int[] nums2) {
            Arrays.sort(nums1);
            Arrays.sort(nums2);
            List<Integer> list = new ArrayList<Integer>();
            int i = 0,j = 0;
            while(i < nums1.length && j < nums2.length){
                if(nums1[i] == nums2[j]){
                    if(list.size() == 0 || !list.contains(nums1[i]))
                        list.add(nums1[i]);
                    ++i;
                    ++j;
                }else if(nums1[i] < nums2[j]){
                    ++i;
                }else{
                    ++j;
                }
            }
            int[] ans = new int[list.size()];
            i = 0;
            for(int num: list){
                ans[i++] = num;
            }
            return ans;
    }
    */
    /*
    利用哈希表来统计一个数组中各个元素出现的次数,然后再遍历另一个
    数组,每遍历到一个元素,就判断哈希表中是否存在这个数,如果存在
    那么就将其添加到列表中,然后将哈希表中这个元素置为false,表示不在存在了
    这样就可以使得列表中的元素是唯一的
  
    public int[] intersection(int[] nums1, int[] nums2) {
            HashMap<Integer,Boolean> map = new HashMap<Integer,Boolean>();
            List<Integer> list = new ArrayList<Integer>();
            int i = 0,j = 0;
            for(int num: nums1){
                map.put(num,true);
            }
            for(int num: nums2){
                if(map.containsKey(num) && map.get(num)){
                    //有可能num这个元素不存在于哈希表中,所以如果直接调用map.get(num)
                    //就会抛出NullPointerException,所以在调用get方法之前需要判断哈希表中
                    //是否存在num元素
                    list.add(num);
                    map.put(num,false);
                }
            }
            int[] ans = new int[list.size()];
            i = 0;
            for(int num: list){
                ans[i++] = num;
            }
            return ans;
    }
    */
    /*
    利用排序 + 二分查找
    再排序之后,然后遍历数组nums1,每获取到一个元素,需要判断这个元素是否已经再nums2中
    查找过了,如果是,那么就进入下一次循环,否则再nums2中查找,如果能找到第一个nums元素,那么需要表示这个是交集,需要将其添加到列表中
    */
    public int[] intersection(int[] nums1, int[] nums2) {
            Arrays.sort(nums1);
            Arrays.sort(nums2);
            List<Integer> list = new ArrayList<Integer>();
            int i = 0,j = 0,k;
            for(i = 0; i < nums1.length; ++i){
                if(i != 0 && nums1[i] == nums1[i - 1])//如果这个元素已经再nums2中查找过了,那么就进入下一次循环
                   continue;
                k = binarySearch(nums2,j,nums2.length - 1,nums1[i]);//再数组nums2[j,nums2.length]查找nums1[i]的最后一个下标
                if(k != -1){
                   list.add(nums2[k]);
                   j = k + 1;
                }
            }
            int[] ans = new int[list.size()];
            i = 0;
            for(int num: list){
                ans[i++] = num;
            }
            return ans;
    }
    public int binarySearch(int[] nums,int from,int to,int target){
        int mid,begin = from;
        while(from <= to){
            mid = (from + to) / 2;
            if(nums[mid] > target)
              to = mid - 1;
            else
              from = mid + 1;
        }
        if(to >= begin && nums[to] == target)//退出循环的时候,to是最后一个出现target的下标,from才是target地下标
           return to;
        return -1;
    }
}

运行结果:
在这里插入图片描述

两个数组的交集II

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

示例 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 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/intersection-of-two-arrays-ii
通过观察本题,我们将发现本题是将两个数组的所有交集都输出,那么这个交集是重复了。所以同样是存在三种方法,其中方法二不变,而方法一、方法三则会受到影响而做出相应的改变
因为输出结果中元素并不是唯一的,所以:
我们进行方法一哈希表查找的时候,哈希表的键对应的值使这个键在数组nums1出现的次数,然后再遍历nums2,每遍历到nums2中的一个元素,都需要判断当前这个元素是否存在于哈希表中,并且对应的值不为0,如果满足这两个条件,说明这个元素是交集,需要添加到列表中,然后将哈希表对应的值减1.而上一题中的哈希表只需要证明这个值存在即可,而不需要知道出现了多少次。

我们在进行方法三的二分查找的时候,每遍历nums1的一个元素nums1[ i ],我们需要在nums2中对应的范围找到第一个nums1[ i ]的下标并将其返回。(而上一题为了保证输出结果元素的唯一性,需要在nums2中对应的范围找到最后一个nums1[ i ]的下标并将其返回).
对应的代码:

class Solution {
    /*
    方法一:利用哈希表:统计nums1中各个元素出现的次数,然后再遍历数组nums2
    这时候每遍历到一个元素,判断哈希表中是否存在这个元素,如果存在,那么就表示
    是一个交集,需要将其添加到列表中,同时将哈希表中这个元素对应的值减1,当哈希表中
    这个元素对应的值为0,那么表示不是交集(不需要对数组进行排序)
    方法二:对数组进行排序 + 遍历
    排序之后,遍历两个数组,直到一个数组遍历完毕为止
    然后比较这个两个数组对应的值,如果相等,说明是交集,这时候需要添加到列表中,同时后移两个
    数组的指针,否则如果不相等,说明不是交集,此时需要将两者中较小的指针后移
    方法三:排序 + 二分查找
    排序完毕之后,遍历nusm1中的元素,每得到一个元素,就将其再nums2数组中进行二分查找,如果能
    够找到,说明是交集,否则不是
    */
    /*
    方法二:
   
    public int[] intersect(int[] nums1, int[] nums2) {
         Arrays.sort(nums1);
         Arrays.sort(nums2);
         List<Integer> list = new ArrayList<Integer>();
         int i = 0,j = 0;
         while(i < nums1.length && j < nums2.length){
             if(nums1[i] == nums2[j]){
                 list.add(nums1[i]);
                 ++i;
                 ++j;
             }else if(nums1[i] < nums2[j]){
                 ++i;
             }else{
                 ++j;
             }
         }  
         i = 0;
         int[] ans = new int[list.size()];
         for(int num: list){
             ans[i++] = num;
         }
         return ans;
    }
    */
    /*
    方法三:二分查找,同样建立再数组有序的前提上
    */
    public int[] intersect(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        List<Integer> list = new ArrayList<Integer>();
        int j = 0,k;
        for(int num: nums1){
            //遍历nums1中的元素,再nums2中进行二分查找
            k = binarySearch(nums2,j,nums2.length - 1,num);
            if(k != -1){
                list.add(num);
                j = k + 1;
            }
        }
        j = 0;
        int[] ans = new int[list.size()];
        for(int num: list){
            ans[j++] = num;
        }
        return ans;
    }
    //获取再nums中[from,to]范围中第一个出现target的下标
    public int binarySearch(int[] num,int from,int to,int target){
        int mid,begin = from;
        while(from <= to){
            mid = (from + to) / 2;
            if(num[mid] >= target)
               to = mid - 1;
            else
               from = mid + 1;
        }
        if(from < num.length && num[from] == target)
           return from;//如果能再对应范围中找到target,那么就返回target对应下标from
        return -1;
    }

}

运行结果:
在这里插入图片描述
如果是给出了n个数组,要求求出着n个数组的交集,那么我们先求出两个数组的交集ans,然后再将求出ans和剩下数组中的一个数组的交集tmp,然后更新ans为tmp,重复上诉步骤即可。由于leetCode本题是会员,没有办法提交,所以在下面写了一下我的代码,如果有错误的话,请大家指教哈

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/*
求解多个有序数组的交集
首先求出其中两个数组的交集,将其存放到一个数组ans中
然后利用ans和其他数组求解交集

所以这时候将本题就转成了寻找两个数组的交集,其中寻找两个有序数组的交集
存在了三种方法,这里主要将二分查找的方法
 */
public class UnionTest {
    public static void main(String[] args) {
        int[] nums1= {4,2,6,8,9,1};
        int[] nums2 = {2,5,3,8,9,1};
        int[] nums3 = {3,8,8,9,9,1};
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        Arrays.sort(nums3);//对数组进行排序,然后利用二分查找寻找交集
        int[] ans = getSame(nums1,nums2);//寻找前两个数组的交集
        ans = getSame(nums3,ans);//将这个交集和剩下的数组求交集,然后赋值给ans即可实现ans为这n个数组交集
        for(int num: ans){
            System.out.print(num + " ");
        }
        System.out.println();

    }

    private static int[] getSame(int[] nums1, int[] nums2) {
        List<Integer> list = new ArrayList<Integer>();
        int j = 0,k;
        for(int num: nums1){
            k = binarySearch(nums2,j,nums2.length - 1,num);//再nums2下标为[j,nums2.length -1]范围寻找num的下标
            if(k != -1){
                //如果找到了,说明是交集
                list.add(num);
                j = k + 1;
            }
        }
        int[] ans = new int[list.size()];
        j = 0;
        for(int num: list){
            ans[j++] = num;
        }
        return ans;
    }

    /**
     * 查找nums中[from,to]中第一个target的下标,并将其返回
     * @param num
     * @param from
     * @param to
     * @param target
     * @return
     */
    public static int binarySearch(int[] num,int from,int to,int target){
        int mid;
        while(from <= to){
            mid = (from + to) / 2;
            if(num[mid] >= target)
                to = mid - 1;
            else
                from = mid + 1;
        }
        if(from < num.length && num[from] == target)
            return from; //如果from再合理范围,并且它对应值是target,说明from就是第一个target的下标
        return -1;
    }
}

运行结果:
在这里插入图片描述

排序数组中的两个数字之和

给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 0 开始计数 ,所以答案数组应当满足 0 <= answer[0] < answer[1] < numbers.length 。
假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。

示例 1:
输入:numbers = [1,2,4,6,10], target = 8
输出:[1,3]
解释:2 与 6 之和等于目标数 8 。因此 index1 = 1, index2 = 3 。

示例 2:
输入:numbers = [2,3,4], target = 6
输出:[0,2]

示例 3:
输入:numbers = [-1,0], target = -1
输出:[0,1]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/kLl5u1

方法一:二分查找
由于数组是一个有序数组,所以遍历当一个元素nums[ i ],那么我们则只要在[i + 1,nums.length - 1]范围中找target - nums[ i ]即可,如果能够找到,那么就将两者的下标添加到res数组中然后返回即可。
方法二:双指针
定义两个指针i,j,并且i的初始值为0,j的初始值为nums.length - 1,如果两者的和等于target,那么就将两者下标构成的数组返回,否则,如果和大于target,那么需要将指针j往左边移动,即j - - ,否则和小于target,那么需要将指针 i 往右边移动,即i ++。重复上述操作,直到i等于j退出循环,因为明确要求了同一个数字不可以用两次.

对应代码:

class Solution {
    /*
    由于数组是已经排好序的,所以我们需要在[i + 1,numbers.length - 1]中寻找
    target - numbers[i]的下标,如果存在将其添加到ans中,退出循环
    public int[] twoSum(int[] numbers, int target) {
        int[] ans = new int[2];
        int k;
        for(int i = 0; i < numbers.length - 1; ++i){
            k = binarySearch(numbers,i + 1,numbers.length - 1,target - numbers[i]); 
            if(k != -1){
                ans[0] = i;
                ans[1] = k;
                break;
            }
        }
        return ans;
    }
    public int binarySearch(int[] numbers,int low,int high,int target){
        int mid;
        while(low <= high){
            mid = low + (high - low) / 2;
            if(numbers[mid] == target)
               return mid;
            else if(numbers[mid] < target){
                low = mid + 1;
            }else{
                high = mid - 1;
            }
        }
        return -1;
    }
    */
    /*
    利用双指针
    */
    public int[] twoSum(int[] numbers, int target) {
        int i = 0,j = numbers.length - 1,sum;
        while(i < j){
            sum = numbers[i] + numbers[j];
            if(sum == target)
               break;
            else if(sum < target){
                ++i;
            }else{
                --j;
            }
        }
        return new int[]{i,j};
    }
}

运行结果:
在这里插入图片描述

和为k的子数组

给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。

示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2
解释: 此题 [1,1] 与 [1,1] 为两种不同的情况

示例 2 :
输入:nums = [1,2,3], k = 3
输出: 2

提示:
1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/QTMn0o

方法一:暴力解决
这里主要将暴力解决中需要注意的问题:

  • 因为数组元素存在负数,所以一旦获取和为k的连续子数组之后,并不能立刻结束这一层循环,执行下一步,而应该将整个数组遍历完毕,比如[1,2,3,5,-5],target = 6,那么尽管在第二层循环中遍历到3的时候已经满足了他们的和等于target了,所以[1,2,3]是一个子数组,但是后面的5 + (-5) = 0,因此[1,2,3,5,-5]同样是子数组。
  • 由于可能当前的元素nums[ i ]就是target了,所以第二层循环的下标初始值应该是 i,而不应该是 i + 1.

对应代码:

    public int subarraySum(int[] nums, int k) {
         int ans = 0,total = 0;
         for(int i = 0; i < nums.length; ++i){
            total = 0;
            for(int j = i; j < nums.length; ++j){
                //因为有可能当前的元素nums[i]就是k,所以每次循环都需要将total初始
                //化为0,然后j是从i开始遍历的
                total += nums[j];
                if(total == k)
                   ++ans;
            }
         }
         return ans;
    }

方法二:利用前缀和 + 哈希表
我们假设dp[ i ]表示的是[ 0,i ]的元素的和,那么要使得[ j , i ]的元素之和等于target,也就是要dp[ i ] - dp[ j - 1 ] = target即可,这时候我们在得知dp[ i ]的情况下,只要我们知道dp[ j - 1]有多少种情况,那么
以nums[ i ]结尾的构成和为target的连续子数组就有多少种情况,因此我们需要统计dp[ j ]有多少种情况即可,此时我们就需要利用到了哈希表实现。

对应代码:

class Solution {
    public int subarraySum(int[] nums, int k) {
         int ans = 0,total = 0;
         HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
         map.put(0,1);//同样需要将0添加到哈希表中,因为可能存在total - k = 0的情况
         for(int i = 0; i < nums.length; ++i){
           total += nums[i];//total表示[0,i]的前缀和
           if(map.containsKey(total - k)){
               /*
               如果哈希表中存在前缀和为total - k,那么就说明以当前nums[i]结尾的
               和为k的连续子数组有map.get(toal - k)种
               */
               ans += map.get(total - k);
           }
           map.put(total,map.getOrDefault(total,0) + 1);
         }
         return ans;
    }
}

运行结果:
在这里插入图片描述

0 和 1 个数相同的子数组

给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

示例 1:
输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。

示例 2:
输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量 0 和 1 的最长连续子数组。

提示:
1 <= nums.length <= 105
nums[i] 不是 0 就是 1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/A1NYOS

看似这个题目和上面那个题目没有什么联系啊,但是一旦我们将数组中的0变成-1之后就有联系了。将数组中的0变成了-1,那么题目就转成了求 (-1) 和 1个数相同的连续子数组,这时候又怎样呢?我们来考虑一下连续子数组有什么条件可以利用的吗?没错,连续子数组的和必然为0了,因为(-1) 和 1 个数相同。此时就已经变成了求解和为0的连续子数组的最长长度了

所以这里同样存在两种方法:

  • 暴力解决,但是这种方法因为数据过多,从而导致了超时
  • 前缀和 + 哈希表

所以这里只好通过前缀和 + 哈希表来解决了。尽管本题的解题思路和上题一样,但是又会有所不同,因为题目的要求就已经不同了嘛。

前缀和dp[ i ]依旧表示为[ 0, i ]的连续子数组的和,但是哈希表的存放的值是第一次构成和为key的最后一个元素的下标。因为要求获取到的是和为0的连续子数组的长度最长,所以哈希表的值是第一次构成和为key的最后一个元素的下标。

对应代码:

class Solution {
    public int findMaxLength(int[] nums) {
        int i,ans = 0,total = 0;
        for(i = 0; i < nums.length; ++i){
            if(nums[i] == 0)
               nums[i] = -1;
        }
        return getResult(nums,0);//获取数组中和为0的最长连续子数组

    }
    
    public int getResult(int[] nums,int target){
        /*
        由于数据大,所以利用双层嵌套循环的时候,就会导致超时
        int ans = 0,i,j = 0,total = 0;
        for(i = 0; i < nums.length - 1; ++i){
            total = nums[i];
            for(j = i + 1; j < nums.length; ++j){
                total += nums[j];
                if(total == target)
                  ans = Math.max(ans,j - i + 1);
            }
        }
        return ans;
        */

        //利用哈希表进行求解,其中对应的值表示长度
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        /*
        如果当前的哈希表中存在total - target,那么map.get(total - target)对应的下标为j,
        此时[j + 1,i]才是我们想要的和为target的连续子数组,清楚这一点之后,我们才会让
        哈希表键为0对应的值为-1,比如[0,1,1,0,1,1,1,0,0],将0变成-1后为
        [-1,1,1,-1,1,1,1,-1,-1],所以当第1个1结尾构成的前缀和为0,这个1对应的下标为1,那么
        1 - map.get(0 - 0) = 1 - (-1) = 2,证明了哈希表中键为0的时候对应的值为-1是正确的。

        */
        map.put(0,-1);//因为数组中没有数字0,所以假设数字为0的下标为-1
        int total = 0,ans = 0;
        for(int i = 0; i < nums.length; ++i){
            total += nums[i];
            /*
            if(map.containsKey(total - target)){
              //如果当前的哈希表中存在total - target,那么
              //map.get(total - target)对应的下标为j,以这个元素结尾的前缀和为total - target,此时
              //[j + 1,i]才是我们想要的和为target的连续子数组,所以对应的个数为i - (j + 1) + 1 = 
              // i - j也即i - map.get(total - target),并且因为target = 0,所以可以直接
              //判断为map.containsKey(total)即可

                ans = Math.max(ans,i - map.get(total - target));
            }
            if(!map.containsKey(total)) //为了保证连续子数组最长,需要保持和为total第一次出现的下标
                map.put(total,i);
                */
            if(map.containsKey(total)){
                /*
                如果当前的哈希表中存在total - target,那么map.get(total - target)对应的下标为j,
                此时[j + 1,i]才是我们想要的和为target的连续子数组,所以对应的个数为i - (j + 1) + 
                1 = i - j也即i - map.get(total - target),并且因为target = 0,所以可以直接
                判断为map.containsKey(total)即可.
                明白这一点之后,才会更加清楚明白为什么哈希表中键为0对应的值为-1
                */
                ans = Math.max(ans,i - map.get(total));
            }else
                map.put(total,i);   
        }
        return ans;
    }

}

运行结果:
在这里插入图片描述

map.put(0,-1);//因为数组中没有数字0,所以假设数字为0的下标为-1
为什么需要将哈希表中键为0对应的值置为-1?0不可以嘛?

个人认为答案是不可以。
我们先弄明白哈希表的含义先,如果当前的哈希表中存在total - target,并且map.get(total - target)对应的下标为j,也就是说[ 0,j ]的前缀和为total - target,所以此时[j + 1,i]才是我们想要的和为target的连续子数组,清楚这一点之后,我们才会让哈希表键为0对应的值为-1,当存在total - target = 0的时候,连续子数组才是[0,i],比如[0,1,1,0,1,1,1,0,0],将0变成-1后[-1,1,1,-1,1,1,1,-1,-1],所以当第1个1结尾构成的前缀和为0,这个1对应的下标为1,那么1 - map.get(0 - 0) = 1 - (-1) = 2,证明了哈希表中键为0的时候对应的值为-1是正确的。

或许可能会说,我将哈希表中的键为 0 对应的值置为0,然后将ans = Math.max(ans,i - map.get(total - target)) 改成ans = Math.max(ans,i - map.get(total - target) + 1)就好啦,怎么就不可以了?
针对于和为0的情况的确这样是没有问题的,但是如果total = 1时又怎样呢?还是上面的例子[0,1,1,0,1,1,1,0,0],将0变成-1后[-1,1,1,-1,1,1,1,-1,-1],当认真分析的时候,就会发现的确是存在问题的,在下标为4的1构成的前缀和为1,那么total - target = 1,此时哈希表中1对应的值为2,所以如果是ans = Math.max(ans,i - map.get(total - target) + 1)的话,那么i - map.get(total - target) + 1 = 3,但是本应该是2(下标为3、下标为4构成的连续子数组)。这样更好解释了当前的哈希表中存在total - target,并且map.get(total - target)对应的下标为j,也就是说[ 0,j ]的前缀和为total - target,所以此时[j + 1,i]才是我们想要的和为target的连续子数组

所以理解当前的哈希表中存在total - target,并且map.get(total - target)对应的下标为j,也就是说[ 0,j ]的前缀和为total - target,所以此时[j + 1,i]才是我们想要的和为target的连续子数组,是本题的关键,然后才会明白ans为什么是ans = Math.max(ans,i - map.get(total - target)),进而理解为什么哈希表中键为0对应的值为-1.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值