徒手挖地球二六周目
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是不同面额种类
本人菜鸟,有错误请告知,感激不尽!