数组专题之求和+数学定理+其他
求和
数组求和问题,即计算数组中所有元素的总和,是编程中常见的任务。以下是一些常用的解决方法:
1. 循环遍历:
- 基本思路: 通过循环遍历数组中的每个元素,并将它们累加起来。
- 实现方式 :
* for 循环 : 遍历数组索引,访问并累加每个元素。
* while 循环 : 通过索引或指针遍历数组,访问并累加每个元素。
* foreach 循环: 遍历数组元素,直接访问并累加每个元素 (适用于 PHP 等语言)。
2. 递归:
- 基本思路: 将数组分为两部分,递归计算每一部分的总和,然后将它们相加。
- 实现方式: 定义一个递归函数,输入数组及其起始和结束索引,递归计算左右两部分的总和,并返回它们的和。
3. 高级方法:
- 数学公式: 利用数学公式,例如高斯求和公式,直接计算数组中所有元素的总和。
- 并行计算: 利用多线程或分布式计算,将数组分割成多个部分,并行计算每个部分的总和,最后将它们相加。
4. 哈希表 :
(1)快速查找和去重:
-
去重: 通过哈希表,我们可以快速判断一个元素是否已经存在于数组中,从而避免重复计数,这在计算不重复元素的总和时很有用。
-
快速查找:在某些情况下,我们需要先查找数组中是否存在特定的元素,然后再决定是否将其加入总和。哈希表可以提供快速查找的功能,从而优化整个求和过程。
(2)优化排序数组求和:
- 前缀和:如果数组是排序的,我们可以利用哈希表来快速计算任意区间内元素的总和。具体方法是将每个元素及其索引存储在哈希表中,然后利用二分查找快速定位到区间起始位置,并计算区间内元素的总和。
(3)复杂求和:
- 加权求和: 如果数组元素具有不同的权重,我们可以将元素和权重作为键值对存储在哈希表中,然后根据权重计算加权总和。
1.两数之和
思路1:暴力枚举法
暴力枚举法很简单,遍历nums
数组的每一个元素,找到和target-nums[i]
相同的元素即可。
时间复杂度 :需要使用到两层for
循环,所以时间复杂度为O(n^2)
。
代码实现:
java
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)
。
代码实现:
java
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)
。
代码实现:
java
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
:
java
ArrayList list1 = new ArrayList();
List
:
java
List<List<Integer>> list2 = new ArrayList<>();
2.添加元素
ArrayList
:
(1)添加单个元素
java
list1.Add(1);
(2)添加多个元素
java
list1.addAll(Arrays.asList(a,b,c));
List
:
(1)添加单个元素
java
list2.Add(1);
(2)添加多个元素
java
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
中。这会导致时间复杂度较高。
代码实现:
java
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
的长度。
代码实现:
java
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)
代码实现:
java
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
的长度。
代码实现:
java
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
java
HashSet<String> st = new HashSet<String>();
(2)添加元素
java
st.add("hello");
(3)删除元素
java
st.remove("hello");
(4)判断某个元素是否在Set中
java
st.contains("hello");
(5)清空Set
java
st.clear();