当前位置:   article > 正文

高频数组合集——从LeetCode题海中总结常见套路_leetcode所有套路

leetcode所有套路

之前总结过一篇数组水题合集:数组水题合集——LeetCode题海实战汇总,这里总结一下高频数组合集

目录

双指针or二分查找:LeetCode4.寻找两个正序数组的中位数

最直接的思路:合并后排序寻找数组的中位数

双指针插入排序法

双指针优化版本

官方解法:二分查找

原地删除或手动删除:LeetCode26.删除排序数组中的重复项

原地删除法:

手动删除法:会有内存泄漏的风险

“童年的记忆型”:LeetCode27.移除元素

经典二分法:LeetCode33.搜索旋转排序数组

转换成更小子问题+左右两边遍历比较:LeetCode152.乘积最大子数组

“左右开弓型”:LeetCode238.除自身以外数组的乘积

常规做法:

“左右开弓法”:

LeetCode7.整数反转

常规暴力解法:

奇技淫巧型:

官方题解:弹出和推入数字 + 溢出前进行检查

借鉴动态规划的思想:LeetCode739.每日温度

经典双指针:LeetCode167.两数之和II-输入有序数组

暴力法

二分法:

双指针,剑指offer

四种方法解:LeetCode189.旋转数组

方法一:暴力交换法

方法二:另开辟数组然后复制

方法三:三次翻转数组求解

左右指针:LeetCode581.最短无序连续子数组


双指针or二分查找:LeetCode4.寻找两个正序数组的中位数

最直接的思路:合并后排序寻找数组的中位数

这种思路没有利用原先的两个数组就是已经正序的,这样的做法虽然思路简单,时空复杂度会很高,但是能快速AC一个困难级别的题目,也不是没有任何意义。

  1. class Solution {
  2. public:
  3. double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
  4. // 合并数组之后再排序,时空复杂度最高
  5. vector<int> v;
  6. v.insert(v.end(),nums1.begin(),nums1.end());
  7. v.insert(v.end(),nums2.begin(),nums2.end());
  8. sort(v.begin(),v.end());
  9. double n;
  10. if(v.size()%2==0)
  11. n = (double(v[v.size()/2])+double(v[(v.size()/2)-1]))/2;
  12. else
  13. n = v[v.size()/2];
  14. return n;
  15. }
  16. };

双指针插入排序法

利用了两个数组都是正序的性质,使用双指针进行排序,时间复杂度有所提高

  1. class Solution {
  2. public:
  3. double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
  4. // 利用数组正序的性质,双指针插入排序
  5. vector<int> v;
  6. int i = 0, j = 0;
  7. while (i < nums1.size() && j < nums2.size()) {
  8. if (nums1[i] < nums2[j]) {
  9. v.push_back(nums1[i]);
  10. i++;
  11. } else if (nums1[i] > nums2[j]) {
  12. v.push_back(nums2[j]);
  13. j++;
  14. } else {
  15. v.push_back(nums1[i]);
  16. v.push_back(nums2[j]);
  17. i++;
  18. j++;
  19. }
  20. }
  21. if (i < nums1.size()) {
  22. while (i < nums1.size()) {
  23. v.push_back(nums1[i]);
  24. i++;
  25. }
  26. } else if (j < nums2.size()){
  27. while (j < nums2.size()) {
  28. v.push_back(nums2[j]);
  29. j++;
  30. }
  31. }
  32. double n;
  33. if(v.size()%2==0)
  34. n = (double(v[v.size()/2])+double(v[(v.size()/2)-1]))/2;
  35. else
  36. n = v[v.size()/2];
  37. return n;
  38. }
  39. };

双指针优化版本

