Let me describe this problem in detail first. Find a sub-array whose sum is a given value in an array of positive integers , and give the starting index (closed interval) of the sub-array, for example:
In the array [3 2 1 2 3 4 5], the sub-array with a sum of 10 is [1 2 3 4], so the answer should be [2,5].
The sub-array whose sum is 15 is [1 2 3 4 5], and the answer is [2,6].
This is a very interesting question, why do you say that? The simplest solution can be written as long as you have basic programming knowledge. The better solution requires you to have data structure and algorithm capabilities. The more efficient the solution, the more clever it is. You may not be able to come up with all the solutions at once, but I believe you After reading this blog, I will sigh the magic of the algorithm.
O(n^3)
back to this question, it actually has 4 time complexity solutions: 0609b52cd837fa, O(n^2)
, O(nlogn)
, O(n)
. If you count the difference in space complexity, there are a total of 5 solutions. I think it's better to observe a person. Algorithm level. Next, let me lead you from simple to ugly, 5 solutions from bronze to king, and take you to sling the interviewer.
Here we set the input parameters as (arr[],target), I will use s and e to represent the start and end positions respectively in the subsequent code. In addition, in order to simplify the code ideas, we assume that there is only one solution in the given parameters (in fact, multiple solutions are not difficult, but it will make the code longer, which is not conducive to describing the idea. The situation of multiple solutions will be left to you after class. Homework).
Bronze-Violent Solving
First of all, of course, the simplest violent solution is to traverse the starting position s and the ending position e, and then find the sum of all the numbers between s and e. The three-level loop is simple and rude, and does not require any skills. I believe you can solve it when you just learn programming in your freshman year.
public int[] find(int[] arr, int target) {
for (int s = 0; s < arr.length; s++) {
for (int e = s+1; e < arr.length; e++) {
int sum = 0;
for (int k = s; k <= e; k++) { // 求s到e之间的和
sum += arr[k];
}
if (target == sum) {
return new int[]{s, e};
}
}
}
return null;
}
Let's analyze the time complexity. Obviously, it is O(n^3). When n exceeds 1000, the slowness will be visible to the naked eye. Think about how to optimize it?
Silver-space for time
In the above code, we need to calculate the sum[s,e] of the array from s to e every time. Assuming that I have already calculated the sum[1,10] between [1,10], Now it is required to sum [2,10] between [2,10] and sum[2,10]. Obviously, a large part of this overlaps (sum[2,10]). Can this part of repeated scanning be eliminated? Here we need to make a clever transformation.
In fact, sum[s, e] = sum[0, e] - sum[0, s-1]
, sum[0,i] can be saved in advance and reused. In fact, the sum array can be obtained through data preprocessing. In the above figure, the sum of the blue area of arr is exactly equal to the red minus the green in the sum array, which is sum(arr[3]-arr[7]) = sum[7]-sum[2]
.
Back to the code, in the coding implementation, I used an extra array arrSum to store all the sums between 0 and i (0<=i<n). For the convenience of processing, the subscript of sumArr starts from 1, and sumArr[i] means far. Sum[0, i-1] in the array. With sumArr, sum[s,e] can be obtained indirectly through sumArr[e+1]-sumArr[s]. The complete code is as follows:
public int[] find(int[] arr, int target) {
int[] sumArr = new int[arr.length + 1];
for (int i = 1; i < sumArr.length; i++) {
sumArr[i] = sumArr[i-1] + arr[i-1]; // 预处理,获取累计数组
}
for (int s = 0; s < arr.length; s++) {
for (int e = s+1; e < arr.length; e++) {
if (target == sumArr[e+1] - sumArr[s]) {
return new int[]{s, e};
}
}
}
return null;
}
Through the above method of using space for time, we can directly reduce the time complexity from O(n^3)
to O(n^2)
.
Gold-binary search
Careful you may have discovered that because the given arr are all positive integers, sumArr must be increasing and ordered. For ordered arrays, we can directly use binary search. For this question, we can traverse the starting point s, and then dichotomy in sumArr to find whether there is an end point e. If s exists for e, then sumArr[e] must be equal to sumArr[s] + target. The transformed code is as follows Compared with the above code, binary search is added.
public int[] find(int[] arr, int target) {
int[] sumArr = new int[arr.length + 1];
for (int i = 1; i < sumArr.length; i++) {
sumArr[i] = sumArr[i-1] + arr[i-1];
}
for (int s = 0; s < arr.length; s++) {
int e = bSearch(sumArr, sumArr[s] + target);
if (e != -1) {
return new int[]{s, e};
}
}
return null;
}
// 二分查找
int bSearch(int[] arr, int target) {
int l = 1, r = arr.length-1;
while (l < r) {
int mid = (l + r) >> 1;
if (arr[mid] >= target) {
r = mid;
} else {
l = mid + 1;
}
}
if (arr[l] != target) {
return -1;
}
return l - 1;
}
As a result, we continue to reduce the time complexity from O(n^2) to O(nlogn).
Diamond-HashMap optimization
In addition to binary optimization, the search of ordered arrays can also be optimized with hashMap, with the help of the query time complexity of HashMap O(1). We once again traded space for time.
public int[] find(int[] arr, int target) {
int[] sumArr = new int[arr.length + 1];
Map<Integer, Integer> map = new HashMap<>();
for (int i = 1; i < sumArr.length; i++) {
sumArr[i] = sumArr[i-1] + arr[i-1];
map.put(sumArr[i], i-1);
}
for (int s = 0; s < arr.length; s++) {
int e = map.getOrDefault(sumArr[s]+target, -1);
if (e != -1) {
return new int[]{s, e};
}
}
return null;
}
We finally reduced the time complexity to O(n), which is a qualitative leap.
King-Ruler Method
Don't worry, it's not over yet, there is a king's solution to this problem. In the above, we have reduced the time complexity from O(n^3) step by step through continuous optimization, but we have increased the use of storage step by step, from the newly added sumArr number at the beginning to the finally increased HashMap , The space complexity has changed from O(1) to O(n). Is there a way to reduce the space complexity? If I can write this, it must be there.
This algorithm is called the ruler method. The ruler is a little hard to understand. Let's take a specific example directly. Suppose there are ropes with different lengths of n-tunes placed side by side. You need to find a continuous part of the rope to form a rope with a length of target. Here you need to pay attention to the continuity. At this time, you can find a ruler with a length of target, and then put the rope section by section on the ruler. If you find that it is short, connect another one to the back. If you find that it is long, throw away the top one. Until the length is just right.
In use, we don't need this ruler, we only need to use the target as the ruler. It may be more difficult to understand. As an example, the following figure demonstrates the process of finding a sub-array whose sum is 22 from the array.
As long as it is small, add to the right, and subtract it to the left, until you find the target.
Why is the ruler correct? I understand that the ruler method is actually an optimization of the second silver solution. It also traverses the starting point s, but does not invalidate the end point e. . If e reaches a certain position, it has already exceeded, because the array is all positive numbers , And then it is definitely super, and there is no need to continue traversing e. Instead, adjust s. If the sum of s is smaller after moving to a certain position to the right, the sum of s will only be smaller when it goes to the right, and there is no need to continue adjusting s... The whole process is like fixing s to traverse e. , And then fix e to traverse s ... until the result is obtained.
The basis for the ruler method is that the sum of e moving to the right must increase, and the sum of s moving to the right must decrease, which means that all the numbers in the array must be positive. There is no perfect algorithm to solve any problem, but there must be the most perfect solution for a specific problem.
After talking about the ruler method, let's see how the ruler method solves this problem. The code is relatively simple, as follows:
public int[] find(int[] arr, int target) {
int s = 0, e = 0;
int sum = arr[0];
while (e < arr.length) {
if (sum > target) {
sum -= arr[s++];
} else if (sum < target) {
sum += arr[++e];
} else {
return new int[]{s, e};
}
}
return null;
}
There is only one level of loop, and the time complexity is O(n). There is no extra space occupied, and the space complexity is O(1), which is the most perfect solution.
to sum up
At first glance, this algorithmic problem is simple, but it's really not easy at a closer look. You may encounter an interview, and there is no way to think of the optimal solution at once, but it is better to give a feasible solution than no solution. I asked someone about this question in an interview before. He came up thinking about how to solve it optimally, but he didn't even write the simplest bronze solution. Remember the next interview, if is really unsolvable, give a 60-point answer first, and then think of a way to increase the score, and don't hand in a blank paper at the end. gives a feasible solution, and then continues iterative optimization. I think this is also a better idea to solve a complex problem.
Finally, I would like to give you a chicken soup, not born to be a king, but just keep working hard to become a king.
Welcome attention to my interview column senior program ape face questions featured , continuously updated, permanent free , this column will continue the program included high-class classic simian face questions I encountered, in addition to providing detailed problem-solving ideas, the It will also provide extended questions from the interviewer's perspective. It is your best choice for advanced interviews. I hope to help you find a better job. In addition, interview questions are also collected. If you encounter a question that you do not know, let me know by private letter, and I will give you a blog for valuable questions.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。