得了,一文把字首和給扒的乾乾淨淨了。
阿新 • • 發佈:2021-01-16
今天我們來說一下刷題時經常用到的字首和思想,字首和思想和滑動視窗會經常用在求子陣列和子串問題上,當我們遇到此類問題時,則應該需要想到此類解題方式,該文章深入淺出描述字首和思想,讀完這個文章就會有屬於自己的解題框架,遇到此類問題時就能夠輕鬆應對。
下面我們先來了解一下什麼是字首和。
字首和其實我們很早之前就瞭解過的,我們求數列的和時,Sn = a1+a2+a3+...an; 此時Sn就是數列的前 n 項和。例 S5 = a1 + a2 + a3 + a4 + a5; S2 = a1 + a2。所以我們完全可以通過 S5-S2 得到 a3+a4+a5 的值,這個過程就和我們做題用到的字首和思想類似。我們的字首和數組裡儲存的就是前 n 項的和。見下圖
![](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/微信截圖_20210113193831.4wk2b9zc8vm0.png)
我們通過字首和陣列儲存前 n 位的和,presum[1]儲存的就是 nums 陣列中前 1 位的和,也就是 **presum[1]** = nums[0], **presum[2]** = nums[0] + nums[1] = **presum[1]** + nums[1]. 依次類推,所以我們通過字首和陣列可以輕鬆得到每個區間的和。
例如我們需要獲取 nums[2] 到 nums[4] 這個區間的和,我們則完全根據 presum 陣列得到,是不是有點和我們之前說的字串匹配演算法中 BM,KMP 中的 next 陣列和 suffix 陣列作用類似。那麼我們怎麼根據 presum 陣列獲取 nums[2] 到 nums[4] 區間的和呢?見下圖
![字首和](https://cdn.jsdelivr.net/gh/tan45du/github.io.phonto2@master/myphoto/字首和.77twdj3gpkg0.png)
好啦,我們已經瞭解了字首和的解題思想了,我們可以通過下面這段程式碼得到我們的字首和陣列,非常簡單
```
for (int i = 0; i < nums.length; i++) {
presum[i+1] = nums[i] + presum[i];
}
```
好啦,我們開始實戰吧。
### leetcode 724. 尋找陣列的中心索引
**題目描述**
> 給定一個整數型別的陣列 nums,請編寫一個能夠返回陣列 “中心索引” 的方法。
>
> 我們是這樣定義陣列 中心索引 的:陣列中心索引的左側所有元素相加的和等於右側所有元素相加的和。
>
> 如果陣列不存在中心索引,那麼我們應該返回 -1。如果陣列有多箇中心索引,那麼我們應該返回最靠近左邊的那一個。
**示例 1:**
> 輸入:
> nums = [1, 7, 3, 6, 5, 6]
> 輸出:3
解釋:
索引 3 (nums[3] = 6) 的左側數之和 (1 + 7 + 3 = 11),與右側數之和 (5 + 6 = 11) 相等。
同時, 3 也是第一個符合要求的中心索引。
**示例 2:**
> 輸入:
> nums = [1, 2, 3]
> 輸出:-1
解釋:
陣列中不存在滿足此條件的中心索引。
理解了我們字首和的概念(不知道好像也可以做,這個題太簡單了哈哈)。我們可以一下就能把這個題目做出來,先遍歷一遍求出陣列的和,然後第二次遍歷時,直接進行對比左半部分和右半部分是否相同,如果相同則返回 true,不同則繼續遍歷。
```java
class Solution {
public int pivotIndex(int[] nums) {
int presum = 0;
//陣列的和
for (int x : nums) {
presum += x;
}
int leftsum = 0;
for (int i = 0; i < nums.length; ++i) {
//發現相同情況
if (leftsum == presum - nums[i] - leftsum) {
return i;
}
leftsum += nums[i];
}
return -1;
}
}
```
### leetcode 560. 和為K的子陣列
**題目描述**
> 給定一個整數陣列和一個整數 k,你需要找到該陣列中和為 k 的連續的子陣列的個數。
**示例 1 :**
> 輸入:nums = [1,1,1], k = 2
> 輸出: 2 , [1,1] 與 [1,1] 為兩種不同的情況。
**暴力法**
**解析**
我們先來用暴力法解決這個題目,很簡單,一下就能 AC。
這個題目的題意很容易理解,就是讓我們返回和為 k 的子陣列的個數,所以我們直接利用雙重迴圈解決該題,這個是很容易想到的。我們直接看程式碼吧。
```java
class Solution {
public int subarraySum(int[] nums, int k) {
int len = nums.length;
int sum = 0;
int count = 0;
//雙重迴圈
for (int i = 0; i < len; ++i) {
for (int j = i; j < len; ++j) {
sum += nums[j];
//發現符合條件的區間
if (sum == k) {
count++;
}
}
//記得歸零,重新遍歷
sum = 0;
}
return count;
}
}
```
好啦,既然我們已經知道如何求字首和陣列了,那我們來看一下如何用字首和思想來解決這個問題。
```java
class Solution {
public int subarraySum(int[] nums, int k) {
//字首和陣列
int[] presum = new int[nums.length+1];
for (int i = 0; i < nums.length; i++) {
//這裡需要注意,我們的字首和是presum[1]開始填充的
presum[i+1] = nums[i] + presum[i];
}
//統計個數
int count = 0;
for (int i = 0; i < nums.length; ++i) {
for (int j = i; j < nums.length; ++j) {
//注意偏移,因為我們的nums[2]到nums[4]等於presum[5]-presum[2]
//所以這樣就可以得到nums[i,j]區間內的和
if (presum[j+1] - presum[i] == k) {
count++;
}
}
}
return count;
}
}
```
我們分析上面的程式碼,發現該程式碼雖然用到了字首和陣列,但是對比暴力法並沒有提升效能,時間複雜度仍為O(n^2),空間複雜度成了 O(n)。那我們有沒有其他方法解決呢?
**字首和 + HashMap**
瞭解這個方法前,我們先來看一下下面這段程式碼,保證你很熟悉
```java
class Solution {
public int[] twoSum(int[] nums, int target) {