其实不用计算完全部的数组再做判断,只需要做出将数组计算到中间的位置然后进行判断即可,这样理论上会剩下一半的时间复杂度。

  1. class Solution {
  2. public:
  3. double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
  4. // 利用数组正序的性质,双指针插入排序
  5. // 其实不用全部将其排序完毕,我们只需要中间的数即可终止程序
  6. length = nums1.size() + nums2.size();
  7. double n;
  8. vector<int> v;
  9. int i = 0, j = 0;
  10. while (i < nums1.size() && j < nums2.size()) {
  11. if (nums1[i] < nums2[j]) {
  12. v.push_back(nums1[i]);
  13. i++;
  14. } else if (nums1[i] > nums2[j]) {
  15. v.push_back(nums2[j]);
  16. j++;
  17. } else {
  18. v.push_back(nums1[i]);
  19. v.push_back(nums2[j]);
  20. i++;
  21. j++;
  22. }
  23. // 只用计算到一半即可
  24. if (v.size() >= length/2 + 1)
  25. return help(v);
  26. }
  27. if (i < nums1.size()) {
  28. while (i < nums1.size()) {
  29. v.push_back(nums1[i]);
  30. i++;
  31. if (v.size() >= length/2 + 1) {
  32. return help(v);
  33. }
  34. }
  35. } else if (j < nums2.size()){
  36. while (j < nums2.size()) {
  37. v.push_back(nums2[j]);
  38. j++;
  39. if (v.size() >= length/2 + 1)
  40. return help(v);
  41. }
  42. }
  43. return 0;
  44. }
  45. private:
  46. int length;
  47. double help(vector<int> v) {
  48. double ans;
  49. if (length%2 == 0) {
  50. ans = (double(v[length/2])+double(v[length/2 - 1]))/2;
  51. } else {
  52. ans = v[length/2];
  53. }
  54. return ans;
  55. }
  56. };

官方解法:二分查找

理论上时间复杂度一样,空间复杂度会有提升

  1. class Solution {
  2. public:
  3. int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k) {
  4. /* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
  5. * 这里的 "/" 表示整除
  6. * nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1
  7. * nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1
  8. * 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2
  9. * 这样 pivot 本身最大也只能是第 k-1 小的元素
  10. * 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
  11. * 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
  12. * 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
  13. */
  14. int m = nums1.size();
  15. int n = nums2.size();
  16. int index1 = 0, index2 = 0;
  17. while (true) {
  18. // 边界情况
  19. if (index1 == m) {
  20. return nums2[index2 + k - 1];
  21. }
  22. if (index2 == n) {
  23. return nums1[index1 + k - 1];
  24. }
  25. if (k == 1) {
  26. return min(nums1[index1], nums2[index2]);
  27. }
  28. // 正常情况
  29. int newIndex1 = min(index1 + k / 2 - 1, m - 1);
  30. int newIndex2 = min(index2 + k / 2 - 1, n - 1);
  31. int pivot1 = nums1[newIndex1];
  32. int pivot2 = nums2[newIndex2];
  33. if (pivot1 <= pivot2) {
  34. k -= newIndex1 - index1 + 1;
  35. index1 = newIndex1 + 1;
  36. }
  37. else {
  38. k -= newIndex2 - index2 + 1;
  39. index2 = newIndex2 + 1;
  40. }
  41. }
  42. }
  43. double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
  44. int totalLength = nums1.size() + nums2.size();
  45. if (totalLength % 2 == 1) {
  46. return getKthElement(nums1, nums2, (totalLength + 1) / 2);
  47. }
  48. else {
  49. return (getKthElement(nums1, nums2, totalLength / 2) + getKthElement(nums1, nums2, totalLength / 2 + 1)) / 2.0;
  50. }
  51. }
  52. };

原地删除或手动删除:LeetCode26.删除排序数组中的重复项

原地删除法:

和下面的LeetCode27题一样的方式,使用erase,不会有内存泄漏的风险,但是样例测出来的时间复杂度会比下面的方式高,对比一下时间,还是很明显的:

  1. class Solution {
  2. public:
  3. int removeDuplicates(vector<int>& nums) {
  4. // 原地删除法
  5. if (nums.size() < 2)
  6. return nums.size();
  7. for (int i = 1; i < nums.size(); i++) {
  8. if (nums[i - 1] == nums[i]) {
  9. nums.erase(nums.begin() + i);
  10. i--;
  11. }
  12. }
  13. return nums.size();
  14. }
  15. };

手动删除法:会有内存泄漏的风险

我们这里只返回了 ++i 长度的数组,会造成什么呢,后面长度数组我们没有返回,也没有管,OJ也只会去读取我们返回长度的数组,所以这里虽然时间复杂度上优化了很多,但是我觉得这这种方法并不好

  1. class Solution {
  2. public:
  3. int removeDuplicates(vector<int>& nums) {
  4. if(nums.size() < 2)
  5. return nums.size();
  6. // 手动删除的方式,不断交换并删除前面没有被删除长度的方式
  7. // 虽然避免了每次erase造成时间复杂度上的压力,但是会有内存泄漏的风险
  8. int i = 0;
  9. for(int j = 1; j < nums.size(); j++) {
  10. if(nums[i] != nums[j]){
  11. i++;
  12. nums[i] = nums[j];
  13. }
  14. }
  15. return ++i;
  16. }
  17. };

