徒手挖地球二六週目
NO.57 插入區間 困難
思路一:暴力法 先將intervals和新區間都輸入到一個數組中,然後對數組中的區間進行合併得到結果。
徒手挖地球二五週目題解中NO.56合併區間中詳細描述瞭如何進行區間合併。
public int[][] insert(int[][] intervals, int[] newInterval) {
int n = intervals.length;
int[][] input=new int[n+1][2];
//將newInterval和Intervals都輸入一個數組
for (int i = 0; i < n; i++) {
input[i][0]=intervals[i][0];
input[i][1]=intervals[i][1];
}
input[n][0]=newInterval[0];
input[n][1]=newInterval[1];
//合併區間
return merger(input);
}
private int[][] merger(int[][] intervals) {
List<int[]> res=new ArrayList<>();
Arrays.sort(intervals,(o1,o2)->o1[0]-o2[0]);
for (int i = 0; i < intervals.length; i++) {
int left=intervals[i][0],right=intervals[i][1];
while (i<intervals.length-1&&right>=intervals[i+1][0]){
right=Math.max(right,intervals[i+1][1]);
i++;
}
res.add(new int[]{left,right});
}
return res.toArray(new int[0][]);
}
時間複雜度:O(nlogn) 將新舊區間輸入一個數組中需要遍歷一次,合併區間操作需要排序是nlogn複雜度,然後合併本身需要遍歷數組一次。
思路二:貪心算法 將當前的一小步進行最優處理,從而使整體最優。思路二是針對思路一進行優化。
本題中已經告知舊區間是有序的,所以思路一中的排序只是爲了讓新區間放置在末尾之後移動到有序的位置上,從而付出了nlogn的代價。
-
針對這一點很容易想到,在第一次遍歷舊區間合集的時候順便進行和新區間的比較,就能直接將新區間插入到有序的位置上。如何進行比較?
新區間應該放置到最後一個右邊界小於新區間左邊界的舊區間後面,這樣新區間放入位置之前的所有舊區間都不會和新區間重疊且不需要和新區間進行合併。
-
找到新區間的插入位置後先不要急於將新區間放入,因爲此時新區間可能需要和放入位置及其後序連續的舊區間進行合併。什麼樣的舊區間會和新區間進行合併?
新區間右邊界>=舊區間左邊界則說明新舊區間需要進行合併,例如[4,5]和[2,6]、[4,8]和[8,9]等等。。。
-
兩個區間合併的結果是[min(新區間左,舊區間左),max(新區間右,舊區間右)]。將合併後的新區間加入結果集。最後將剩餘的舊區間加入結果集。
public int[][] insert(int[][] intervals, int[] newInterval) {
List<int[]> res=new ArrayList<>();
int index=0,n=intervals.length;
//找到新區間的放置位置,最後一個右邊界小於新區間左邊界的舊區間的後面
while (index<n&&newInterval[0]>intervals[index][1]){
res.add(intervals[index++]);
}
//temp記錄合併後新區間的左右邊界值
int temp[]=new int[]{newInterval[0],newInterval[1]};
while (index<n&&newInterval[1]>=intervals[index][0]){
temp[0]=Math.min(temp[0],intervals[index][0]);
temp[1]=Math.max(temp[1],intervals[index][1]);
index++;
}
//將合併後的新區間放入結果集
res.add(temp);
//將剩餘區間放入結果集
while (index<n){
res.add(intervals[index++]);
}
return res.toArray(new int[0][]);
}
時間複雜度:O(n)
NO.58 最後一個單詞的長度 簡單
思路一:逆序 沒什麼好說的,從後往前找。注意trim()去空格
public int lengthOfLastWord(String s) {
if (s==null||s.equals(""))return 0;
String str = s.trim();
int i=str.length()-1;
for (; i >= 0; i--) {
if (str.charAt(i)==' ')break;
}
return str.length()-i;
}
時間複雜度:O(n)
NO.59 旋轉矩陣II 中等
思路一:按層模擬法 和徒手挖地球二四周目的NO.54螺旋矩陣的處理方法類似,一層一層遍歷,從左到右、由上到下、由右到左、由下到上從1開始每次自增1進行填充。
既然是四次方向變化,那麼就需要四個"標記"分別標識上面一行,右邊一列,下邊一行,左邊一列填充到的位置,標識分別叫做t,r,b,l。
有了標識之後從左向右就可以寫作for(i=l;i<=r;i++)
,即從左開始到右結束;遍歷完上面這一行就將標識t++,即t行填充完畢。
public int[][] generateMatrix(int n) {
int[][] res=new int[n][n];
int num=1,t=0,r=n-1,b=n-1,l=0;
while (num<=n*n){
//從左到右
for (int i = l; i <= r; i++) res[t][i]=num++;
t++;
//從上到下
for (int i = t; i <= b; i++) res[i][r]=num++;
r--;
//從右到左
for (int i = r; i >= l; i--) res[b][i]=num++;
b--;
//從下到上
for (int i = b; i >= t; i--) res[i][l]=num++;
l++;
}
return res;
}
時間複雜度:O(n^2)
NO.322 零錢兌換 中等
思路一:深度優先遍歷 暴力方法超時!檢查所有的組合方式,找出符合要求的組合中硬幣數量最少的。
int ans = Integer.MAX_VALUE;
public int coinChange(int[] coins, int amount) {
if (coins==null||coins.length==0)return -1;
dfs(coins,amount,0);
return ans==Integer.MAX_VALUE?-1:ans;
}
//深搜,count記錄硬幣數量
private void dfs(int[] coins, int amount, int count) {
//如果小於0,說明當前組合不對,回溯
if (amount<0){
return;
}
//等於0說明當前組合正確,如果硬幣數量更少,則更新結果
if (amount==0){
ans=Math.min(ans,count);
return;
}
for (int i = 0; i < coins.length; i++) {
dfs(coins,amount-coins[i],count+1);
}
}
時間複雜度:O(amount^n) n是不同面額硬幣的種類
思路二:動態規劃 dp數組的含義:dp[i]=x 表示至少x個硬幣組成i元,即i元的最優解。
dp數組初始化:長度爲amount+1,即0元~amount元。dp[0]=0,0元自然是不需要硬幣。1~amount初始化爲amount+1,因爲硬幣面額都是整數無論如何amount元也不會需要amount+1個硬幣進行組合。
狀態轉移:無論當前目標金額是多少,都要從coins列表中取出一個面額,然後目標金額就會較少這個面額。如果dp[當前金額-取出面額]有解,則dp[當前金額]就是在其子問題dp[當前金額-取出面額]的基礎上加一個硬幣。
public int coinChange(int[] coins, int amount) {
int[] dp=new int[amount+1];
//初始化
Arrays.fill(dp,amount+1);
dp[0]=0;
//填寫dp
for (int i = 1; i <= amount; i++) {
//金額i的所有子問題
for (int coin : coins) {
//不存在這個子問題
if (i-coin<0)continue;
//在子問題的基礎上加一個硬幣,取最小值
dp[i]=Math.min(dp[i],dp[i-coin]+1);
}
}
//檢查amount是否有解
return dp[amount]==amount+1?-1:dp[amount];
}
時間複雜度:O(amount*n) n是不同面額種類
本人菜鳥,有錯誤請告知,感激不盡!