1. 程式人生 > >得了,一文把字首和給扒的乾乾淨淨了。

得了,一文把字首和給扒的乾乾淨淨了。

今天我們來說一下刷題時經常用到的字首和思想,字首和思想和滑動視窗會經常用在求子陣列和子串問題上,當我們遇到此類問題時,則應該需要想到此類解題方式,該文章深入淺出描述字首和思想,讀完這個文章就會有屬於自己的解題框架,遇到此類問題時就能夠輕鬆應對。 下面我們先來了解一下什麼是字首和。 字首和其實我們很早之前就瞭解過的,我們求數列的和時,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) {