您当前的位置: 首页 >  面试

惊鸿一博

暂无认证

  • 1浏览

    0关注

    535博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

算法笔记_面试题_20.数组相关_模板及示例十几道

惊鸿一博 发布时间:2021-11-30 11:44:09 ,浏览量:1

目录

1. 排序数组的合并

例1. 合并两个排序数组并返回(模板)

例2. 合并两个排序数组到其中一个长的数组上

例3. 合并k个排序数组

2. 数组中的第k大

例4. 未排序数组中的第K个最大元素​ (模板)

例5. 未排序字符串数字数组中的第k大字符串

例6. 未排序数组中的中位数

例7. 两个排序数组的中位数

3. 子数组(subarray)相关

例8. 求最大子数组和(模板)

例9. 股票的最大利润(只买卖一次)

例10. 求最大子数组和_找2个不重叠子数组

例11. 股票的最大利润(可买卖多次)

例12. 求最小子数组和

例13. 最大子数组差

例14. 找和为 0 的子数组

例15. 找和最接近 0 的子数组

4. 双指针相关

例16. 找数组中和等于target的两个元素(2Sum)(模板)

例17. 找数组中和等于0的三个数(3Sum)

例18. 找数组中和最接近target的三个数(3Sum Closest)

例19. 找数组中和等于target的四个元素(4Sum)

例20. 数组划分 (Partition Array)

5. 颜色排序

例21. 三种颜色排序

例22. 多种颜色排序

6. 交错排序

例23. 交错正负数

1. 排序数组的合并 例1. 合并两个排序数组并返回(模板)

描述:将按升序排序的整数数组A和B合并,新数组也需有序。来源:(lintcode 6 · 合并排序数组简单)样例 1: 输入:A = [1]    B = [1]   输出:[1,1] 解释:返回合并后的数组。

样例 2: 输入:A = [1,2,3,4]   B = [2,4,5,6]  输出:[1,2,2,3,4,4,5,6]    

代码

有点类似 合并两个排序链表。只不过这里使用时vector相关的操作。

class Solution {
public:
    vector mergeSortedArray(vector &A, vector &B) {
        int i = 0;
        int j = 0;
        vector result; //第一次竟然错误的写成vector result(A.size()+B.size()) 这是定义一个vector放入指定数量的0先。
        while (i < A.size() && j < B.size()) {
            if (A[i] < B[j]) {
                result.push_back(A[i++]); //先插入,再指向下一个元素
            } else {
                result.push_back(B[j++]);
            }
        }
        
        while (i < A.size()) {
            result.push_back(A[i++]);
        }
        while (j < B.size()) {
            result.push_back(B[j++]);
        }

        return result;
    }
};

当然后面两个while循环,也可以使用 if判断 + insert插入。注意这里迭代器加法的使用。

        if (i < A.size()) {
            result.insert(result.end(), A.begin() + i, A.end());
        }
        if (j < B.size()) {
            result.insert(result.end(), B.begin() + j, B.end());
        }
例2. 合并两个排序数组到其中一个长的数组上

描述: 合并两个排序的整数数组A和B变成一个新的数组。你可以假设A具有足够的空间(A数组的大小大于或等于m+n)去添加B中的元素。数组A和B分别含有m和n个数。(来源:lintcode 64 · 合并排序数组(简单版)= leetcode 88. 合并两个有序数组)样例 输入:A = [1,2,3]  m = 3;    B = [4,5]    n = 2   输出:[1,2,3,4,5]

代码

因为要合并到一个长的数组上,所以可以从后往前合并。其他的同例1。在最后处理时,当长数组还有剩余时不用处理,因为本身已经有序且在目标位置上。短的数组则需要重新放入长的数组中。注意这里 定义的当前位置的起始位置: cur = m + n - 1;

class Solution {
public:
    void mergeSortedArray(int A[], int m, int B[], int n) {
        int i = m - 1;
        int j = n - 1;
        int cur = m + n - 1;
        while (i >=0 && j >= 0) {
            if (A[i] > B[j]) {
                A[cur--] = A[i--];
            } else {
                A[cur--] = B[j--];
            }
        }

        while (j >= 0) {
            A[cur--] = B[j--];
        }
    }
};
例3. 合并k个排序数组

描述: 将 k 个有序数组合并为一个大的有序数组。(来源:lintcode 486 · 合并k个排序数组)样例 1: 输入:  [ [1, 3, 5, 7], [2, 4, 6], [0, 8, 9, 10, 11] ] 输出:  [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]样例 2: 输入: [  [1,2,3],  [1,2]  ] 输出: [1,1,2,2,3]挑战: 在 O(N log k) 的时间复杂度内完成:N 是所有数组包含的整数总数量。k 是数组的个数。

