普通数组(5) --hot 100 in leetcode
最大子数组和 算法概述 原题
此题要求为找到最大的子数组的和。因为这个题目只要求一个最终的和,所以完全不需要双指针和滑动窗口这样相对负责的结构,简单的dp(动态规划)就能做出来。
时间复杂度为O(n):遍历一次
空间复杂度为O(1):只需要存储中间常数
JAVA 1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public int maxSubArray (int [] nums) { int pre = 0 , maxAns = nums[0 ]; for (int x : nums) { pre = Math.max(pre + x, x); maxAns = Math.max(maxAns, pre); } return maxAns; } }
C++ 1 2 3 4 5 6 7 8 9 10 11 class Solution {public : int maxSubArray (vector<int >& nums) { int pre=0 ,max_=nums[0 ]; for (const auto &num:nums){ pre=max (pre+num,num); max_=max (pre,max_); } return max_; } };
重要实例方法及属性(C++) max_=nums[0]
:注意不要和关键字重名
注意 max(pre+num,num)
:max()
应该作用在前缀和和当前值之上,因为前缀和不一定是最大的,甚至可能为负值,而取单值作为最终子数组也未尝不可,要注意考虑到这个关键的可能性。
还有分治法(线段树)可用,后面再更新。
合并区间 算法概述 原题
这道题要求把给出的大数组中的小数组范围交叠的合并起来。可以通过遍历(还需要加上不少判断)解决,但也可以通过排序,判断永远会存在,但排序的开销应该不是数据集要多大就会产生多大的。
时间复杂度为O(nlogn):排序和一次线性扫描(遍历的时间)
空间复杂度为O(logn):排序
JAVA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public int [][] merge(int [][] intervals) { if (intervals.length == 0 ) { return new int [0 ][2 ]; } Arrays.sort(intervals, new Comparator <int []>() { public int compare (int [] interval1, int [] interval2) { return interval1[0 ] - interval2[0 ]; } }); List<int []> merged = new ArrayList <int []>(); for (int i = 0 ; i < intervals.length; ++i) { int L = intervals[i][0 ], R = intervals[i][1 ]; if (merged.size() == 0 || merged.get(merged.size() - 1 )[1 ] < L) { merged.add(new int []{L, R}); } else { merged.get(merged.size() - 1 )[1 ] = Math.max(merged.get(merged.size() - 1 )[1 ], R); } } return merged.toArray(new int [merged.size()][]); } }
重要实例方法及属性(JAVA) toArray(T[] a)
:返回特定类型的数组Arrays.sort(intervals, new Comparator<int[]>())
:使用比较器Comparator
进行比较,比较器是需要实现的接口,当比较器返回负值,则第一个参数在前
C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : vector<vector<int >> merge (vector<vector<int >>& intervals) { if (intervals.size () == 0 ) { return {}; } sort (intervals.begin (), intervals.end ()); vector<vector<int >> merged; for (int i = 0 ; i < intervals.size (); ++i) { int L = intervals[i][0 ], R = intervals[i][1 ]; if (!merged.size () || merged.back ()[1 ] < L) { merged.push_back ({L, R}); } else { merged.back ()[1 ] = max (merged.back ()[1 ], R); } } return merged; } };
重要实例方法及属性(C++) vector.back()
:返回最后一个元素(不需要像java那样使用getter)sort()
:默认升序排列,且默认比较每个子数组中的第一个元素,如相同,则比较后续元素
注意 即使题目解答思路简单,但仍然需要考虑如何灵活使用STL algorithm或实例方法使代码更加优雅与清晰。 在可以使用更加高效的内置工具解决问题的时候,应该尽量避免使用简单的if来模拟相似的效果。
轮转数组 算法概述 原题
题目要求为将给定数组轮转指定次数。创建一个新数组存储新索引下的旧数组的值,会消耗较大空间,所以可以使用 中间变量代替。
时间复杂度为O(n):遍历一次
空间复杂度为O(1):中间变量存储
JAVA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public void rotate (int [] nums, int k) { int n = nums.length; k = k % n; int count = gcd(k, n); for (int start = 0 ; start < count; ++start) { int current = start; int prev = nums[start]; do { int next = (current + k) % n; int temp = nums[next]; nums[next] = prev; prev = temp; current = next; } while (start != current); } } private int gcd (int x, int y) { return y > 0 ? gcd(y, x % y) : x; } }
C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : void rotate (vector<int >& nums, int k) { int n = nums.size (); k = k % n; int count = gcd (k, n); for (int start = 0 ; start < count; ++start) { int current = start; int prev = nums[start]; do { int next = (current + k) % n; swap (nums[next], prev); current = next; } while (start != current); } } };
重要实例方法及属性(C++) gcd(a,b)
:使用欧几里得算法求最大公约数
注意 C++在较新版本的 numeric 中有很多新方法,可以活用。其实这个算法每个while还是将整个数组遍历完了,只不过是跳着来的, do-while 是必须的,确保最后一个遍历的有效运行。这其实是一个一直往后跳,到最后再跳回每跳过的,再往后跳的循环过程,依照最大公约数来划定外层循环次数能够有效减少总循环。
除自身以外数组的乘积 算法概述 原题
题目要求在相同索引处返回除相同索引元素外其他元素的乘积(不能使用除法)。使用两个左右数组对当前遍历索引的左右侧的元素分别求前缀积和后缀积,通过改善算法,也可以将输出数组作为左数组,选择中间变量作为右数组直接和输出数组交互,也就是右缀积直接和左缀积相乘(也可以用双指针替代,还更简单)。
时间复杂度为O(n):两次遍历
空间复杂度为O(1):只有中间变量存储
JAVA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int [] productExceptSelf(int [] nums) { int length=nums.length; int [] answer=new int [length]; answer[0 ]=1 ; for (int i=1 ;i<length;i++){ answer[i]=nums[i-1 ]*answer[i-1 ]; } int R=1 ; for (int i=length-1 ;i>=0 ;i--){ answer[i]=answer[i]*R; R*=nums[i]; } return answer; } }
C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : vector<int > productExceptSelf (vector<int >& nums) { int length = nums.size (); vector<int > answer (length) ; answer[0 ] = 1 ; for (int i = 1 ; i < length; i++) { answer[i] = nums[i - 1 ] * answer[i - 1 ]; } int R = 1 ; for (int i = length - 1 ; i >= 0 ; i--) { answer[i] = answer[i] * R; R *= nums[i]; } return answer; } };
注意 要思考如何用输出数组对空间复杂度进行优化,在同一个循环内完成更新和操作两个工作。 注意本题内首尾是特殊的,一个前缀积默认为1,一个后缀积默认为1,需要设置好左右数组的默认值。
缺失的第一个正数 算法概述 原题
题目要求为找出数组中缺失的最小正数。我第一反应是哈希秒了,但原地直接使用哈希是最慢的。哈希是没有错的,但是应该换一种思路
JAVA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public int firstMissingPositive (int [] nums) { int n = nums.length; for (int i = 0 ; i < n; ++i) { if (nums[i] <= 0 ) { nums[i] = n + 1 ; } } for (int i = 0 ; i < n; ++i) { int num = Math.abs(nums[i]); if (num <= n) { nums[num - 1 ] = -Math.abs(nums[num - 1 ]); } } for (int i = 0 ; i < n; ++i) { if (nums[i] > 0 ) { return i + 1 ; } } return n + 1 ; } }
C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : int firstMissingPositive (vector<int >& nums) { int n = nums.size (); for (int & num: nums) { if (num <= 0 ) { num = n + 1 ; } } for (int i = 0 ; i < n; ++i) { int num = abs (nums[i]); if (num <= n) { nums[num - 1 ] = -abs (nums[num - 1 ]); } } for (int i = 0 ; i < n; ++i) { if (nums[i] > 0 ) { return i + 1 ; } } return n + 1 ; } };
注意 nums[num - 1] = -abs(nums[num - 1]);
:这一行不能直接改为任意负数,因为这一行的功能不仅包含把正整数改为相应索引的负数,而且还将该负数的值确定为原值的负值,也就意味着对于当循环遍历到了这些被修改过的索引值时,只需取绝对值(int num = abs(nums[i])
)就仍然按照正常流程工作。
还需要学习的是这种 借助索引 和 添加标记 的能力。