“童年的记忆型”:LeetCode27.移除元素

这题大一的时候肯定做过,应该是谭浩强那本书上的例题,但是当时不会STL,不会erase,现在用STL还是很爽的……

  1. class Solution {
  2. public:
  3. int removeElement(vector<int>& nums, int val) {
  4. for(int i = 0;i<nums.size();i++){
  5. if(nums[i]==val){
  6. nums.erase(nums.begin()+i);
  7. i--;//记得原地修改的时候,前面移除了一个元素这里要将i--
  8. }
  9. }
  10. return nums.size();
  11. }
  12. };

经典二分法:LeetCode33.搜索旋转排序数组

剑指offer上的原题,不多说了,非常经典

  1. class Solution {
  2. public int search(int[] nums, int target) {
  3. int len = nums.length;
  4. int left = 0, right = len-1;
  5. while(left <= right){
  6. int mid = (left + right) / 2;
  7. if(nums[mid] == target)
  8. return mid;
  9. else if(nums[mid] < nums[right]){
  10. if(nums[mid] < target && target <= nums[right])
  11. left = mid+1;
  12. else
  13. right = mid-1;
  14. }
  15. else{
  16. if(nums[left] <= target && target < nums[mid])
  17. right = mid-1;
  18. else
  19. left = mid+1;
  20. }
  21. }
  22. return -1;
  23. }
  24. }

转换成更小子问题+左右两边遍历比较:LeetCode152.乘积最大子数组

思路: 求最大值,可以看成求被0拆分的各个子数组的最大值。

先计算从左到右的相乘的最大值,再计算从右到左的最大值;再将两组最大值相比,

当一个数组中没有0存在,则分为两种情况:

1.负数为偶数个,则整个数组的各个值相乘为最大值;

2.负数为奇数个,则从左边开始,乘到最后一个负数停止有一个“最大值”,从右边也有一个“最大值”,比较,得出最大值。

  1. class Solution {
  2. public:
  3. int maxProduct(vector<int>& nums) {
  4. // 问题转换成求被0分割的各个子数组的最大乘积问题
  5. int maxNum = nums[0];
  6. // 左边求出最大乘积子数组问题
  7. int a = 1;
  8. for (auto num : nums) {
  9. a *= num;
  10. if (a > maxNum)
  11. maxNum = a;
  12. // 遇到0后将数组分割
  13. if (num == 0)
  14. a = 1;
  15. }
  16. // 右边求出最大乘积子数组问题
  17. int b = 1;
  18. for (int i = nums.size()-1; i>=0; i--) {
  19. b *= nums[i];
  20. if (b > maxNum)
  21. maxNum = b;
  22. // 遇到0后将数组分割
  23. if (nums[i] == 0)
  24. b = 1;
  25. }
  26. // 返回左右两侧找到的最大值
  27. return maxNum;
  28. }
  29. };

“左右开弓型”:LeetCode238.除自身以外数组的乘积

常规做法:

  1. class Solution {
  2. public:
  3. vector<int> productExceptSelf(vector<int>& nums) {
  4. int a = 1;
  5. for(int i=0;i<nums.size();i++){
  6. a*=nums[i];
  7. }
  8. if(a==0){
  9. vector<int> v;
  10. for(int i=0;i<nums.size();i++){
  11. int temp = 1;
  12. for(int j = 0; j<nums.size();j++){
  13. if(i!=j){
  14. temp*=nums[j];
  15. }
  16. }
  17. v.push_back(temp);
  18. }
  19. return v;
  20. }
  21. for(int i=0;i<nums.size();i++){
  22. nums[i] = a/nums[i];
  23. }
  24. return nums;
  25. }
  26. };

“左右开弓法”:

这种类型其实很有意思,可以参考接雨水那道题,求道不求术!

 

 

 

LeetCode7.整数反转

常规暴力解法:

真是吐了,坑就一大堆,佛了,最后连个取负符号都能超时???那还能怎样,吐了

让我想起了恶心的PAT,当时那种就是这样的,每道题都要考虑大数问题,这种求术不求道的,除了培养一点异常捕获思想,基本没啥用……

  1. class Solution {
  2. public:
  3. int reverse(int x) {
  4. if(x>=1534236469||x<-2147483648)
  5. return 0;
  6. bool flag = true;
  7. if(x<0)
  8. x = -x;
  9. else
  10. flag = false;
  11. string s = to_string(x);
  12. long long int ans = 0;
  13. for(int i = 0;i<s.size();i++){
  14. ans+=((s[i]-'0')*pow(10,i));
  15. }
  16. // int ans = stoi(ss); //stoi会超范围
  17. if(flag)
  18. ans = -ans;
  19. return ans;
  20. }
  21. };