代码

这和 合并k个链表 是类似的。要求O(N log K),应该自然想到二分法,即此处的归并排序。

class Solution {
public:
    vector mergekSortedArrays(vector &arrays) {
        vector result;
        if (arrays.size() == 0) {
            return result;
        }

        int k = arrays.size();
        return mergeSortedArraysCore(arrays, 0, k - 1);
    }

    vector mergeSortedArraysCore(vector & arrays, int start, int end) {
        if (start == end) {
            return arrays[start];
        }
        int mid = start + (end - start) / 2;
        vector left  = mergeSortedArraysCore(arrays, start, mid);
        vector right = mergeSortedArraysCore(arrays, mid+1, end);

        return mergeTwoArrays(left, right);
    }

    vector mergeTwoArrays(vector A, vector B) {
         //同例1(合并两个数组) 一模一样
};
2. 数组中的第k大 例4. 未排序数组中的第K个最大元素​ (模板)

描述:给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。(来源:leetcode 215. 数组中的第K个最大元素 =  lintcode 5 · 第k大元素 ≈  lintcode 606 · 第K大的元素 II)示例 1: 输入: [3,2,1,5,6,4] 和 k = 2 输出: 5示例 2: 输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 输出: 4

代码

可以使用优先队列(priority_queue, 内部实现堆(堆内部实现平衡二叉树)),定义一个只含k个数的优先队列,以小顶堆(priority_queue que;)的形式存储,当该优先队列存满后,下一个值若比当前堆顶元素(nums[i] > que.top())大,则去掉堆顶元素,插入当前元素,此时小顶堆仍然自动保持有序。则第k大元素即为当前小顶堆的top元素。

class Solution {
public:
    int findKthLargest(vector& nums, int k) {
        int n = nums.size();
        if (n < k) {
            return -1;
        }
        priority_queue que; //特别注意:小顶堆,谁大谁放二叉树底层。
        for (int i = 0; i < n; i++) {
            if (que.size() < k) {
                que.push(nums[i]);
            } else {
                if (nums[i] > que.top()) {  //特别注意:大的替换小的,最终剩下k个最大的,小顶堆中的top元素,就是第k个最大的元素
                    que.pop();
                    que.push(nums[i]);
                }
            }
        }
        return que.top();
    }
};

拓展:当然该题,还可以使用快排中的patition方法来做。详见本文例6-代码2 。

例5. 未排序字符串数字数组中的第k大字符串

描述:给你一个字符串数组 nums 和一个整数 k 。nums 中的每个字符串都表示一个不含前导零的整数。返回 nums 中表示第 k 大整数的字符串。注意:重复的数字在统计时会视为不同元素考虑。例如,如果 nums 是 ["1","2","2"],那么 "2" 是最大的整数,"2" 是第二大的整数,"1" 是第三大的整数。(来源:leetcode 1985. 找出数组中的第 K 大整数)示例 1: 输入:nums = ["3","6","7","10"], k = 4   输出:"3" 解释:nums 中的数字按非递减顺序排列为 ["3","6","7","10"] 其中第 4 大整数是 "3"示例 2: 输入:nums = ["2","21","12","1"], k = 3   输出:"2" 解释:nums 中的数字按非递减顺序排列为 ["1","2","12","21"] 其中第 3 大整数是 "2"

提示:

1 middle,说明中位数在左侧,就只在左侧搜索;若 i < middle,说明中位数在右侧,只在右侧搜索。这样一次可以排除“一半”,从而减小了时间复杂度。注意代码中是如何缩小搜索范围的(使用 while(true) + 改变搜索边界 + 加1或减1)。(下面的middle 表示索引值,所以等于 int middle = (n - 1) / 2; )

class Solution {
public:
    int median(vector &nums) {
        int n = nums.size();
        if (n == 0) {
            return -1;
        }

        vector A(nums.begin(), nums.end()); //为了不改变输入数组
        int middle = (n - 1) / 2; //表示索引值。当题目要求找第k大元素时,此处变成 size - k 即可,其他完全一样。
        int left = 0;
        int right = n - 1;

        while (true) {     //特别注意: 这里使用的是 一直循环!!
            int i = partition(A, left, right);
            if (i == middle) {
                return A[i];
            } else if (i > middle) {
                right = i - 1; //特别注意:这里要-1,为了防止各个数相等的情况,比如 0 0 0 0 0 2 1的情况
            } else {
                left = i + 1;  //特别注意:这里要+1,为了防止各个数相等的情况,比如 0 0 0 0 0 2 1的情况
            }
        }

    }

