数组专题之求和+数学定理+其他
- 求和
- 1.两数之和
- 15.三数之和
- 数学定理
- 169.多数元素
- 其他
- 4.寻找两个正序数组的中位数
- 128.最长连续序列
求和
数组求和问题,即计算数组中所有元素的总和,是编程中常见的任务。以下是一些常用的解决方法:
1. 循环遍历:
- 基本思路: 通过循环遍历数组中的每个元素,并将它们累加起来。
- 实现方式:
* for 循环: 遍历数组索引,访问并累加每个元素。
* while 循环: 通过索引或指针遍历数组,访问并累加每个元素。
* foreach 循环: 遍历数组元素,直接访问并累加每个元素 (适用于 PHP 等语言)。
2. 递归:
- 基本思路: 将数组分为两部分,递归计算每一部分的总和,然后将它们相加。
- 实现方式: 定义一个递归函数,输入数组及其起始和结束索引,递归计算左右两部分的总和,并返回它们的和。
3. 高级方法:
- 数学公式: 利用数学公式,例如高斯求和公式,直接计算数组中所有元素的总和。
- 并行计算: 利用多线程或分布式计算,将数组分割成多个部分,并行计算每个部分的总和,最后将它们相加。
4. 哈希表:
(1)快速查找和去重:
-
去重: 通过哈希表,我们可以快速判断一个元素是否已经存在于数组中,从而避免重复计数,这在计算不重复元素的总和时很有用。
-
快速查找:在某些情况下,我们需要先查找数组中是否存在特定的元素,然后再决定是否将其加入总和。哈希表可以提供快速查找的功能,从而优化整个求和过程。
(2)优化排序数组求和:
- 前缀和:如果数组是排序的,我们可以利用哈希表来快速计算任意区间内元素的总和。具体方法是将每个元素及其索引存储在哈希表中,然后利用二分查找快速定位到区间起始位置,并计算区间内元素的总和。
(3)复杂求和:
- 加权求和: 如果数组元素具有不同的权重,我们可以将元素和权重作为键值对存储在哈希表中,然后根据权重计算加权总和。
1.两数之和
思路1:暴力枚举法
暴力枚举法很简单,遍历nums
数组的每一个元素,找到和target-nums[i]
相同的元素即可。
时间复杂度:需要使用到两层for
循环,所以时间复杂度为O(n^2)
。
代码实现:
class Solution {public int[] twoSum(int[] nums, int target) {for(int i=0;i<nums.length;i++){for(int j=i+1;j<nums.length;j++){if(nums[j]==target-nums[i]){return new int[] {i,j};} } }return null;}
}
思路2:哈希表法
使用哈希表来解决该问题的思路是,创建一个哈希表来存放数据,其中key
为target-nums[i]
,value
为对应的索引。在遍历数组时,将数组中元素的值和对应的索引记录到哈希表中,检查当前元素的补数(target-nums[i]
)是否在哈希表中,如果在,其value
即为所求,若不在,将元素记录到哈希表中并接着遍历数组。
时间复杂度:只需要遍历一次数组,用到一个for
循环,所以时间复杂度为O(n)
。
代码实现:
class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer,Integer> tab = new HashMap<>();for(int i=0;i<nums.length;i++){int tar = target-nums[i];if(tab.containsKey(tar)){return new int[] {i,tab.get(tar)};}else{tab.put(nums[i],i);}}return null;}
}
15.三数之和
思路:
本题采用枚举+双指针的做法,外循环对nums
的元素依次遍历,在遍历时寻找可以和外循环元素相加为0的元素对,内层循环采用首尾双指针进行遍历,但是这样做的前提是需要首先对数组进行排序,最后需要注意由于不能输出重复的结果,所以每次遍历时都要跳过相同元素。详细的讲解参考视频讲解-三数之和。
时间复杂度:
这段代码的时间复杂度为O(n^2)
,其中n
为数组nums
的长度。主要的时间复杂度来源于两层循环,外层循环遍历数组nums
,内层循环使用双指针法遍历数组中的剩余元素。在最坏情况下,内层循环的时间复杂度为O(n)
,所以总的时间复杂度为O(n^2)
。
代码实现:
class Solution {public List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> ans = new ArrayList<>();//对数组进行排序,方便采用首尾指针进行遍历Arrays.sort(nums);for(int i=0;i<nums.length;i++){//跳过重复元素if(i > 0 && nums[i] == nums[i-1]) continue;int l = i + 1;int r = nums.length - 1;int target = 0 - nums[i];while(l < r){if(nums[l] + nums[r] == target){ans.add(Arrays.asList(nums[i],nums[l],nums[r]));//跳过重复元素while(l < r && nums[l] == nums[ l + 1]) l++;while(l < r && nums[r] == nums[r - 1]) r--;l++;r--;}else if(nums[l] + nums[r] < target){l++;}else{r--;}}}return ans;}
}
知识拓展:Java
中动态数组的使用(ArrayList
和List
)
区别:
1.List
是ArrayList
的泛型等效类,List
是一个接口,而ArrayList
是一个类,它实现了List
接口,所以List
不能被构造,List list=new List()
这种写法是错误的,而ArrayList
就可以被构造。List list = new ArrayList();
这句创建了一个ArrayList
的对象后把向上转型成了List
。此时它是一个List
对象了,有些ArrayList
有但是List
没有的属性和方法,它就不能再用了。而ArrayList list=new ArrayList();
创建一对象则保留了ArrayList
的所有属性。
2.List
相比ArrayList
来说更加安全,因为ArrayList
加入的数据为object
类型,需要装箱和拆箱操作。List
声明时就决定了类型,所以是类型安全的,省掉了装箱与拆箱的过程,并且效率更高。
用法:
1.定义
ArrayList
:
ArrayList list1 = new ArrayList();
List
:
List<List<Integer>> list2 = new ArrayList<>();
2.添加元素
ArrayList
:
(1)添加单个元素
list1.Add(1);
(2)添加多个元素
list1.addAll(Arrays.asList(a,b,c));
List
:
(1)添加单个元素
list2.Add(1);
(2)添加多个元素
list2.add(Arrays.asList(a,b,c));
数学定理
169.多数元素
思路1:
使用哈希表来解决,在哈希表中记录每个数组元素出现的次数,以数组元素为key
,出现次数为value
,遍历数组,如果nums[i]
的出现次数大于n/2
,则返回该元素,否则,更新该元素在哈希表中的value
值。
时间复杂度:
时间复杂度取决于两个主要因素:循环遍历不同元素的次数和在HashMap
中添加和访问元素的次数。
- 在循环遍历数组元素时,需要遍历
n
个元素,因此时间复杂度为O(n)
。 - 在HashMap中添加或访问元素的平均时间复杂度为
O(1)
。但是在最坏情况下,所有元素都是不同的,因此在HashMap
中添加或访问元素的时间复杂度为O(n)
。 - 因此,总体时间复杂度为
O(n^2)
。
需要注意的是,虽然代码中使用了HashMap
来记录元素出现的次数,但是在最坏情况下,每次遍历都需要进行线性搜索以查找元素是否已经在HashMap
中。这会导致时间复杂度较高。
代码实现:
class Solution {public int majorityElement(int[] nums) {Map<Integer,Integer> record = new HashMap<>();int n = nums.length;int cpm = n / 2;for(int i = 0;i <n;i++){int count = record.getOrDefault(nums[i],0) + 1;if(count > cpm){return nums[i];}else{record.put(nums[i],count);}}return -1; }
}
思路2:
Boyer-Moore 投票算法
:每次都找出一对不同的元素,从数组中删除,直到数组为空或只有一种元素。如果存在元素 e
出现频率超过半数,那么数组中最后剩下的就只有 e
。简单来说,就是数组元素之间相互抵消,遇到相同的就加1,不同的就减1,当count‘为0的时候就需要更新比较的元素值,说明上一个元素已经被抵消完了,需要换一个出现次数更多的元素,最后剩下来的元素一定是那个多数元素,视频讲解点击视频讲解-多数元素。
时间复杂度:
时间复杂度为O(n)
,其中n是数组nums
的长度。
代码实现:
class Solution {public int majorityElement(int[] nums) {int ans = -1;int count = 0;for(int i = 0; i < nums.length;i++){if(count == 0){ans = nums[i];count++;}else if(nums[i] == ans){count++;}else{count--;}}return ans;}
}
其他
4.寻找两个正序数组的中位数
思路:
该题的解决采用二分思想,只需要给出两个有序数组一个恰当的【分割线】,中位数的值就由位于这个【分割线】的两侧的数来决定,确定分割线的位置使用二分查找法,需要注意的点是:分割线左边的所有元素的数值<分割线右边所有元素的数值。
注:这里的思路写的比较简单,详细的可以看这个视频详细思路-寻找两个正序数组的中位数
时间复杂度:
时间复杂度为O(log min(m,n)),m,n分别为两数组的长度,空间复杂度为O(1)
代码实现:
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {//将nums1设置为长度较小的数组,方便代码的编写if(nums1.length>nums2.length){int[] temp = nums1;nums1 = nums2;nums2 = temp;}int m = nums1.length;int n = nums2.length;//计算分割线左边元素的数量int totalleft = (m+n+1)/2;//二分法查找nums1部分的分割线int left = 0;int right = m;while(left<right){//这是对于(left+right)/2的特殊处理方式,防止发生整型溢出//同时使用二分法时,如果出现left=i,则这里需要+1,否则不需要int i = left+(right-left+1)/2;int j = totalleft - i;if(nums1[i-1] > nums2[j]){right = i - 1;}else{left = i;}}int i = left;int j = totalleft - i;//最后得到两个数组分割线左右两边元素的最大值的最小值//为了防止出现分割线左右两边没有元素的极端情况加上判断int nums1LeftMax = i ==0 ? Integer.MIN_VALUE : nums1[i-1];int nums1RightMin = i ==m ? Integer.MAX_VALUE : nums1[i];int nums2LeftMax = j == 0 ? Integer.MIN_VALUE : nums2[j-1];int nums2RightMin = j == n ? Integer.MAX_VALUE : nums2[j];//计算中位数的值if(((m+n) % 2) == 1){return Math.max(nums1LeftMax,nums2LeftMax);}else{return (double) ((Math.max(nums1LeftMax,nums2LeftMax) + Math.min(nums1RightMin,nums2RightMin))) / 2;}}
}
128.最长连续序列
思路:
本题使用Set
求解,将数组中的元素用Set
存储,去掉重复元素。我们可以Set
中的元素映射到一个数轴上来,可以看到连续的元素会形成一个区间,只要我们找到每个区间的第一个元素,然后通过判断Set
中是否包含以该元素开头的连续区间的元素,最后得到区间的长度,然后在所有长度中取最大值即可。
时间复杂度:
时间复杂度为O(n)
,其中n是数组nums
的长度。
代码实现:
class Solution {public int longestConsecutive(int[] nums) {Set<Integer> set = new HashSet<>();int ans = 0;for(int num : nums){set.add(num);}for(int item : set){if(!set.contains(item - 1)){int x = item + 1;while(set.contains(x)) x++;ans = Math.max(ans,x - item);}}return ans;}
}
知识扩展:
HashSet的使用
HashMap
同时也被称为集合,该容器中只能存储不重复的对象,常用来去重。
(1)新建一个HashSet
HashSet<String> st = new HashSet<String>();
(2)添加元素
st.add("hello");
(3)删除元素
st.remove("hello");
(4)判断某个元素是否在Set中
st.contains("hello");
(5)清空Set
st.clear();