給定一個實數序列,設計一個最有效的算法,找到一個總和數最大的區間等于某個事先給定的數字。
我們可以使用前綴和和哈希表來設計一個高效的算法。這個算法的時間復雜度是 O(n),空間復雜度也是 O(n)。
#include <vector>
#include <unordered_map>
#include <iostream>std::pair<int, int> findMaxLengthSubarrayWithSum(const std::vector<double>& arr, double targetSum) {std::unordered_map<double, int> sumIndex; // 用于存儲前綴和及其索引double currentSum = 0; // 當前的前綴和int maxLength = 0; // 最長子數組的長度int endIndex = -1; // 最長子數組的結束索引sumIndex[0] = -1; // 初始化前綴和為0的索引為-1for (int i = 0; i < arr.size(); ++i) {currentSum += arr[i]; // 計算當前前綴和double complement = currentSum - targetSum; // 計算需要尋找的互補和if (sumIndex.find(complement) != sumIndex.end()) { // 如果找到了互補和int length = i - sumIndex[complement]; // 計算子數組長度if (length > maxLength) { // 如果這個子數組更長maxLength = length; // 更新最大長度endIndex = i; // 更新結束索引}}if (sumIndex.find(currentSum) == sumIndex.end()) { // 如果這個前綴和之前沒出現過sumIndex[currentSum] = i; // 記錄這個前綴和的索引}}if (endIndex != -1) { // 如果找到了符合條件的子數組return {endIndex - maxLength + 1, endIndex}; // 返回起始和結束索引} else {return {-1, -1}; // 如果沒找到,返回{-1, -1}}
}int main() {std::vector<double> arr = {1.0, 4.0, 20.0, 3.0, 10.0, 5.0}; // 示例數組double targetSum = 33.0; // 目標和auto result = findMaxLengthSubarrayWithSum(arr, targetSum); // 調用函數查找子數組if (result.first != -1) { // 如果找到了符合條件的子數組std::cout << "找到了和為 " << targetSum << " 的最長子數組,索引范圍: [" << result.first << ", " << result.second << "]" << std::endl;} else { // 如果沒有找到符合條件的子數組std::cout << "沒有找到和為 " << targetSum << " 的子數組" << std::endl;}return 0;
}
這個算法的工作原理如下:
- 我們使用一個哈希表
sumIndex
來存儲每個前綴和及其對應的索引。 - 我們遍歷數組,不斷累加當前的和
currentSum
。 - 對于每個位置,我們計算
complement = currentSum - targetSum
。如果這個complement
存在于我們的哈希表中,那么說明我們找到了一個和為targetSum
的子數組。 - 我們記錄找到的最長的子數組。
- 如果當前的
currentSum
還沒有在哈希表中,我們就將它加入哈希表。
這個算法的優點是:
- 時間復雜度為 O(n),因為我們只需要遍歷一次數組。
- 空間復雜度為 O(n),用于存儲哈希表。
- 它可以處理包含正數、負數和零的數組。
- 它可以找到最長的符合條件的子數組。
這個算法比簡單的滑動窗口方法更有效,因為它可以處理包含負數的情況,并且可以在一次遍歷中找到最長的符合條件的子數組。
第二個問題,在一個二維矩陣中,尋找一個矩陣的區域,使其中的數字之和達到最大值,著名的"最大子矩陣和"問題。我們可以使用Kadane算法的二維擴展來解決這個問題。以下是C++實現,并附有中文注釋:
#include <vector>
#include <climits>
#include <iostream>struct SubMatrix {int top, left, bottom, right, sum;
};SubMatrix findMaxSumSubMatrix(const std::vector<std::vector<int>>& matrix) {int rows = matrix.size();if (rows == 0) return {0, 0, 0, 0, 0};int cols = matrix[0].size();if (cols == 0) return {0, 0, 0, 0, 0};SubMatrix result = {0, 0, 0, 0, INT_MIN}; // 存儲最終結果std::vector<int> temp(rows, 0); // 臨時數組,用于存儲列的和// 枚舉所有可能的左右列邊界for (int left = 0; left < cols; ++left) {std::fill(temp.begin(), temp.end(), 0); // 重置臨時數組for (int right = left; right < cols; ++right) {// 將當前列加到臨時數組中for (int i = 0; i < rows; ++i) {temp[i] += matrix[i][right];}// 在臨時數組上應用一維Kadane算法int currentSum = 0;int maxSum = INT_MIN;int tempTop = 0, top = 0, bottom = 0;for (int i = 0; i < rows; ++i) {currentSum += temp[i];if (currentSum > maxSum) {maxSum = currentSum;top = tempTop;bottom = i;}if (currentSum < 0) {currentSum = 0;tempTop = i + 1;}}// 更新全局最大值if (maxSum > result.sum) {result = {top, left, bottom, right, maxSum};}}}return result;
}int main() {std::vector<std::vector<int>> matrix = {{1, 2, -1, -4, -20},{-8, -3, 4, 2, 1},{3, 8, 10, 1, 3},{-4, -1, 1, 7, -6}};SubMatrix result = findMaxSumSubMatrix(matrix);std::cout << "最大子矩陣和為: " << result.sum << std::endl;std::cout << "子矩陣位置: (" << result.top << "," << result.left << ") 到 ("<< result.bottom << "," << result.right << ")" << std::endl;return 0;
}
這個算法的工作原理如下:
- 我們枚舉矩陣的所有可能的左右列邊界。
- 對于每一對左右邊界,我們計算這些列之間所有元素的和,存儲在一個臨時數組中。
- 在這個臨時數組上,我們應用一維的Kadane算法來找到最大子數組和,這個子數組對應于我們要找的子矩陣的上下邊界。
- 我們跟蹤全局最大和及其對應的子矩陣位置。
- 最后返回具有最大和的子矩陣。
這個算法的時間復雜度是O(n^3),其中n是矩陣的邊長(假設矩陣是方陣)。雖然這不是最優的解法,但它是較為直觀且易于實現的方法。
對于更大的矩陣,存在更高效的算法,如使用二維線段樹或者二維樹狀數組的方法,它們可以將時間復雜度降低到O(n^2 log n)或O(n^2)。但這些方法的實現會更加復雜。