    // 快排的 partition 部分, 当成一个积木块理解记忆
    int partition(vector &nums, int start, int end) {
        int i = start;
        int j = end;
        int key = nums[i];
        while (i < j) {
            while (i < j && nums[j] >= key) {
                --j;
            }
            if (i < j) {
                nums[i] = nums[j];
            }
            while (i < j && nums[i] = A.size()) { //当A遍历完了仍然没有找到,则必然存在B中,且是此层的B的第 bStart + k个元素。
            return B[bStart + k - 1];
        }
        if (bStart >= B.size()) {
            return A[aStart + k - 1];
        }
        if (k == 1) {   // 当锁定到两个元素时,第1小的元素,则必然是两者中的min值。
            return min(A[aStart], B[bStart]);
        }

        int a = (aStart + k/2 - 1 < A.size()) ?   //当前比较对象:第 start + k/2 个元素,作为索引需要-1 
                A[aStart + k/2 - 1] : INT_MAX;
        int b = (bStart + k/2 - 1 < B.size()) ?
                B[bStart + k/2 - 1] : INT_MAX;
        if (a < b) {
            return findKthSmaller(A, B, aStart + k/2, bStart, k - k/2); //A前start+k/2个元素小,所以舍弃,从 start+k/2开始向后搜索,同时搜索长度减少k/2, 下面的类似。
        } else {
            return findKthSmaller(A, B, aStart, bStart + k/2, k - k/2);
        }
    }
};
3. 子数组(subarray)相关 例8. 求最大子数组和(模板)

描述:给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。(来源: lintcode 41 · 最大子数组 = leetcode 53. 最大子数组和 = leetcode 剑指 Offer 42. 连续子数组的最大和)示例 1: 输入:nums = [-2,1,-3,4,-1,2,1,-5,4]   输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

重要知识背景:

  • 前缀和:索引为i的数的前面的所有数的求和(包含num[i]),记作 prefixSum[i]
  • 则求索引 i ~ j 的子数组的和公式: prefixSum [j] – prefixSum [i-1]
  • 这个公式是求子数组和中最最常用和普遍的公式。
  • 则求到索引为i的数的最大子数组和 f[i] = prefixSum[i] – min(prefixSum(0~i-1));即 当前位置的最大前缀和 = 当前位置的前缀和 - 之前最小的前缀和;整个数组的最大子数组和为:取所有位置最大前缀和的最大者。

根据上述描述,图示如下:

代码1

采用动态规划的方法。状态定义:dp[i] 表示以索引位置 i 结束的最大子数组的和 (则dp[i]包含元素nums[i];dp[n-1]不是最终结果);则状态转移方程为: dp[i] = max(nums[i], dp[i-1] + nums[i]); 然后去dp[i] 中最大的即为整个数组的 最大子数组和。(时间复杂度O(N) , 空间复杂度O(N))

class Solution {
public:
    int maxSubArray(vector& nums) {
        int n = nums.size();
        if (n == 0) {
            return 0;
        }

        vector dp(n); //dp[i]表示以索引位置i结束的最大子数组和的值
        dp[0]      = nums[0];
        int maxSum = nums[0];
        
        for (int i = 1; i < n; i++) { //注意这里是从1开始的
            dp[i]  = max(nums[i], dp[i-1] + nums[i]);
            maxSum = max(maxSum, dp[i]);
        }

        return maxSum;
    }
};

代码2

使用几个常量来记录 计算到当前位置i时的前缀和 curSum、计算到当前位置i时的最大子数组和(不一定包含元素nums[i]) maxSum(maxSum是最终结果)、当前数组中的最小子数组和 minSum,则 到计算到当前 位置时的 maxSum = max (maxSum, curSum - minSum);(时间复杂度O(N) , 空间复杂度O(1))

class Solution {
public:
    int maxSubArray(vector& nums) {
        int n = nums.size();
        if (n == 0) {
            return INT_MIN;
        }
        
        int maxSum = INT_MIN;
        int minSum = 0;
        int curSum = 0;
        for (int i = 0; i < n; i++) {
            curSum += nums[i];      //计算当前位置的前缀和
            maxSum = max(maxSum, curSum - minSum); // 当前位置的最大前缀和=当前位置的前缀和 - 之前最小的前缀和,然后取所有位置最大前缀和的最大者。
            minSum = min(minSum, curSum); //确保 minSum 是历史上的最小前缀和
        }

        return maxSum;
    }
};
例9. 股票的最大利润(只买卖一次)

描述:假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?(来源:lintcode 149 · 买卖股票的最佳时机 = leetcode 剑指 Offer 63. 股票的最大利润)示例 :   输入: [7,1,5,3,6,4]   输出: 5解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

代码1:

本题基本等价于例8-求最大子数组和。只需要做一个转换,将原始数组变成后一个数减前一个数组成一个新的数组,在这个新的数组上求最大子数组和,即为最大利润。比如数组A= [7,1,5,3,6,4] 变成 数组B=[-6,4,-2,3,-1],然后求B的最大子数组和。注意:当小于0时取0。

class Solution {
public:
    int maxProfit(vector& prices) {
        // write your code here
        if (prices.size() == 0 || prices.size() == 1) {
            return 0;
        }

        vector nums;
        for (int i = 1; i < prices.size(); i++) {
            nums.push_back(prices[i]-prices[i-1]);
        }
        
        int max = maxSubArray(nums);
        return max > 0 ? max : 0;
    }

