數組是算法中最經常使用的一種數據結構,也是面試中最常考的考點。在LeetCode題庫中,標記爲數組類型的習題到目前爲止,已累計到了202題。然而,這202道習題並非每道題只標記爲數組一個考點,大部分習題都有兩到三個考點。好比,考查數組+哈希表、數組+動態規劃+數學、數組+回溯等。面試
看到如此多考點標籤,若是盲目地按照一個標籤內部全部習題的順序去刷題,會讓人有點錯亂感。對於時間比較緊湊的同窗來講,題目的數量比較多,想在較短期內刷完是一個很大的挑戰。所以,本文針對時間較緊湊的同窗精選一些數組類型的表明性題目,進行分類總結,但願可以起到一點幫助(PS:實際上是做者但願藉此進一步加深本身對考點的認知,創建一個有效的知識體系… …)。算法
標記爲數組類型的題目有200多道題,本文的重點關注那些主要考察數組的題目。對於考察點主要放在其它考點(好比:二分查找、雙指針、哈希表等)上的題目,做者計劃把這些題目放在後序總結篇章中。按照做者刷題的狀況,在屬於數組考點系列的題目中,劃分爲四個常考問題:子數組問題、矩陣問題、O(n)類型問題和思惟轉換類型問題。數組
本文是《LeetCode刷題總結-數組篇(上)》,總結概括有關子數組問題的題目。本期題目數量共17題,其中難度爲簡單有1題,難度爲中等的有12題,難度爲困難的有4題。具體題目信息及解答見下文。數據結構
題號:53,難度:簡單函數
題目描述:優化
解題思路:spa
本題最爲經典和普遍的解法是應用動態規劃的思想來解答,其時間複雜度爲O(n)。題目中鼓勵嘗試使用更爲精妙的分治法求解,經過翻閱相關解答和評論發現,分治法並無動態規劃解答的優雅,其時間複雜度爲O(nlogn),也並非最優。因此,介紹一下應用動態規劃解題的思路。設計
從數組第一個元素開始遍歷,用一個一維數組存儲遍歷到當前元素的最大連續子數組的和。3d
當遍歷到第i個元素時,若是前i-1和元素中連續子數組和加上第i個元素時比第i個元素的值要大,那麼就更新dp[i] = dp[i-1] + nums[i],不然dp[i] = nums[i]。指針
具體代碼:
class Solution { public int maxSubArray(int[] nums) { int[] dp = new int[nums.length + 1]; int result = nums[0]; for(int i = 0;i < nums.length;i++) { dp[i+1] = Math.max(dp[i]+nums[i], nums[i]); result = Math.max(dp[i+1], result); } return result; } }
執行結果:
題號:152,難度:中等
題目描述:
解題思路:
這題實際上是例1 最大子序和一個變例,由加法變換成了乘法操做(依舊是應用動態規劃的思路)。此時須要作的改變是定義兩個變量來存儲當前子序列的乘積,一個是保存最大值,一個是保存最小值(包含負數的子序列)。
具體代碼:
class Solution { public int maxProduct(int[] nums) { int result = nums[0], n_max = 1, n_min = 1; for(Integer n: nums) { if(n < 0) { int temp = n_max; n_max = Math.max(n_min * n, n); n_min = Math.min(temp * n, n); } else { n_max = Math.max(n_max * n, n); n_min = Math.min(n_min * n, n); } result = Math.max(n_max, result); } return result; } }
執行結果:
題號:78,難度:中等。(可參考子集II, 題號90,難度:中等)
題目描述:
解題思路:
本題考查咱們應用回溯來求解全部子集的問題,在一些算法教材中最經典的問題時求解全排列的問題,解法和這道題相似。
此題須要特別注意的是,首先採用鏈表在遞歸過程當中添加元素,在回溯時刪除元素,可以有效提升時間效率。其次,給遞歸調用程序設計一個start參數,能夠避免同一個元素被重複遞歸調用,達到了剪枝效果。
最後,在結果列表中採用從新建立一個列表存儲子集的結果,是由於在遞歸函數中列表參數只對應一個地址,採用從新建立至關於應用了深拷貝的思想,避免告終果均爲空集的狀況。
具體代碼:
class Solution { private List<List<Integer>> result; public List<List<Integer>> subsets(int[] nums) { result = new ArrayList<>(); if(nums.length <= 0) return result; dfs(nums, 0, new LinkedList<Integer>()); return result; } public void dfs(int[] nums, int start, LinkedList<Integer> list) { result.add(new ArrayList<Integer>(list)); for(int i = start;i < nums.length;i++) { list.addLast(nums[i]); dfs(nums, i + 1, list); list.removeLast(); } } }
執行結果:
題號:128,難度:困難
題目描述:
解題思路:
採用哈希表存儲數組中全部元素,而後應用哈希表查詢當前元素的左右兩邊序列數字是否存在,查詢操做的時間複雜度爲O(1),因此總體的時間複雜度爲O(n)。
具體代碼:
class Solution { public int longestConsecutive(int[] nums) { int result = 0; Set<Integer> set = new HashSet<>(); for(Integer n: nums) set.add(n); for(Integer n: nums) { if(set.contains(n)) { int len = 1; int temp = n; while(set.contains(--temp)) { len++; set.remove(temp); } temp = n; while(set.contains(++temp)) { len++; set.remove(temp); } result = Math.max(result, len); } } return result; } }
執行結果:
題號:713,難度:中等
題目描述:
解題思路:
本題考查應用雙指針的思想,一前一後同時日後遍歷。
具體代碼:
class Solution { public int numSubarrayProductLessThanK(int[] nums, int k) { int result = 0, left = 0, right = 0; int target = 1; while(right < nums.length) { target *= nums[right++]; while(left < right && target >= k) target = target / nums[left++]; result += (right - left); } return result; } }
執行結果:
題號:560,難度:中等
題目描述:
解題思路:
本題採用哈希表存儲從數組第一個元素不斷日後的子序列和,而後判斷到當前元素的序列總和減去K的值在哈希表中有多少個,即爲包含當前元素的子序列能夠獲得目標結果,利用先後子序列的差能夠獲得目標子序列和爲K。
具體代碼:
class Solution { public int subarraySum(int[] nums, int k) { Map<Integer, Integer> map = new HashMap<>(); map.put(0, 1); int sum = 0, result = 0; for(int i = 0; i < nums.length; ++i) { sum += nums[i]; if(map.containsKey(sum-k)) result += map.get(sum-k); map.put(sum, map.getOrDefault(sum, 0)+1); } return result; } }
執行結果:
題號:974,難度:中等
題目描述:
解題思路:
從第一個元素開始,求取連續子數組的餘數(sum % k),採用Map存儲每一個餘數的個數。
相同餘數的子數組個數大於等於2時,任意選取其中兩個子數組餘數相減,即餘數抵消,可獲得一個符合題目要求的sum。(此處的個數計算方式爲:n*(n-1) / 2)
可是,此處有兩個須要注意的點:
(1) 若是餘數爲0,最終0的餘數個數只有一個時(1*(1-1)/2 = 0),這樣計算會漏掉(若是爲多個,也會有遺漏,能夠本身計算,能夠本身稍微琢磨)。因此,在初始化Map時,添加如下代碼:
map.put(0, 1);
(2) 若是餘數爲負數,就不能執行相同餘數相減抵消的操做。此時,須要作如下處理:
// sum % K 正常計算方法 ((sum % K) + K) % K // 若是爲負數時,須要轉換爲正數,這個轉換原
具體代碼:
class Solution { public int subarraysDivByK(int[] A, int K) { Map<Integer, Integer> map = new HashMap<>(); map.put(0, 1); int result = 0; int sum = 0; for(Integer a: A) { sum += a; map.put(((sum % K) + K) % K , map.getOrDefault(((sum % K) + K) % K, 0)+1); } // System.out.println("map = "+map); for(Integer key: map.keySet()) result += map.get(key) * (map.get(key) - 1) / 2; return result; } }
執行結果:
題號:689,難度:困難
題目描述:
解題思路:
採用動態規劃求解,狀態轉移方程:dp[2][n] = max(dp[2][n-1], dp[1][n-k] + sumRange(n, n -k+1))。其中一維長度爲3,表示三個子數組。
具體代碼(代碼引用自LeetCode的一個題解):
class Solution { public int[] maxSumOfThreeSubarrays(int[] nums, int k) { int[][] dp = new int[3][nums.length]; int[] cummulative = new int[nums.length]; int sum = 0; for (int i = 0; i < nums.length; i++) { sum += nums[i]; cummulative[i] = sum; } for (int i = 0; i < 3; i++) { for (int j = 0; j < nums.length; j++) { if (j < (i + 1) * k - 1) { dp[i][j] = 0; } else { if (i == 0) { // 易錯點: 當k=1的時候,邊界條件須要處理一下。 dp[i][j] = Math.max(j > 0 ? dp[i][j - 1] : 0, rangeSum(cummulative, j - k + 1, j)); } else { dp[i][j] = Math.max(j > 0 ? dp[i][j - 1]: 0, rangeSum(cummulative, j - k + 1, j) + dp[i - 1][j - k]); } } } } int[] ans = new int[3]; int length = dp[2].length - 1; for (int i = 2; i >= 0; i--) { int[] row = dp[i]; for (int j = length - 1; j >= 0; j--) { if (row[j] != row[length]) { ans[i] = j - k + 2; length = j - k + 1; break; } } } return ans; } private int rangeSum(int[] cummulative, int left, int right) { if (left == 0) { return cummulative[right]; } else { return cummulative[right] - cummulative[left - 1]; } } }
執行結果:
題號:718,難度:中等
題目描述:
解題思路:
本題既能夠用哈希表來解答,也能夠用動態規劃的思想來解答。應用動態規劃的思路解答的時間效率最高。此處介紹一下動態規劃的解題思路。dp[i][j]表示A [i-1]爲終點,B[j-1]爲終點時二者的最長公共子數組。具體更新策略見代碼。
具體代碼:
class Solution { public int findLength(int[] A, int[] B) { int[][] dp = new int[A.length + 1][B.length + 1]; int res = 0; for (int i = 1; i <= A.length; i++) for (int j = 1; j <= B.length; j++) { if (A[i - 1] == B[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; res = Math.max(res, dp[i][j]); } return res; } }
執行結果:
題號:792,難度:中等
題目描述:
解題思路:
要特別注意子序列的含義,子序列是按照從前日後的順序任意多個元素組成的序列,其中的順序不能更改。所以,不能應用哈希表統計字母的個數來判斷是否包含某個單詞。此處可採用暴力法直接匹配查找,時間效率較低。此處可採用二分查找來優化匹配結果,能提升時間效率。
具體代碼(貼一個LeetCode上評論的代碼):
class Solution { List<Integer> index[]=new ArrayList[26]; public int numMatchingSubseq(String S, String[] words) { for(int i=0;i<S.length();i++){ char ch=S.charAt(i); if(index[ch-'a']==null) index[ch-'a']=new ArrayList(); index[ch-'a'].add(i); } int res=0,pre; for(String str:words){ pre=-1; for(int i=0;i<str.length();i++){ pre=helper(str.charAt(i)-'a',pre); if(pre==-1) break; } if(pre!=-1) res++; } return res; } private int helper(int i,int pre){ if(index[i]==null) return -1; int l=0,r=index[i].size()-1; if(pre==-1) return index[i].get(0); if(index[i].get(r)<=pre) return -1; while(l<r){ int mid=(l+r)/2; if(index[i].get(mid)<=pre) l=mid+1; else r=mid; } return index[i].get(l); } }
執行結果:
題號:795, 難度:中等
題目描述:
解題思路:
最大元素知足大於等於L小於等於R的子數組個數 = 最大元素小於等於R的子數組個數 - 最大元素小於L的子數組個數。
具體代碼:
class Solution { public int numSubarrayBoundedMax(int[] A, int L, int R) { return numSubarrayBoundedMax(A, R) - numSubarrayBoundedMax(A, L - 1); } private int numSubarrayBoundedMax(int[] A, int Max) { int res = 0; int numSubarry = 0; for (int num : A) { if (num <= Max) { numSubarry++; res += numSubarry; } else { numSubarry = 0; } } return res; } }
執行結果:
題號:907,難度:中等
題目描述:
解題思路:
參考自LeetCode的評論解答:計算每一個數在子數組中最小的次數。
具體代碼:
class Solution {
public int sumSubarrayMins(int[] A) { long res = 0; long mod = 1000000007; for (int i = 0; i<A.length; i++) { int l = i-1; for (; l>=0 && A[i] < A[l]; l--) ; int r = i+1; for (; r<A.length && A[i] <= A[r]; r++) ; res += (i-l)*(r-i)*A[i]; } return (int)(res % mod); } }
執行結果:
題號:891,難度:困難
題目描述:
解題思路:
具體代碼:
class Solution { public int sumSubseqWidths(int[] A) { final int MOD = (int) (1e9 + 7); Arrays.sort(A); int n = A.length; long res = 0; long p = 1; for (int i = 0; i < n; ++i) { res = (res + (A[i] - A[n - 1 - i]) * p) % MOD; p = (p << 1) % MOD; } return (int) ((res + MOD) % MOD); } }
執行結果:
題號:918, 難度:中等
題目描述:
解題思路:
由於題目要求有環形,因此須要定義兩個變量。一個變量存儲當前無環形是的連續最大子數組和,一個存儲無環形連續最小子數組和。最後採用數組的總和減去最小和,和已經保存的最大和進行比較。另外,須要注意一點若是數組所有爲負數時,此時直接返回子數組的最大值(由於此時,最小子數組和就是數組的和)。
具體代碼:
class Solution { public int maxSubarraySumCircular(int[] A) { int max = A[0]; int min = A[0]; int maxSoFar = A[0]; int minSoFar = A[0]; int sum = A[0]; for (int i=1;i<A.length;i++) { sum += A[i]; maxSoFar = Math.max(A[i],maxSoFar+A[i]); minSoFar = Math.min(A[i],minSoFar+A[i]); max = Math.max(max,maxSoFar); min = Math.min(min,minSoFar); } if (max < 0) return max; return Math.max(max,sum-min); } }
執行結果:
題號:978,難度:中等
題目描述:
解題思路:
採用連續三個位置數據是否符合湍流特徵來判斷,時間複雜度爲O(n)。
具體代碼(引用自LeetCode一個評論代碼):
class Solution { public int maxTurbulenceSize(int[] A) { int N = A.length; int ans = 1; int anchor = 0; for (int i = 1; i < N; ++i) { int c = Integer.compare(A[i-1], A[i]); if (i == N-1 || c * Integer.compare(A[i], A[i+1]) != -1) { if (c != 0) ans = Math.max(ans, i - anchor + 1); anchor = i; } } return ans; } }
執行結果:
題號:1031,難度:中等
題目描述:
解題思路:
採用滑動窗口的思路來解答,對長度爲L的數組,採用大小爲L的滑動窗口,對於長度爲M的數組採用大小爲M的窗口。而後,經過兩個窗口之間的距離來遍歷。
具體代碼:
class Solution { public int maxSumTwoNoOverlap(int[] A, int L, int M) { int len = A.length, dpL[] = new int[len - L + 1], dpM[] = new int[len - M + 1], max = 0; for (int i = 0; i < L; i++) dpL[0] += A[i]; for (int i = 0; i < M; i++) dpM[0] += A[i]; for (int i = 1; i < len - L + 1; i++) dpL[i] = dpL[i - 1] + A[i + L - 1] - A[i - 1]; for (int i = 1; i < len - M + 1; i++) dpM[i] = dpM[i - 1] + A[i + M - 1] - A[i - 1]; for (int i = 0; i < len - L - M + 1; i++) { int count = len - i - L - M; while (count >= 0) { max = Math.max(max, Math.max(dpL[i] + dpM[i + L + count], dpM[i] + dpL[i + M + count])); count--; } } return max; } }
執行結果:
題號:1157,難度:困難
題目描述:
解題思路:
採用哈希數組來解答,一旦哈希數組中目標元素值大於等於threshold,就返回目標數字,不然返回-1。
具體代碼:
class MajorityChecker { private int[] nums; private int[] ans; private int max; public MajorityChecker(int[] arr) { nums = arr; max = arr[0]; for(int x : arr) if(x > max) max = x; } public int query(int left, int right, int threshold) { ans = new int[max + 5]; for(int i = left;i <= right;i++){ if(++ans[nums[i]] >= threshold) return nums[i]; } return -1; } }
執行結果: