中级提升四
题目一:最小路径和
- 题目:
- 暴力递归:
(1)尝试方法:往下或者右的位置一次尝试
(2)basecase的确定:右下角
(3)特殊情况:在最右边,只能往下走;在最下边,只能往右走
public static int minPath(int[][] matrix){ if (matrix == null){ return 0; } return process(matrix, 0, 0); } public static int process(int[][] martix, int x, int y){ if (x == martix.length - 1 && y == martix[0].length - 1){ //basecase return martix[x][y]; } if (x == martix.length - 1){ //最右端,只能往下走 return martix[x][y] + process(martix, x, y + 1); } if (y == martix[0].length - 1){ //最底端 return martix[x][y] + process(martix, x + 1, y); } int right = process(martix, x + 1, y); int down = process(martix, x, y + 1); return martix[x][y] + Math.min(right, down); }
- 动态规划:
(1)两个可变参数,建立二维的dp表
(2)dp表的右下角,以及最右边,和最下边,可以直接确定结果
(3)dp表中的普遍位置依赖关系:由其右边与下边的较小值决定
(4)确定填表顺序,从dp的右下角到左上角
(5)最终位置:dp[0][0]
public static int minPath(int[][] matrix){ if (matrix == null){ return 0; } int[][] dp = new int[matrix.length][matrix[0].length]; dp[matrix.length - 1][matrix[0].length - 1] = matrix[matrix.length - 1][matrix[0].length - 1]; for (int x = matrix.length - 2; x >= 0 ; x--){ //只能往右走 dp[x][matrix[0].length - 1] = dp[x + 1][matrix[0].length - 1] + matrix[x][matrix[0].length - 1]; } for (int y = matrix[0].length - 2; y >= 0 ; y--){ //只能往下走 dp[matrix.length - 1][y] = dp[matrix.length - 1][y + 1] + matrix[matrix.length - 1][y]; } for (int x = matrix.length - 2; x >= 0; x--){ for (int y = matrix[0].length - 2; y >= 0; y--){ dp[x][y] = Math.min(dp[x + 1][y], dp[x][y + 1]) + matrix[x][y]; } } return dp[0][0]; }
题目二:容器装水问题
- 题目:
- 思路:
(1)先找到每一个位置可以容纳水的值,其由左侧的最大值与右侧的最大值决定
(2)单个位置存水量的表达式:max(min(leftMax,rightMax)- 当前位置高度,0)与0比较防止负数的出现
(3)最终将每一个位置加起来,就是其整体的容纳水量
(4)实现过程:leftMax与rightMax数组,存储每个位置左右的最大值,再求每一个位置的存水量
public static int maxWater(int[] arr) { int[] leftMax = new int[arr.length]; int[] rightMax = new int[arr.length]; int sum = 0; int max = Integer.MIN_VALUE; for (int i = 1; i < arr.length; i++) { max = Math.max(max, arr[i - 1]); leftMax[i] = max; } max = Integer.MIN_VALUE; for (int i = arr.length - 2; i >= 0; i--) { max = Math.max(max, arr[i + 1]); rightMax[i] = max; } for (int i = 0; i < arr.length; i++) { sum += Math.max((Math.min(leftMax[i], rightMax[i]) - arr[i]), 0); } return sum; }
- 优化:
(1)不使用leftMax与rightMax数组,只使用两个变量记录最值
(2)使用双下标left,right遍历数组,两边可以通过leftMax与rightMax中,较小的值确定一边的存水量,同时不断更新leftMax与rightMax
public static int maxWater(int[] arr){ int left = 0; int right = arr.length - 1; int leftMax = arr[left]; int rightMax = arr[right]; int sum = 0; while (left < right){ if (leftMax > rightMax){ right--; if (arr[right] < rightMax){ sum += (rightMax - arr[right]); }else { rightMax = arr[right]; } }else { left++; if (arr[left] < leftMax){ sum += (leftMax - arr[left]); }else { leftMax = arr[left]; } } } return sum; }
题目三:分割数组最大值
- 题目:
- 思路:
(1)预处理,得到每个位置左侧与右侧的最大值
(2)按照要求找到最大差值即可
public static int maxDevide(int[] arr) { int[] leftMax = new int[arr.length]; int[] rightMax = new int[arr.length]; int res = 0; int max = Integer.MIN_VALUE; for (int i = 0; i < arr.length; i++) { max = Math.max(max, arr[i]); leftMax[i] = max; } max = Integer.MIN_VALUE; for (int i = arr.length - 1; i >= 0; i--) { max = Math.max(max, arr[i]); rightMax[i] = max; } for (int i = 0; i < arr.length; i++) { res = Math.max(res, Math.abs(leftMax[i] - rightMax[i])); } return res; }
- 优化
(1)先找到整个数组的最大值,因为是整体划分成两部分,结果一定是max - 某个数
(2)若max划分到左部分,那么为了使得差值最大,右部分就是最右侧的数
(3)同理,若max划分到右部分,那么为了使得差值最大,左部分就是最左侧的数
(4)找到这两种情况产生的最大值即可
public static int maxDevide(int[] arr) { int leftMax = arr[0]; int rightMax = arr[arr.length - 1]; int max = 0; for (int i : arr){ max = Math.max(max, i); } return Math.max(max - leftMax, max - rightMax); }
题目四:旋转字符串
- 题目:
- 思路:
(1)先判断a,b字符串的长度是否相同,若不相同直接放回false
(2)a = a + a
(3)判断b是否是a的字串,可以使用kmp算法
public static boolean circleString(String a, String b){ if (a.length() != b.length()){ return false; } a = a + a; return kmp(a, b); } public static boolean kmp(String str1, String str2){ if (str1 == null || str2 == null || str1.length() < 1 || str2.length() < 1){ return false; } char[] chs1 = str1.toCharArray(); char[] chs2 = str2.toCharArray(); int index1 = 0; int index2 = 0; int[] next = getNextArray(chs2);//str2的nextArr数组 while (index1 < chs1.length && index2 < chs2.length){ if (chs1[index1] == chs2[index2]){ index1++; index2++; } else if (index2 == 0){ //等同与:else if (next[index2] == -1) //index2回到0位置,且str1仍不匹配,直接使index1移动 index1++; } else { //index2的跳跃,就是对应下标next数组中的值 index2 = next[index2]; } } //判断是否匹配成功 return index2 == chs2.length; } public static int[] getNextArray(char[] str) { if (str.length == 1){ return new int[] {-1}; } //默认,0位置是-1,1位置是1 int[] nextArr = new int[str.length]; nextArr[0] = -1; nextArr[1] = 1; int index = 2; //next数组的位置 int pre = 0; //要比较字符的位置(前缀的下一个)也有前缀长度的含义 while (index < str.length){ if (str[index - 1] == str[pre]){ //index - 1 与 pre 位置相同 //nextArr[index] 就是上一个位置加一 //pre:前缀长度增加 nextArr[index] = nextArr[index - 1] + 1; index++; pre++; } else if (pre > 0){ //不相同,跳到上一个前缀进行比较 pre = nextArr[pre]; }else { //跳到了最前面,也不相同 nextArr[index] = 0; index++; } } return nextArr; }
题目五:咖啡杯问题
- 题目
首先,给你几个数据:
数组arr:表示几个咖啡机,这几个咖啡机生产一杯咖啡所需要的时间就是数组中的值,例如arr=[2,3,7]就表示第一台咖啡机生产一杯咖啡需要2单位时间,第二台需要3单位时间,第三台需要7单位时间。
int N:表示有N个人需要用咖啡机制作咖啡,每人一杯,同时,假设制作完咖啡后,喝咖啡时间为0,一口闷。
int a:表示用洗碗机洗一个咖啡杯需要的时间,串行运行。
int b:表示咖啡杯也可以不洗,自然晾干的时间,咖啡杯要么洗,要么晾干。
现在,请你求出这N个人从开始用咖啡杯制作咖啡到杯子洗好或者晾干的最少时间?
- 思路:
(1)先生成每一个人对应最短时间喝完咖啡的时间drinkTimes
(2)drinkTimes可以通过小根堆的结果获得,小根堆元素由两个元素组成:机器空闲的时间,机器生成一次咖啡的时间。根据二者的和来确定小根堆的比较方式
(3)便可以根据dirnkTimes尝试a,b两个洗杯子的时间,暴力递归得到最小时间
public static class Machine{ //堆中放入的数据类型 public int timePoint; //一个机器可以开始使用的时间 public int workTime; //生成一杯咖啡的时间 public Machine(int t, int w){ timePoint = t; workTime = w; } } public static class MachineComparator implements Comparator<Machine>{ //小根堆的比较器 public int compare(Machine o1, Machine o2){ return (o1.workTime + o1.timePoint) - (o2.workTime + o2.timePoint); } } public static int minTime(int[] arr, int n, int a, int b){ PriorityQueue<Machine> heap = new PriorityQueue<Machine>(new MachineComparator()); for (int i : arr){ //初始的开始时间均为0,机器运行时间在arr中 heap.add(new Machine(0, i)); } int[] drinkTimes = new int[n]; for (int i = 0; i < n; i++){ //使用堆排序填drinkTimes Machine cur = heap.poll(); cur.timePoint += cur.workTime; drinkTimes[i] = cur.timePoint; heap.add(cur); } return process(drinkTimes, a, b, 0, 0); } public static int process(int[] drinkTimes, int a, int b, int index, int washLine){ //washTime:洗咖啡的机器有空的时间 //index:目前遍历到drinkTimes的位置 if (index == drinkTimes.length - 1){ //basecase //使用机器要受机器是否有空的时间控制 return Math.min(Math.max(washLine, drinkTimes[index]) + a, drinkTimes[index] + b); } //选择机器洗,计算时间 int wash = Math.max(washLine, drinkTimes[index]) + a; //洗完剩余咖啡杯最早的结束时间 int next1 = process(drinkTimes, a, b, index + 1, wash); //需要完成所有事情的时间,是由前二者的较大值决定的 int p1 = Math.max(wash, next1); //选择风干 int dry = drinkTimes[index] + b; //风干剩余咖啡杯最早的结束时间 int next2 = process(drinkTimes, a, b, index + 1, washLine); //需要完成所有事情的时间,是由前二者的较大值决定的 int p2 = Math.max(wash, next1); return Math.min(p1, p2); }
题目六:arr数组的特殊要求
- 题目:
- 思路:
(1)将arr中的数分为3类,奇数,因子含4的偶数,因子不含4的偶数,假设个数为a,b,c
(2)因为:奇数 * 奇数为奇数,奇数 * 因子不含4的偶数不满足题意,故奇数一定要与含4的偶数相邻;因子不含4的偶数要与偶数相邻
(3)故我们可以通过三种数字的个数判断arr的要求能否成功 - 情况分析
(1)c == 0:奇 _ 含4因子 _ 奇 _ 含四因子 _ 奇 ; b >= a - 1
(2)c != 0:将不含二因子的偶数放到一起 _ 含4因子 _ 奇 _ 含四因子 _ 奇 ;b >= a - 代码:
public static boolean adjustArr(int[] arr){ int a = 0; int b = 0; int c = 0; for (int i : arr){ if (i % 4 == 0){ b++; }else if (i % 2 == 0){ c++; }else { a++; } } return c == 0 && b >= a - 1 || c != 0 && b >= a; }
题目七:仅用栈实现队列
- 题目:在只有栈的数据结构的情况下,如何实现队列
- 应用:在一些必须使用队列的题目中,只允许使用栈的情况下,可以使用提供的栈,实现队列的功能。例如:使用栈的结构实现图的广度优先遍历
- 思路:
(1)构造两个栈push,pop
(2)每次加入数据时先加入push栈
(3)若要弹出数据先检查pop栈是否为空,若pop栈为空,一次性将push栈中的数据全部加入pop栈,再弹出pop栈的栈顶元素;若pop栈不为空则直接弹出pop的栈顶 - 代码:
public static class TwoStacksQueue{ private Stack<Integer> pushStack; private Stack<Integer> popStack; public TwoStacksQueue(){ pushStack = new Stack<Integer>(); popStack = new Stack<Integer>(); } public void push(int value){ pushStack.push(value); } public int pop(){ if (pushStack.empty() &&popStack.empty()){ throw new RuntimeException("Queue is empty"); } if (popStack.isEmpty()){ while (!pushStack.isEmpty()){ popStack.push(pushStack.pop()); } } return popStack.pop(); } public int peek(){ if (pushStack.empty() &&popStack.empty()){ throw new RuntimeException("Queue is empty"); } if (popStack.isEmpty()){ while (!pushStack.isEmpty()){ popStack.push(pushStack.pop()); } } return popStack.peek(); } }
题目八:仅用队列实现栈
- 题目:在只有队列的数据结构的情况下,如何栈
- 思路:
(1)构造两个队列one,two
(2)每次所有的数据都只在一个队列中
(3)若要加入数据只能加入到目前有数据的一个队列中,若目前两个队列均为空,默认加入队列one
(4)若要弹出数据,先将有数据的队列将除了最后一个全部加入另一个空队列,最后将最后一个元素返回即可 - 代码:
public static class TwoQueueStack{ private Queue<Integer> one; private Queue<Integer> two; public TwoQueueStack(){ one = new LinkedList<Integer>(); two = new LinkedList<Integer>(); } public void push(int value){ if (two.isEmpty()){ one.add(value); }else { two.add(value); } } public int pop(){ int res = 0; if (one.isEmpty() && two.isEmpty()){ throw new RuntimeException("Stack is empty"); }else { if (one.isEmpty()){ while (!two.isEmpty()){ res = two.poll(); if (!two.isEmpty()){ one.add(res); } } }else { while (!one.isEmpty()){ res = one.poll(); if (!one.isEmpty()){ two.add(res); } } } } return res; } }