    int maxSubArray(vector & nums) {
        //完全同例8-求最大子数组和
    }
};

代码2:

同例8,只不过这里要处理的是当前状态时的最大利润,需要保存之前的最小股票值minValue.

依然类似于动态规划,dp[i]表示当前 i 时刻卖出的最大利润,则 dp[i] = max(dp[i-1], prices[i] - minValue)。这里不用保存每个状态,所以直接使用一个变量即 maxProfit 来表示状态。

class Solution {
public:
    int maxProfit(vector& prices) {
        int n = prices.size();
        if (n == 0) {
            return 0;
        }
        
        int maxProfit = 0;
        int minValue  = prices[0];
        for (int i = 1; i < n; i++) {
            maxProfit = max(maxProfit, prices[i] - minValue); // 记录当前利润,并更新最大利润
            minValue  = min(minValue,  prices[i]);            // 记录当前最小值
        }

        return maxProfit;  // 不用再跟0比较,因为maxProfit初始值是0,更新时使用max函数,意味着肯定大于0.
    }
};
例10. 求最大子数组和_找2个不重叠子数组

描述:给定一个整数数组,找出两个 不重叠 子数组使得它们的和最大。每个子数组的数字在数组中的位置应该是连续的。返回最大的和。子数组最少包含一个数。(来源:42 · 最大子数组 II)样例:输入:nums = [1, 3, -1, 2, -1, 2]   输出:7解释:最大的子数组为 [1, 3] 和 [2, -1, 2] 或者 [1, 3, -1, 2] 和 [2].

代码:

例8的拓展,基本模块同上一例题。只不过要找2个子数组。为了不重叠,一个从前往后找,并保存到达i位置时的最大子数组和(记作 maxLeft[i]),一个从后往前找(记作 maxRight[i]);这样,maxLeft[i] 和 maxRight[i+1]就不重合了! 然后,从所有的 maxLeft[i] + maxRight[i+1] 中取最大的即为最终要求结果。