奇技淫巧型:

说实话这种奇技淫巧也就图一时的快感,没啥卵用

几个月后面试一问,肯定没办法立马AC

所以一定要求道不求术啊!

  1. class Solution {
  2. public:
  3. int reverse(int x) {
  4. int max = 0x7fffffff, min = 0x80000000;//int的最大值最小值
  5. long rs = 0;//用long类型判断溢出
  6. for(;x;rs = rs*10+x%10,x/=10);//逆序,正负通吃,不用单独考虑负值
  7. return rs>max||rs<min?0:rs;//超了最大值低于最小值就返回0
  8. }
  9. };

官方题解:弹出和推入数字 + 溢出前进行检查

挺无聊的解法……没啥意思呵呵

溢出检查的是这道题的亮点

  1. class Solution {
  2. public:
  3. int reverse(int x) {
  4. int rev = 0;
  5. while (x != 0) {
  6. int pop = x % 10;
  7. x /= 10;
  8. if (rev > INT_MAX/10 || (rev == INT_MAX / 10 && pop > 7)) return 0;
  9. if (rev < INT_MIN/10 || (rev == INT_MIN / 10 && pop < -8)) return 0;
  10. rev = rev * 10 + pop;
  11. }
  12. return rev;
  13. }
  14. };

借鉴动态规划的思想:LeetCode739.每日温度

做过那么多左右开弓型的数组题,这道题最好的思路应该不难想到从后向前推

明显的动态规划思想:从n到n-1....1!

  1. * 根据题意,从最后一天推到第一天,这样会简单很多。因为最后一天显然不会再有升高的可能,结果直接为0
  2. * 再看倒数第二天的温度,如果比倒数第一天低,那么答案显然为1,如果比倒数第一天高,又因为倒数第一天
  3. * 对应的结果为0,即表示之后不会再升高,所以倒数第二天的结果也应该为0。
  4. * 自此我们容易观察出规律,要求出第i天对应的结果,只需要知道第i+1天对应的结果就可以:
  5. * - 若T[i] < T[i+1],那么res[i]=1;
  6. * - 若T[i] > T[i+1]
  7. * - res[i+1]=0,那么res[i]=0;
  8. * - res[i+1]!=0,那就比较T[i]和T[i+1+res[i+1]](即将第i天的温度与比第i+1天大的那天的温度进行比较)

  1. class Solution {
  2. public:
  3. vector<int> dailyTemperatures(vector<int>& T) {
  4. vector<int> v(T.size());
  5. v[T.size()-1] = 0;
  6. for(int i=T.size()-2;i>=0;i--){
  7. // 要求出第i天对应的结果,只需要求出第i+1天对应的结果
  8. for(int j=i+1;j<T.size();j+=v[j]){
  9. if(T[i]<T[j]){
  10. v[i] = j-i;
  11. break;
  12. }
  13. // 如果对应的结果为零,表示以后都不会再升高了,所以此时的结果也为0
  14. else if(v[j]==0){
  15. v[i]=0;
  16. break;
  17. }
  18. }
  19. }
  20. return v;
  21. }
  22. };

经典双指针:LeetCode167.两数之和II-输入有序数组

暴力法的时间复杂度是O(N*N),二分法为O(N*logN),双指针为O(N),所以以后排序数组的题不能下意识想到二分,双指针也是要下意识想到的!!!

暴力法

肯定超时;

就算不超时也拿不到offer

  1. class Solution {
  2. public:
  3. vector<int> twoSum(vector<int>& numbers, int target) {
  4. vector<int> ans;
  5. for(int i = 0; i<numbers.size()-1; i++){
  6. for(int j = i+1; j<numbers.size(); j++){
  7. if(numbers[i]+numbers[j]==target){
  8. ans.push_back(i+1);
  9. ans.push_back(j+1);
  10. return ans;
  11. }else if(numbers[i]+numbers[j]>target){
  12. break;
  13. }
  14. }
  15. }
  16. return ans;
  17. }
  18. };

二分法:

很奇怪,居然没调通

