一共有三道题:
1.输入一个整数数组和一个整数k,请问数组中有多少个数字之和等于k的连续子数组?例如,输入数组[1,1,1],k=2,则返回2。
2.输入一个只包含0和1的数组,求0和1的个数相同时,最长连续子数组的长度。例如[0,1,0]是2.
3.输入一个整数数组,如果一个数字左边的子数组的数字之和等于右边的子数组的数字之和,那么返回该数字的下标,如果没有,返回-1.
这三道题其实都是针对子数组的题目, 具体的思路在每题的解答中有写。
题1:
package 剑指Offer.第二章数组.和为k的子数组; import java.util.HashMap; import java.util.Map; /** * @program:多线程和IO * @descripton:输入一个整数数组和一个整数k,请问数组中有多少个数字之和等于k的连续子数组? * @例如,输入数组[1,1,1],k=2,则返回2. * @author:ZhengCheng * @create:2021/10/7-16:50 **/ public class DemoHe { /* 分析:首先,我们不仅需要找到,等于k的子数组,其次,我们还要统计它的数值。 首先第一个难点,我们如何找到总和等于k的子数组。如果按照暴力的直接累加的方式,非常麻烦,暴力法的复杂度过高。 那么我们可以做一些优化,首先如何快速求出子数组的值,我们可以首先将所有的累加值,记录下来。 假设前i项得 m 前j项得 n 。那么 从i+1到j,这里面的子数组的和为n-m。 此时我们可以O(1)求得子数组的和 所以我们使用一个HashMap,记录下val 和 其存在的次数 ,只要我们找到 sum - k 可以从map里找到,那么我们就得到 了记一次 count++; */ public static void main(String[] args) { int[] arr = {1,2,-1,2,1,-2,1,2,1,1}; int k = 3; DemoHe d = new DemoHe(); int i = d.sunbarraySum(arr, k); System.out.println(i); } private int sunbarraySum(int[] arr ,int k){ int sum = 0; int count = 0; HashMap<Integer, Integer> map = new HashMap<>(); map.put(0,1); for (int i = 0; i < arr.length; i++) { sum += arr[i]; //如果我们找到了又sum-k 那么就说明有了一个count? //为什么不是count++,而是+= 呢 ,使用count++会少记几次。因为如果在子数组的子数组中有0的情况,那么count++就会少记。 count += map.getOrDefault(sum - k,0); //将现在得到的sum 加入到map中 map.put(sum,map.getOrDefault(sum,0)+1); } return count ; } }
题2:
package 剑指Offer.第二章数组.零和一个数相同的子数组; import java.util.HashMap; /** * @program:多线程和IO * @descripton:输入一个只包含0和1的数组,求0和1的个数相同时,最长连续子数组的长度。例如[0,1,0]是2. * @author:ZhengCheng * @create:2021/10/7-17:21 **/ public class demo { public static void main(String[] args) { } //分析,将0看成-1,即可将其转化为和为0的最长连续子数组长度。我们可以通过一个map记录index private int maxLengthArr(int[] arr){ int sum = 0; int maxLength = 0; HashMap<Integer, Integer> map = new HashMap<>(); map.put(0,-1);//起始 for (int i = 0; i < arr.length; i++) { sum += arr[i]==0 ? -1:1; if (map.containsKey(sum)){ maxLength = Math.max(maxLength,i - map.get(sum)); }else { map.put(sum,i); } } return maxLength; } }
题3:
package 剑指Offer.第二章数组.左右两边子数组的和相等; /** * @program:多线程和IO * @descripton:输入一个整数数组,如果一个数字左边的子数组的数字之和等于右边的子数组的数字之和,那么返回该 * @数字的下标,如果没有,返回-1. * @author:ZhengCheng * @create:2021/10/7-16:28 **/ public class Demo01 { //最初始的想法:通过双指针,从左到右和从右到左扫描。如果sumLeft > sumRight i++-> sumLeft += arr[i]; //通过双指针,一点一点补充,看似没有什么问题,但是最后发现,不能保证每次+arr[i] 或者+arr[j]是一定增大的,如果 //有负数出现,那么会存在问题。如果能够确定是正整数数组,那么可以用双指针法。 //分析:寻找一个这样的数字,左右两边之和相等。那么相当于我们在寻找一个sum == total - arr[i] - sum ; //的关系,所以我们先找到total ,然后再扫一遍。速度是O(n) public static void main(String[] args) { int[] arr = {1,7,3,6,2,9}; int i = new Demo01().pivotIndex(arr); System.out.println(i); } private int pivotIndex(int[] arr){ int total = 0; //total int sum = 0; //前n和 for (int i = 0; i < arr.length; i++) { total += arr[i]; } for (int i = 0; i < arr.length; i++) { sum += arr[i]; if (sum - arr[i]== total - sum){ return i; } } return -1; } }