  • 本题 maxLeft[i] 含义:遍历到i位置的,历史最大子数组和;使用 curSum - minSum。
    •  maxSum = max(maxSum, curSum - minSum);  maxLeft[i] = maxSum;
  • 例13 maxLeft[i] 含义:遍历到i位置的,包含i位置的最大子数组和;看上一个值大于0就加上。
    • (不同于例13: maxLeft[i] = max(nums[i], maxLeft[i-1] + nums[i]);)
class Solution {
public:
    int maxTwoSubArrays(vector &nums) {
        int n = nums.size();
        if (n == 0) {
            return 0;
        }

        vector maxLeft(n);
        int maxSum = INT_MIN;
        int minSum = 0;
        int curSum = 0;
        for (int i = 0; i < n; i++) {   // 从左往右计算
            curSum += nums[i];
            maxSum = max(maxSum, curSum - minSum); 
            minSum = min(minSum, curSum);
            maxLeft[i] = maxSum;  //注意:for循环最后保存当前的最大子数组和
        }

        vector maxRight(n);
        maxSum = INT_MIN;
        minSum = 0;
        curSum = 0;
        for (int i = n - 1; i >= 0; i--) { // 从右往左计算
            curSum += nums[i];
            maxSum = max(maxSum, curSum - minSum);
            minSum = min(minSum, curSum);
            maxRight[i] = maxSum;
        }

        int result = INT_MIN; 
        for (int i = 0; i < n - 1; i++) { // 在 左侧 + 右侧 中取最大值
            result = max(result, maxLeft[i] + maxRight[i+1]);
        }

        return result;
    }
};
例11. 股票的最大利润(可买卖多次)

描述:给定一个数组 prices 表示一支股票每天的价格.交易次数不限, 不过你不能同时参与多个交易 (也就是说, 如果你已经持有这支股票, 在再次购买之前, 你必须先卖掉它).设计一个算法求出最大的利润. (来源:150 · 买卖股票的最佳时机 II = leetcode 122. 买卖股票的最佳时机 II)

样例:输入: [2, 1, 2, 0, 1]   输出: 2解释: 1. 在第 2 天以 1 的价格买入, 然后在第 3 天以 2 的价格卖出, 利润 1          2. 在第 4 天以 0 的价格买入, 然后在第 5 天以 1 的价格卖出, 利润 1          总利润 2.

代码

类似例9,同样可以用DP的方式解决。状态定义:dp[i] 表示索引为i那天交易可能获得的最大利润。状态转移方程:索引为i天时交易产生的最大利润等于 i-1 天产生的最大利润 加上 i-1天买入,i天卖出产生的利润。即  dp[i] = max (dp[i-1] , dp[i-1] + prices[i]-prices[i-1])。 这里可以使用一个变量 maxProfits 滚动相加, 即 maxProfits = max(maxProfits, maxProfits + prices[i]-prices[i-1]);

需要注意的是:maxProfits 初值不能是 INT_MIN, 因为在状态转移方程中它同时参与了max函数中两个比较对象的计算。

class Solution {
public:
    int maxProfit(vector& prices) {
        int n = prices.size();
        if (n == 0) {
            return 0;
        }

        int maxProfits = 0;   //注意不能是INT_MIN!   // dp[i] 表示索引为i那天交易可能获得的最大利润
        for (int i = 1; i < n; i++) {   // dp[i] = max (dp[i-1] , dp[i-1] + prices[i]-prices[i-1])
            maxProfits = max(maxProfits, maxProfits + prices[i]-prices[i-1]);
        }

        return maxProfits;
    }
};
例12. 求最小子数组和

描述:给定一个整数数组,找到一个具有最小和的连续子数组。返回其最小和。子数组最少包含一个数字。(来源:lintcode 44 · 最小子数组)样例 输入:数组 = [1,-1,-2,1]  输出:-3 解释:子数组[-1,-2]的和是最小值-3。

代码

这里给出2种方法,都基本等同于求最大连续子数组。方法1:数组取反,求最大连续子数组和,返回相反数。方法2:DP的方法,累计数组和,每次计算minSum = min(minSum, curSum - maxSum) 每次更新maxSum。

class Solution {
public:
    int minSubArray(vector &nums) {
        int n = nums.size();
        if (n == 0) {
            return 0;
        }

        int minSum = INT_MAX;
        int curSum = 0;
        int maxSum = 0;
        for (int i = 0; i < n; i++) {
            curSum += nums[i];
            minSum = min(minSum, curSum - maxSum);
            maxSum = max(maxSum, curSum);
        }
        
        return minSum;
    }
};
例13. 最大子数组差

描述:给定一个整数数组,找出两个不重叠的子数组A和B,使两个子数组和的差的绝对值 |SUM(A) - SUM(B)| 最大。返回这个最大的差值。子数组最少包含一个数。(来源:lintcode 45 · 最大子数组差)

样例 1: 输入:数组 = [1, 2, -3, 1]  输出:6 解释:子数组是 [1,2] 和[-3].所以答案是 6.

样例 2: 输入:数组 = [0,-1]  输出:1 解释:子数组是 [0] 和 [-1].所以答案是 1. 挑战:时间复杂度为O(n),空间复杂度为O(n)

代码

等同于 求最大子数组 + 最小子数组 + 不重叠子数组的要求。思路:维护四个数组,当前位置 i 左边的最大子数组和,最小子数组和。当前位置右边的最大子数组和,最小子数组和。然后枚举分割线,扫描一下取最大即可。状态定义比如:leftMax[i] 表示以索引位置i结束的最大子数组和;转移方程:leftMax[i] = max(nums[i], leftMax[i-1] + nums[i]); 其他3个数组类似。

class Solution {
public:
    int maxDiffSubArrays(vector &nums) {
        int n = nums.size();
        if (n == 0 || n == 1) {
            return 0;
        }

        vector leftMax(n);  // leftMax[i]表示以索引位置i结束的最大子数组和的值
        vector leftMin(n);
        vector rightMax(n);
        vector rightMin(n);
        
        leftMax[0] = nums[0];
        leftMin[0] = nums[0];
        for (int i = 1; i < n; i++) {
            leftMax[i] = max(nums[i], leftMax[i-1] + nums[i]);
            leftMin[i] = min(nums[i], leftMin[i-1] + nums[i]);
        }

        rightMax[n-1] = nums[n-1]; // 特别注意:这里初始化的是最后一个元素!
        rightMin[n-1] = nums[n-1];
        for (int i = n - 2; i >= 0; i--) { //注意:是i--,别误写成++
            rightMax[i] = max(nums[i], rightMax[i+1] + nums[i]);
            rightMin[i] = min(nums[i], rightMin[i+1] + nums[i]);
        }

        int maxDiff = 0;
        for (int i = 0; i < n - 1; i++) {
            maxDiff = max(maxDiff, leftMax[i] - rightMin[i+1]);
            maxDiff = max(maxDiff, rightMax[i+1] - leftMin[i]);
        }

        return maxDiff;
    }
};
例14. 找和为 0 的子数组

描述:给定一个整数数组,找到和为 0的子数组。你的代码应该返回满足要求的子数组的起始位置和结束位置;至少有一个子数组的和为 0. (来源:lintcode 138.子数组之和)

样例 1: 输入: [-3, 1, 2, -3, 4]   输出: [0,2] 或 [1,3]     样例解释: 返回任意一段和为0的区间即可。样例 2: 输入: [-3, 1, -4, 2, -3, 4]  输出: [1,5]

代码

考虑到两个子数组的和为0,意味着不同的前缀和(累加到索引位置i的和,记为sum[i])中有相等的,即 sum[i到j] = 0, 即 sum[i-1] = sum[j]。 在候选项中找相等,所以应自然相等使用hash数据结构。判断的是sum是否相等,所以将sum作为哈希的key,因为最终结果要的是索引值,所以索引值作为哈希的value。当判断有相等前缀和出现时,则返回上一个前缀和的索引 i 的后一个位置 (i+1)(也就是子数组和的第一个位置),以及当前索引值。

class Solution {
public:
    vector subarraySum(vector &nums) {
        vector result;
        int n = nums.size();
        if (n == 0) {
            return result;
        }

        unordered_map hashMap;
        int curSum = 0;  //记录累加到当前索引i时的前缀和
        hashMap.insert({0, -1}); //key表示 sum, value 表示索引位置(到索引为i(value)的位置的前缀和为sum(key))
        for (int i = 0; i < n; i++) {
            curSum += nums[i];
            if (hashMap.find(curSum) != hashMap.end()) {
                result.push_back(hashMap.at(curSum) + 1);  //找到历史上的前缀和比如 前a个数,等于当前(索引为i)的前缀和,则子数组和为0的区间自然是 [a+1, i] // 或 hashMap[curSum] + 1
                result.push_back(i);
                return result;
            }
            hashMap.insert({curSum, i}); //注意 插入hashMap的方式!// 或 hashMap[curSum] = i; 
        }

        return result;
    }
};
例15. 找和最接近 0 的子数组

描述:给定一个整数数组,找到一个和最接近于零的子数组。返回满足要求的子数组的起始位置和结束位置。(来源:139 · 最接近零的子数组和)样例: 输入: [-3,1,1,-3,5]   输出: [0,2] 解释: [0,2], [1,3], [1,1], [2,2], [0,4] (注意:返回的是索引的 start 和 end 位置) 挑战:O(nlogn)的时间复杂度

代码

求最接近于0的子数组,意味着找两个前缀和,这两个前缀和的差(diff)最小。题目要求时 O(nlogn) ,应该自然想到要排序! 所以这里,我们先把各个索引位置对应的前缀和求出来,组成一个个节点元素(Node(value, pos)),然后对这些前缀和进行排序,则 前缀和相互接近 的自然排到了一起,然后就只需要看相邻两个元素,找出差最小的两个元素,则它们对应的索引即为所求。

特别注意:因为要排序,可以使用 pair(key, value)这样的结构,也可以为了使代码可读性更好,使用自定义的数据结构(文中自定义Node数据结构),然后需要重载 operator

关注
打赏
1663399408
查看更多评论
立即登录/注册

微信扫码登录

0.0430s