先放到这儿,日后再看

  1. class Solution {
  2. public:
  3. vector<int> twoSum(vector<int>& numbers, int target) {
  4. vector<int> ans;
  5. for(int i = 0; i<numbers.size()-1; i++){
  6. int left = i+1;
  7. int right = numbers.size()-1;
  8. while(left<=right){
  9. int mid = left+(right-left)/2;
  10. if(numbers[mid]+numbers[i]==target){
  11. ans.push_back(i+1);
  12. ans.push_back(mid+1);
  13. return ans;
  14. }else if(numbers[mid]+numbers[i]<target){
  15. left = mid+1;
  16. }else{
  17. right = mid;
  18. }
  19. }
  20. }
  21. return ans;
  22. }
  23. };

双指针,剑指offer

  1. class Solution {
  2. public:
  3. vector<int> twoSum(vector<int>& numbers, int target) {
  4. vector<int> ans;
  5. int left = 0;
  6. int right = numbers.size()-1;
  7. while(left<right){
  8. if(numbers[left]+numbers[right]==target){
  9. ans.push_back(left+1);
  10. ans.push_back(right+1);
  11. return ans;
  12. }else if(numbers[left]+numbers[right]<target){
  13. left++;
  14. }else{
  15. right--;
  16. }
  17. }
  18. return ans;
  19. }
  20. };

四种方法解:LeetCode189.旋转数组

这道题很经典,我记得当时是华为还是360的有过这道题,不过那道题没有要求要在原地修改,并且是机试,要求比较低;这里要求要原地修改,很有意思。

方法一:暴力交换法

暴力交换法,每次交换一个数组,会超时

  1. class Solution {
  2. public:
  3. void rotate(vector<int>& nums, int k) {
  4. // 暴力交换法,每次交换一个,会超时
  5. int temp, previous;
  6. for (int i = 0; i < k; i++) {
  7. previous = nums[nums.size() - 1];
  8. for (int j = 0; j < nums.size(); j++) {
  9. temp = nums[j];
  10. nums[j] = previous;
  11. previous = temp;
  12. }
  13. }
  14. }
  15. };

方法二:另开辟数组然后复制

这种方法是最直观的,但是违反了题目的原则(要在原地修改),所以面试这么写肯定凉凉

  1. class Solution {
  2. public:
  3. void rotate(vector<int>& nums, int k) {
  4. // 防止k的大小比数组的大小大情况
  5. k = k % nums.size();
  6. // 最直观的方法:重新开一个数组记录然后复制到nums当中去
  7. // 违反了题目原地修改的原则,不推荐
  8. vector<int> temp;
  9. for (int i = nums.size()-k; i<nums.size(); i++)
  10. temp.push_back(nums[i]);
  11. for (int i = 0; i<nums.size()-k; i++)
  12. temp.push_back(nums[i]);
  13. for (int i = 0; i<nums.size(); i++)
  14. nums[i] = temp[i];
  15. return;
  16. }
  17. };

方法三:三次翻转数组求解

三次翻转:

  • 第一次翻转:翻转整个数组
  • 第二次翻转:翻转[0,k]
  • 第三次翻转:翻转[k+1,n]

通过这三次翻转,就可以得到答案,相关证明的方式可以参考线性代数矩阵翻转中求转置矩阵的方法是一样的,很简单的。这种方法也是方式华为机试题解中推荐的一种答案,比较经典。

  1. class Solution {
  2. public:
  3. void rotate(vector<int>& nums, int k) {
  4. int n = nums.size();
  5. k %= n;
  6. // 三次翻转解法
  7. reverse(nums.begin(), nums.end());
  8. reverse(nums.begin(), nums.begin()+k);
  9. reverse(nums.begin()+k, nums.end());
  10. return;
  11. }
  12. };

左右指针:LeetCode581.最短无序连续子数组

这道题先拷贝一份对数组进行排序,然后使用左右指针对其进行排序,所要寻找的最短无序连续子数组肯定在数组的中间(就算在靠右或者靠左的位置也可以认为是中间的位置),与原来没有被排序的数组进行对比,用左右指针锁定中间的和长度即可。

  1. class Solution {
  2. public:
  3. int findUnsortedSubarray(vector<int>& nums) {
  4. vector<int> temp(nums.begin(), nums.end());
  5. sort(temp.begin(), temp.end());
  6. // 双指针查找中间不对应升序的数组
  7. int i = 0;
  8. int j = nums.size() - 1;
  9. while (i <= j) {
  10. if (temp[i] == nums[i])
  11. i++;
  12. else
  13. break;
  14. }
  15. while (i <= j) {
  16. if (temp[j] == nums[j])
  17. j--;
  18. else
  19. break;
  20. }
  21. return j-i+1;
  22. }
  23. };

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/633062
推荐阅读
相关标签
  

闽ICP备14008679号