參考博主charlotte的總結,記錄自己的刷題過程。她真的很棒~https://github.com/huxiaoman7/leetcodebook
數組
什麼是數組
我們知道常用的數據存儲方式有兩種:順序存儲和非順序存儲。順序存儲就是把數據存儲在一塊連續的空間內。數組(array)就是典型的順序存儲,而鏈表就是典型的非順序存儲。
數組通常用於存儲一系列相同類型的數據。當我們在創建數組時,會在內存中劃分出一塊連續的內存用於存儲數據,插入數據時,會將數據按順序存儲在這塊連續的內存中,讀取時通過訪問數組的索引迅速取出。數組名就是一個指針,指向這段內存的起始地址。通過數組的類型,編譯器知道在訪問下一個元素的時候需要在內存中後移多少個字節。由於數組在存儲時是順序存儲的,存儲數據的內存也是連續的,所以數組在讀取數據時比較容易,隨機訪問速度快,但是插入和刪除就比較費勁了。讀取可以直接根據索引,插入和刪除則比較耗時,插一個數據需要移動大量元素,在內存中空出一個元素的空間,然後將要增加的元素放在其中,如果想刪除一個元素,同樣需要移動大量元素去掉被移動的元素。所以如果需求是快速訪問數據,很少或者幾乎不插入和刪除元素,數組是一個不錯的選擇。
最常見的有一維數組和二維數組,稍微複雜一點的是多維數組和動態數組。在c++中,STL提供了Vector,在Java中,Collection集合中提供了ArrayList和Vector,對於Python而言,內置的List就是一個動態指針數組。當列表中沒有空間存儲新的元素時,列表會動態地改變大小以容納新的元素,每次改變大小時,會預留一部分空間以降低改變大小的頻率。
類別
-
1.無序數組
- 概念:未經過排序的數組
- 優點:插入快
- 缺點:查找慢,刪除慢,大小固定
-
2.有序數組
-
概念:數組中的元素是按照一定規則排列的。
-
優點:查找效率高。根據元素值查找時可以使用二分查找,效率比無序數組高很多,在數據量大的時候尤其明顯。對於leetcode中很多查找元素類的題目,如果沒有事先說明是有序數組,可以事先對數組進行排序,再進行查找,二分法或其他方法都可以。
-
缺點:插入和刪除較慢。插入元素時,首先判斷該元素的小標,然後對該小標之後的所有元素後移以爲才能進行插入,所以有序數組比較適合查找頻繁,而插入刪除操作較少的情況。
複雜度 有序數組 無序數組 查找複雜度 O(logn) O(n) 插入複雜度 O(n) O(1)
-
-
3.如何權衡
- 根據需求性能判斷:對於插入時性能要求高而對查詢的要求不高或者插入操作遠多於查詢操作時,選擇無序數組;相反,如果對查詢性能要求較高或者查詢操作量大時,則選擇有序數組。
- 根據數據量判斷:數據量小且可預知,查詢速度比插入速度更重要使用有序數組;數據量大且不可知,插入速度比查詢速度更重要使用無序數組。
題型總結
【一維數組】
1.K-Sum
這類題目通常會給定一個數組和一個值,讓求出這個數組中兩個/三個/K個值的和等於這個給定的值target。leetcode第一題就是two-sum,對於這類題目,首先看題目要求的時間複雜度和空間複雜度是什麼,其次看有沒有限制條件,如要求不能有重複的子數組或者要求按照升序/降序排列等。解法如下:
1.暴力解法:最常見,但是通常會超時,只能作爲備選,
2.hash-map:建立一個hash-map循環遍歷一次即可
3.two-pointers:定位兩個指針根絕和的大小來移動另外一個。這裏設定的指針個數根據題目中K的個數來定。3Sum中可以設定3個指針,固定兩個,移動另一個。
Java
/*hash-map*/
class Solution {
public int[] twoSum(int[] numbers, int target) {
int[] result = new int[2];
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
for (int i = 0;i<numbers.length;i++){
if (map.containsKey(target-numbers[i])){
result[0]=map.get(target-numbers[i]);
result[1]= i+1;
}
map.put(numbers[i],i+1);
}
return result;
}
}
Leetcode中包含該類型的題目:
序號 | 題目 | 難度 | 代碼 |
---|---|---|---|
1 | Two Sum | easy | python、java、c++ |
167 | Two Sum II-Input array is sorted | easy | python、java、c++ |
15 | 3Sum | medium | python、java、c++ |
16 | 3Sum Closet | medium | python、java、c++ |
259 | 3Sum Smaller | medium | python、java、c++ |
18 | 4Sum | medium | python、java、c++ |
題解:
1. 使用hashmap遍歷中存儲對應的數值(target-nums[i]),但是因爲不能使用某元素兩次,所以要先判斷是否存在所需要的key再添加該元素。
167. 雙指針。一個指向頭元素,一個指向尾元素。如果和大於target就向前移動尾指針,如果小於target就向後移動頭指針,直到和爲目標值或者頭尾指針相遇。
15. 先排序,使用Arrays.sort(注意小寫)。然後從左往右遍歷,先固定一個元素,對後面所有元素做2SUM,target_temp=target-nums[i].但是要注意不可以重複,有兩種方式:一個是使用HashSet來做,一個是在遇到和下一個(對固定的元素和頭指針是後一個,對尾指針是前一個)相同的元素時需要跳過~
//使用HashSet
public List<List<Integer>> threeSum(int[] nums) {
Set<List<Integer>> res = new HashSet<>();
if(nums.length==0)
return new ArrayList<>(res);
Arrays.sort(nums);
for(int i=0; i<nums.length-2;i++){
int j =i+1;
int k = nums.length-1;
while(j<k){
int sum = nums[i]+nums[j]+nums[k];
if(sum==0)
res.add(Arrays.asList(nums[i],nums[j++],nums[k--]));
else if ( sum >0)
k--;
else if (sum<0)
j++;
}
}
return new ArrayList<>(res);//注意這裏要用ArrayList,因爲List是抽象類不能實例化
}
//使用規則判斷
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> set = new ArrayList<>();
for(int i=0;i<nums.length;i++){
if(i>0 && nums[i]==nums[i-1])//判斷一次
continue;
int target = -nums[i];
int l=i+1, r=nums.length-1;
while(l<r){
if((nums[l]+nums[r])==target){
List<Integer> tmp = new ArrayList<Integer>(nums[i], nums[l], nums[r]);
set.add(tmp);
l++;
r--;
while(nums[l]==nums[l-1] && l<r)//判斷兩次
l++;
while(nums[r]==nums[r+1] && l<r)//判斷三次
r--;
}
else if((nums[l]+nums[r])>target)
r--;
else
l++;
}
}
return set;
}
Question:
- 爲什麼return new ArrayList<>(res);正確 但是return Arrays.asList(res)不對?(其中Set<List<Integer>> res = new HashSet<>();)
看一下asList的描述:
public static <T> List<T> asList(T... a)
其中a應該是一個array,或者是有一系列數值/對象
// create an array of strings
String a[] = new String[]{"abc","klm","xyz","pqr"};
List list1 = Arrays.asList(a);
// printing the list
System.out.println("The list is:" + list1);
輸出結果
The list is:[abc, klm, xyz, pqr]
16 3sum closest
數組三個數之和與target值最近的和。
思路:排序,固定一個,用之後的元素2SUM。
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
if(nums==null || nums.length<3)
return target;
int start=0;
int res = nums[0]+nums[1]+nums[nums.length-1];
while(start<nums.length){
int l=start+1, r=nums.length-1;
while(l<r){
int sum = nums[start]+nums[l]+nums[r];
if(sum==target)
return target;
res = Math.abs(res-target)>Math.abs(sum-target)? sum:res;
if(sum>target)
r--;
else
l++;
}
start++;
}
return res;
}
}
18. 4Sum
四個數的和 思路:固定一個求3sum 注意3sumx中防止重複條件
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
int start=0;
Arrays.sort(nums);
while(start<nums.length){
if(start!=0 && nums[start]==nums[start-1]){
start++;
continue;
}
int temp_target = target-nums[start];
if(threeSum(start+1, nums, temp_target)!=null){
for(List<Integer> li:threeSum(start+1, nums, temp_target)){
//System.out.println("this is the result of 3sum");
ArrayList<Integer> tmp = new ArrayList<Integer>();
tmp.add(nums[start]);
tmp.addAll(li);
res.add(tmp);
}
}
start++;
}
return res;
}
public List<List<Integer>> threeSum(int sta, int[] nums, int target){
int start=sta;
List<List<Integer>> res = new ArrayList<>();
while(start<nums.length){
int temp_tar = target-nums[start];
if(start!=sta && nums[start]==nums[start-1]){
start++;
continue;
}
int l=start+1, r=nums.length-1;
while(l<r){
if((nums[l]+nums[r])==temp_tar){
res.add(Arrays.asList(nums[start], nums[l], nums[r]));
while(l<r && nums[l]==nums[l+1]) l++;
while(l<r && nums[r]==nums[r-1]) r--;
l++;
r--;
}
else if((nums[l]+nums[r])>temp_tar)
r--;
else
l++;
}
start++;
}
return res;
}
2. 區間問題
這類題目通常會給一個包含多個子數組的數組,然後針對區間是否重合來判斷true or false。
-
解題技巧:
1.按start排序
2.在前一個區間的end和後一個區間的start找交集
-
例題:252 meeting room[easy](https://leetcode.com/problems/meeting-rooms/)
-
題目理解:給定一個數組,包含每個會議的開始時間和結束時間[[s1,e1],[s2,e2],...],判斷一個人能否參加所有的會議。
-
test case:
Example1:
Input: [[0,30],[5,10],[15,20]]Output: falseExample2:Input: [[7,10],[2,4]]Output: true
-
解題思路:在這個題目裏,如果一個人要參加所有會議,那麼所有會議的時間應該不能重合,所以只需要判斷後一個會議開始的start > 前一個會議結束的end就就可以,如果end>start,就返回false。首先得先對所有子數組的start進行排序,然後再進行判斷start和end
class Solution{
public boolean canAttendMeetings(Interval[] intervals){
Arrays.sort(intervals,(x,y)->(x.start-y.start);
//i從1開始,因爲涉及到判斷前一個數組的end
for (int i =1;i <intervals.length;i++){
if (intervals[i-1].end > intervals[i].start){
return false;
}
}
return true;
Leetcode中包含該類型的題目:
序號 | 題目 | 難度 | 代碼 |
---|---|---|---|
56 | Merge Intervals | medium | python、java、c++ |
57 | Insert Interval | hard | python、java、c++ |
252 | Meeting Rooms | easy | python、java、c++ |
253 | Meeting Rooms II | medium | python、java、c++ |
352 | Data Stream as Disjoint Intervals | hard | python、java、c++ |
56. Merge intervals
需要注意combine函數應該向右合併。因爲後面可能還要繼續合併,resultLen計數的技巧。
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
int resultLen = intervals.length;
for(int i=0;i<intervals.length-1;i++){
if(intervals[i+1][0]<=intervals[i][1]){
combine(intervals, i, i+1);
resultLen--;//這裏很巧妙
}
}
int[][] res = new int[resultLen][2];
int j=0;
for(int i=0;i<intervals.length;i++){
if(intervals[i]!=null){
res[j] = intervals[i];
j++;
}
}
return res;
}
public void combine(int[][] intervals, int l, int r){
intervals[r][0] = intervals[l][0];
intervals[r][1] = Math.max(intervals[l][1], intervals[r][1]);//注意右邊界應該是兩個區間的右邊界的最大值
intervals[l] = null;//注意要賦空值方便判斷
}
57. Insert Interval
Example 1:
Input: intervals = [[1,3],[6,9]], newInterval = [2,5]
Output: [[1,5],[6,9]]
有序二維數組插入新的interval,如果有需要進行合併,合併之後依然有序。
思路一:利用56的合併區間的方法。定義新的數組插入新區間,sort之後合併。但是時間複雜度比較高。
public int[][] insert(int[][] intervals, int[] newInterval) {
int[][] new_intervals = new int[intervals.length+1][2];
for(int i=0;i<intervals.length;i++){
new_intervals[i] = intervals[i];
}
new_intervals[intervals.length] = newInterval;
Arrays.sort(new_intervals, (x, y) -> Integer.compare(x[0], y[0]));
int[][] res = merge(new_intervals);
return res;
}
public int[][] merge(int[][] intervals) {
int resultLen = intervals.length;
for (int m = 0; m < intervals.length - 1; m++) {
if (intervals[m + 1][0] <= intervals[m][1]) {
combine(intervals, m, m + 1);
resultLen -= 1;
}
}
int[][] result = new int[resultLen][2];
int j = 0;
for (int i = 0; i < intervals.length; i++) {
if (intervals[i] != null) {
result[j] = intervals[i];
j++;
}
}
return result;
}
private void combine(int[][] intervals, int pos1, int pos2) {
intervals[pos2][0] = intervals[pos1][0];
intervals[pos2][1] = Math.max(intervals[pos2][1], intervals[pos1][1]);
intervals[pos1] = null;
}
思路二:依次遍歷interval,用List<int[]>暫存結果,最後toArray,注意toArray的參數應該是目標對象的new一個實例。
遍歷的過程中,遇到不重疊的情況就直接放進去(在前面在後面),如果重疊就原地合併兩個區間,更新newInterval爲新的區間(注意應該是左邊界最小值,右邊界最大值)(不能直接插入,因爲後面可能還要合併)。
public int[][] insert(int[][] intervals, int[] newInterval) {
List<int[]> res = new ArrayList<>();
boolean flag = false;
for(int[] interval:intervals){
if(interval[1]<newInterval[0])
res.add(interval);
else if(interval[0]>newInterval[1]){
if(!flag){
res.add(newInterval);
flag = true;
}
res.add(interval);
}
else{//overlap的情亂,更新newInterval,但是不直接插入
newInterval[0] = Math.min(interval[0], newInterval[0]);
newInterval[1] =Math.max(interval[1], newInterval[1]);
}
}
if(!flag)//只有newInteerval是最後一個纔到這裏
res.add(newInterval);
return res.toArray(new int[res.size()][2]);
}
352. Data Stream as Disjoint Intervals
Given a data stream input of non-negative integers a1, a2, ..., an, ..., summarize the numbers seen so far as a list of disjoint intervals.
For example, suppose the integers from the data stream are 1, 3, 7, 2, 6, ..., then the summary will be:
[1, 1]
[1, 1], [3, 3]
[1, 1], [3, 3], [7, 7]
[1, 3], [7, 7]
[1, 3], [6, 7]
Follow up:
What if there are lots of merges and the number of disjoint intervals are small compared to the data stream's size?
看了半天才懂意思,就是有一個data stream,根據這個數據流生成不重疊的有序interval序列。和57很像,只不過每次來一個新的數字先自動生成一個<val, val>的區間,然後再往裏加,注意判斷條件稍微複雜點:不重疊,相鄰,重疊。
另外,因爲是要我們定義一個新的class,這個構造函數中要實例化intervals對象,但是你一開始的長度未知,所以不能用int[][]。要使用可變長度的數據結構——》List<int[]>。所以在getInterval就要對數據進行轉化。
class SummaryRanges {
/** Initialize your data structure here. */
public List<int[]> intervals;
public SummaryRanges() {
intervals = new ArrayList<>();
}
public void addNum(int val) {
int[] newInterval = new int[]{val, val};
List<int[]> res = new ArrayList<>();
int[][] intervals_arr = getIntervals();
boolean flag = false;
for(int[] interval :intervals_arr){
if(newInterval[0]-1>interval[1])
res.add(interval);
else if(newInterval[1]+1<interval[0]){
if(!flag){
res.add(newInterval);
flag = true;
}
res.add(interval);
}
else{
newInterval[0] = Math.min(newInterval[0], interval[0]);
newInterval[1] = Math.max(newInterval[1], interval[1]);
}
}
if(!flag)
res.add(newInterval);
intervals = res;
}
public int[][] getIntervals() {
return intervals.toArray(new int[intervals.size()][2]);
}
}
3.子數組類題目
這類題目通常會在一個包含多個子數組的數組中,求和/積,最大最小等。形式有很多種,例如求一個數組中和最小的子數組(209題),或者積最小的子數組(238題)
-
解題技巧:
-
滑動窗口(sliding window)
-
-
例題:209 Minimum Size Subarray Sum[Medium]
-
題目理解:給定我們一個數字,讓我們求子數組之和大於等於給定值的最小長度
-
test case:
Input: s = 7, nums = [2,3,1,2,4,3]
Output: 2
解釋:滿足子數組和=7的最小長度數組是[4,3],所以output=2
-
解題思路:求的數字要大於等於這個數字target,譬如這個testcase中,[2,3,1,2,4,3],從前往後相加,前四項相加的和爲8.已經大於7了,但是我們的target是7,後面的幾項也都是正整數,繼續往後走,肯定也會大於target7,所以這個時候我們把left指針往右移動一位,那麼就是相當於成爲了一個滑動窗口(sliding window),這種方法在String類型的題目出現的更多。
class Solution {
public int minSubArrayLen(int s, int[] nums) {
int res = Integer.MAX_VALUE;//因爲是求min,所以設定是Max_VALUE,然後最後的res比較就行,注意res不是0
int left = 0,sum = 0;
for (int i = 0;i<nums.length;i++){
sum += nums[i];//每遍歷一次,就把前i次數字相加求和
while (left <= i && sum >=s){
res = Math.min(res, i-left+1);
sum -= nums[left++];
}
}
return res == Integer.MAX_VALUE? 0:res;
Leetcode中包含該類型的題目:
序號 |
題目 | 難度 | 代碼 |
---|---|---|---|
78 | Subsets | medium | python、java、c++ |
90 | Subsets II | medium | python、java、c++ |
53 | Maximum Subarray | easy | python、java、c++ |
325 | Maximum Size Subarray Sum Equals k | medium | python、java、c++ |
209 | Minimum Size Subarray Sum | medium | python、java、c++ |
238 | Product of Array Except Self | medium | python、java、c++ |
152 | Maximum Product Subarray | medium | python、java、c++ |
239 | Sliding Window Maximum | hard | python、java、c++ |
295 | Find Median from Data Stream | hard | python、java、c++ |
228 | Summary Ranges | medium | python、java、c++ |
163 | Missing Ranges | medium | python、java、c++ |
78. Subsets
Given a set of distinct integers, nums, return all possible subsets (the power set).
Note: The solution set must not contain duplicate subsets.
Example:
Input: nums = [1,2,3] Output: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
思路一:非遞歸的解法,我們可以一位一位的網上疊加,比如對於題目中給的例子 [1,2,3] 來說,最開始是空集,那麼我們現在要處理1,就在空集上加1,爲 [1],現在我們有兩個自己 [] 和 [1],下面我們來處理2,我們在之前的子集基礎上,每個都加個2,可以分別得到 [2],[1, 2],那麼現在所有的子集合爲 [], [1], [2], [1, 2],同理處理3的情況可得 [3], [1, 3], [2, 3], [1, 2, 3], 再加上之前的子集就是所有的子集合了,
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
res.add(new ArrayList<>());
for(int i=0; i<nums.length; i++){
int size = res.size();
for(int j=0;j<size;j++){
//List<Integer> generate = res.get(j); 注意這裏不能直接賦值,因爲是對象,直接賦值還是一個對象
List<Integer> generate = new ArrayList<Integer>();
generate.addAll(res.get(j));
generate.add(nums[i]);
res.add(generate);
}
}
return res;
}
}