徒手挖地球二五週目
NO.55 跳躍遊戲 中等
思路一:貪心算法 NO.45跳躍遊戲II的姊妹題,思路一樣可以結合學習,題解參考徒手挖地球二三週目。
每次都在本次跳躍範圍內找到下一跳最遠的位置。如果最後最遠的都爲都不能到結尾,則false。
public boolean canJump(int[] nums) {
if (nums==null||nums.length==0)return true;
//end當前跳躍範圍,maxPosition記錄當前跳躍範圍內下一跳最遠的位置
int end=0,maxPosition=0;
for (int i = 0; i < nums.length-1; i++) {
//記錄當前範圍內下一跳最遠的位置
maxPosition=Math.max(maxPosition,nums[i]+i);
//走到當前跳躍最遠點
if (i==end){
//跳到最遠的位置
end=maxPosition;
}
}
return end>=nums.length-1;
}
時間複雜度:O(n)
NO.56 合併區間 中等
思路一:排序 將所有區間按照左邊界大小進行非遞減排序。
什麼樣的區間是重疊的需要合併?
[1,3]、[2,6] 第1個區間的右邊界大於下一個區間的左邊界即發生重疊。
需要合併成[第一個區間的左邊界,max(第一個區間的右邊界,第二個區間的右邊界)]這個區間加入結果集。
public int[][] merge(int[][] intervals) {
List<int[]> res=new ArrayList<>();
if (intervals==null||intervals.length==0)return res.toArray(new int[0][]);
//每個區間按照區間左邊界升序排序
Arrays.sort(intervals, (o1,o2)->o1[0]-o2[0]);
//遍歷每個區間
for (int pre = 0; pre < intervals.length; pre++) {
//記錄當前區間的左右邊界值
int left=intervals[pre][0],right=intervals[pre][1];
//如果當前區間的右邊界大於下一個區間的左邊界,即發生重疊
while (pre<intervals.length-1&&right>=intervals[pre+1][0]){
right=Math.max(right,intervals[pre+1][1]);
pre++;
}
res.add(new int[]{left,right});
}
return res.toArray(new int[0][]);
}
時間複雜度:O(n*logn) 區間數組只需要遍歷一次,主要是排序的時間複雜度。
NO.88 合併兩個有序數組 簡單
思路一:暴力法 沒啥說的直接B合併到A後面的預留位置,然後直接API對A進行排序。
public void merge(int[] A, int m, int[] B, int n) {
for (int i = 0; i < n; i++) {
A[m+i]=B[i];
}
Arrays.sort(A);
}
時間複雜度:O((m+n)*log(m+n)) 排序的複雜度
思路二:雙指針法 最直接想到的雙指針法就是像合併兩個有序鏈表一樣雙指針分別指向兩個數組開頭,從前向後遍歷兩個數組。
但是本題中A數組要作爲最終的結果數組,所以需要將A中的m個元素保存到A2數組中,然後像上述方法一樣雙指針遍歷A2和B數組,合併保存到A數組中。
public void merge(int[] nums1, int m, int[] nums2, int n) {
//保存nums1的m個元素
int[] A2=new int[m];
for (int i = 0; i < m; i++) {
A2[i]=nums1[i];
}
//雙指針比較併合並保存到nums1中
int i=0,j=0,index=0;
while (i<A2.length&&j<nums2.length){
nums1[index++]=(A2[i]<nums2[j]?A2[i++]:nums2[j++]);
}
//兩個數組有剩餘時保存到nums1後面
while (j<nums2.length)nums1[index++]=nums2[j++];
while (i<A2.length)nums1[index++]=A2[i++];
}
時間複雜度:O(m+n)
空間複雜度:O(m) 保存nums1的m個元素。
思路三:逆序雙指針法 不使用額外的數組去保存nums1的m個元素,從而優化空間。
方法就是:逆序!其實就是將思路二都逆向進行。
雙指針分別指向nums1和nums2的尾部,逆序遍歷,比較大的元素優先合併入結果數組;從結果數組的尾部向前保存併入的元素。
public void merge(int[] nums1, int m, int[] nums2, int n) {
//指針都指向尾部,比較大的元素優先合併至nums1尾部
int i=m-1,j=n-1,index=nums1.length-1;
while (i>=0&&j>=0){
nums1[index--]=(nums1[i]>nums2[j]?nums1[i--]:nums2[j--]);
}
//檢查是否有剩餘
while (i>=0)nums1[index--]=nums1[i--];
while (j>=0)nums1[index--]=nums2[j--];
}
時間複雜度:O(m+n)
空間複雜度:O(1)
NO.994 腐爛的橘子 簡單
思路一:廣度優先遍歷 這道題可以解讀爲:腐爛橘子到達最遠好橘子的最短路徑。
寫一個很簡陋的BFS的框架:
while(隊列不空){
node=隊列.poll();
for(node的鄰接節點){
if(鄰接節點m未曾入隊){
隊列.add(m);
}
}
}
知道了BFS,這道題就比較簡單了。
- 先遍歷一遍,統計初始新鮮橘子的數量並將初始腐爛橘子入隊。
- 然後BFS,同時round記錄進行了多少輪次的"傳染"。每輪開始都要記錄當前輪次開始有多少個壞橘子n。
- 將本輪開始時的所有壞橘子都出隊,並對出隊節點的四個鄰接節點進行判斷和"傳染"。
- 最後檢查好橘子還有沒有。
只有壞橘子纔會入隊,所以沒有框架裏鄰接節點m未曾入隊的檢查,因爲入過隊的都變成壞橘子了。
public int orangesRotting(int[][] grid) {
int row = grid.length,col=grid[0].length;
Queue<int[]> queue=new LinkedList<>();
//遍歷,統計新鮮橘子,壞橘子座標入隊
int count=0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (grid[i][j]==1)count++;
else if (grid[i][j]==2)queue.add(new int[]{i,j});
}
}
//round傳染的輪次
int round=0;
//當隊列不空並且還存在好橘子就廣搜BFS
while (count>0&&!queue.isEmpty()){
round++;
//n記錄當前壞橘子數量,防止出隊入隊導致不同輪次之間混亂
int n = queue.size();
for (int i = 0; i < n; i++) {
int[] rc = queue.poll();
int r=rc[0],c=rc[1];
//每個出隊的壞橘子的四個正方向上鄰接節點是否是好橘子,如果是就傳染腐爛併入隊
if (r-1>=0&&grid[r-1][c]==1){
grid[r-1][c]=2;
count--;
queue.add(new int[]{r-1,c});
}
if (r+1<row&&grid[r+1][c]==1){
grid[r+1][c]=2;
count--;
queue.add(new int[]{r+1,c});
}
if (c-1>=0&&grid[r][c-1]==1){
grid[r][c-1]=2;
count--;
queue.add(new int[]{r,c-1});
}
if (c+1<col&&grid[r][c+1]==1){
grid[r][c+1]=2;
count--;
queue.add(new int[]{r,c+1});
}
}
}
if (count>0)return -1;
else return round;
}
時間複雜度:O(n) n數組元素個數,整個過程遍歷數組兩次。
代碼很冗長,但是思路還算清楚。
本人菜鳥,有錯誤請告知,感激不盡!