目录
六、区间
48. 汇总区间 ①
49. 合并区间 ②
50. 插入区间 ②
51. 用最少数量的箭引爆气球 ② ×
七、栈
52. 有效的括号 ①
53. 简化路径 ② ×
54. 最小栈 ② ×
55. 逆波兰表达式求值 ② √-
56. 基本计算器 ③
六、区间
48. 汇总区间 ①
给定一个 无重复元素 的 有序 整数数组 nums
。
返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums
的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums
的数字 x
。
列表中的每个区间范围 [a,b]
应该按如下格式输出:
"a->b"
,如果a != b
"a"
,如果a == b
示例 1:
输入:nums = [0,1,2,4,5,7] 输出:["0->2","4->5","7"] 解释:区间范围是: [0,2] --> "0->2" [4,5] --> "4->5" [7,7] --> "7"
示例 2:
输入:nums = [0,2,3,4,6,8,9] 输出:["0","2->4","6","8->9"] 解释:区间范围是: [0,0] --> "0" [2,4] --> "2->4" [6,6] --> "6" [8,9] --> "8->9"
提示:
0 <= nums.length <= 20
-231 <= nums[i] <= 231 - 1
nums
中的所有值都 互不相同nums
按升序排列
方法1:
public List<String> summaryRanges(int[] nums) {ArrayList<String> list = new ArrayList<>();if (nums.length == 0){return null;}int left = 0;int right = left + 1;while (right < nums.length){if (nums[right] - nums[right - 1] == 1){right++;}else {if (right == left + 1){list.add(nums[left] + "");}else {list.add(nums[left] + "->" + nums[right - 1]);}left = right;right++;}}if (right == left + 1){list.add(nums[left] + "");}else {list.add(nums[left] + "->" + nums[right - 1]);}return list;}
方法2:(0ms)
public List<String> summaryRanges(int[] nums) {List<String> ret = new ArrayList<String>();int i = 0;int n = nums.length;while (i < n) {int low = i;i++;while (i < n && nums[i] == nums[i - 1] + 1) {i++;}int high = i - 1;StringBuffer temp = new StringBuffer(Integer.toString(nums[low]));if (low < high) {temp.append("->");temp.append(Integer.toString(nums[high]));}ret.add(temp.toString());}return ret;}
49. 合并区间 ②
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]] 输出:[[1,6],[8,10],[15,18]] 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]] 输出:[[1,5]] 解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
提示:
1 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104
二维数组排序:
// 先升序排序
Arrays.sort(intervals, (i1,i2) -> i1[0]-i2[0]);
方法1:(227ms)
public static int[][] merge(int[][] intervals) {sort(intervals);int[][] result = new int[intervals.length][2];int index = 0;ArrayDeque<Integer> queue = new ArrayDeque<>();for (int[] interval : intervals) {if (queue.size() == 0){queue.addLast(interval[0]);queue.addLast(interval[1]);} else {if (interval[0] <= queue.getLast()){if (interval[1] > queue.getLast()){queue.removeLast();queue.addLast(interval[1]);}}else {result[index][0] = queue.removeFirst();result[index][1] = queue.removeLast();index++;queue.addLast(interval[0]);queue.addLast(interval[1]);}}}result[index][0] = queue.getFirst();result[index][1] = queue.getLast();result = Arrays.copyOf(result, index + 1);return result;}public static int[][] sort(int[][] srcArrays){for (int i = 0; i < srcArrays.length - 1; i++) {for (int j = i + 1; j < srcArrays.length; j++) {if (srcArrays[i][0] > srcArrays[j][0]){int temp[] = srcArrays[i];srcArrays[i] = srcArrays[j];srcArrays[j] = temp;}}}return srcArrays;}
方法2:(0ms)
static int[][] merge(int[][] intervals) {int min = Integer.MAX_VALUE;int max = Integer.MIN_VALUE;for (int[] x : intervals) {min = Math.min(min, x[0]);max = Math.max(max, x[0]);}int[] range = new int[max - min + 1];for (int i = 0; i < intervals.length; i++) {// 记录了从某个start出发,最大结束区间是在哪里。即: range[start] = max(range[end])range[intervals[i][0] - min] = Math.max(intervals[i][1] - min, range[intervals[i][0] - min]);}int start = 0;int end = 0;List<int[]> res = new ArrayList<>();for (int i = 0; i < range.length; i++) {if (range[i] == 0) {// 没有从这个start出发的。continue;}// 如果有,就计算这个点能到多远if (i <= end) {// 这个start在end以内,说明可以连接起来end = Math.max(range[i], end);} else {// 这个satrt超出了end的范围,说明找到了一个区间。res.add(new int[] { start + min, end + min });start = i;end = range[i];}}res.add(new int[] { start + min, end + min });return res.toArray(new int[res.size()][]);}
方法3:(2ms)
public int[][] merge(int[][] intervals) {quickSort(intervals,0,intervals.length-1);List<int[]> ans = new ArrayList();ans.add(intervals[0]);for(int[] interval : intervals){int[] ansInterval = ans.get(ans.size()-1);if(ansInterval[1] < interval[0]){ans.add(interval);}else{ansInterval[1] = Math.max(ansInterval[1],interval[1]);}}return ans.toArray(new int[ans.size()][]);}
方法4:(8ms)
public int[][] merge(int[][] intervals) {// 先按照区间起始位置排序Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);// 遍历区间int[][] res = new int[intervals.length][2];int idx = -1;for (int[] interval: intervals) {// 如果结果数组是空的,或者当前区间的起始位置 > 结果数组中最后区间的终止位置,// 则不合并,直接将当前区间加入结果数组。if (idx == -1 || interval[0] > res[idx][1]) {res[++idx] = interval;} else {// 反之将当前区间合并至结果数组的最后区间res[idx][1] = Math.max(res[idx][1], interval[1]);}}return Arrays.copyOf(res, idx + 1);}作者:Sweetiee 🍬
链接:https://leetcode.cn/problems/merge-intervals/solutions/204805/chi-jing-ran-yi-yan-miao-dong-by-sweetiee/
方法5(9ms)
public int[][] merge(int[][] intervals) {Arrays.sort(intervals, (a, b) -> a[0] - b[0]);List<int[]> ans = new ArrayList<>();for (int i = 0; i < intervals.length; ++i) {if (ans.size() == 0 || intervals[i][0] > ans.get(ans.size() - 1)[1]) ans.add(intervals[i]);else ans.get(ans.size() - 1)[1] = Math.max(intervals[i][1], ans.get(ans.size() - 1)[1]);}return ans.toArray(new int[ans.size()][2]);}
50. 插入区间 ②
给你一个 无重叠的 ,按照区间起始端点排序的区间列表。
在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。
示例 1:
输入:intervals = [[1,3],[6,9]], newInterval = [2,5] 输出:[[1,5],[6,9]]
示例 2:
输入:intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8] 输出:[[1,2],[3,10],[12,16]] 解释:这是因为新的区间 [4,8] 与 [3,5],[6,7],[8,10] 重叠。
示例 3:
输入:intervals = [], newInterval = [5,7] 输出:[[5,7]]
示例 4:
输入:intervals = [[1,5]], newInterval = [2,3] 输出:[[1,5]]
示例 5:
输入:intervals = [[1,5]], newInterval = [2,7] 输出:[[1,7]]
提示:
0 <= intervals.length <= 104
intervals[i].length == 2
0 <= intervals[i][0] <= intervals[i][1] <= 105
intervals
根据intervals[i][0]
按 升序 排列newInterval.length == 2
0 <= newInterval[0] <= newInterval[1] <= 105
方法2:(0ms)
public int[][] insert(int[][] intervals, int[] newInterval) {int n = intervals.length;if(n == 0) {int[][] result = new int[1][2];result[0] = newInterval;return result;}int min = newInterval[0], max = newInterval[1];int start = 0;while (start < n && min > intervals[start][1]) {start++;}if(start == n) {int[][] result = new int[n+1][2];for (int i = 0; i < n; i++) {result[i] = intervals[i];}result[n] = newInterval;return result;}min = Math.min(min, intervals[start][0]);int end = n-1;while (end >= 0 && max < intervals[end][0]) {end--;}if(end == -1) {int[][] result = new int[n+1][2];result[0] = newInterval;for (int i = 0; i < n; i++) {result[i+1] = intervals[i];}return result;}max = Math.max(max, intervals[end][1]);int[][] result = new int[start + n - end][2];for (int i = 0; i < start; i++) {result[i] = intervals[i];}result[start] = new int[]{min, max};for (int i = 0; i < n - end - 1; i++) {result[start+1+i] = intervals[end+i+1];}return result;}
方法3:(1ms)
public int[][] insert(int[][] intervals, int[] newInterval) {List<int[]> list = new LinkedList<>();int i = 0;//区间不重合while(i < intervals.length && newInterval[0] > intervals[i][1]) {list.add(new int[]{intervals[i][0],intervals[i][1]});i++;}//区间开始重合 本题难点while(i < intervals.length && newInterval[1] >= intervals[i][0]) {newInterval[0] = Math.min(newInterval[0], intervals[i][0]);newInterval[1] = Math.max(newInterval[1], intervals[i][1]);i++;}list.add(newInterval);//剩下的区间加入到集合while(i < intervals.length) {list.add(intervals[i]);i++;}int[][] res = new int[list.size()][];return list.toArray(res);}
51. 用最少数量的箭引爆气球 ② ×
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points
,其中points[i] = [xstart, xend]
表示水平直径在 xstart
和 xend
之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x
处射出一支箭,若有一个气球的直径的开始和结束坐标为 x
start
,x
end
, 且满足 xstart ≤ x ≤ x
end
,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points
,返回引爆所有气球所必须射出的 最小 弓箭数 。
示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]] 输出:2 解释:气球可以用2支箭来爆破: -在x = 6处射出箭,击破气球[2,8]和[1,6]。 -在x = 11处发射箭,击破气球[10,16]和[7,12]。
示例 2:
输入:points = [[1,2],[3,4],[5,6],[7,8]] 输出:4 解释:每个气球需要射出一支箭,总共需要4支箭。
示例 3:
输入:points = [[1,2],[2,3],[3,4],[4,5]] 输出:2 解释:气球可以用2支箭来爆破: - 在x = 2处发射箭,击破气球[1,2]和[2,3]。 - 在x = 4处射出箭,击破气球[3,4]和[4,5]。
提示:
1 <= points.length <= 105
points[i].length == 2
-231 <= xstart < xend <= 231 - 1
方法2:(28ms)
public int findMinArrowShots(int[][] points) {if(points == null || points.length == 0) return 0;Arrays.sort(points, new Comparator<int[]>(){public int compare(int[] i, int[] j){if(i[1] == j[1]) return i[0] - j[0];return i[1] - j[1];}});int start = points[0][0], end = points[0][1], counts = 1;for(int i = 0; i < points.length; i++){if(points[i][0] <= end){start = Math.max(points[i][0], start);end = Math.min(points[i][1], end);}else{counts++; start = points[i][0]; end = points[i][1];}}return counts;}
方法3:(51ms)
public int findMinArrowShots(int[][] points) {Arrays.sort(points, (a, b) -> Integer.compare(a[1], b[1]));int pos = points[0][1];int count = 1;for (int i = 1; i < points.length; i++) {if (pos >= points[i][0]) {continue;} else {pos = points[i][1];count++;}}return count;}
方法4:(56ms)
public int findMinArrowShots(int[][] points) {// 贪心int n = points.length;if(n == 0) return 0;Arrays.sort(points, (a, b) -> Long.compare(a[1], b[1]));int result = 1;// 第一支箭直接射出int arrow = points[0][1]; for(int i = 1; i < n; i++){if(points[i][0] <= arrow){// 该区间能被当前箭right穿过continue;}arrow = points[i][1]; // 继续射出箭result++; // 箭数加1}return result;}作者:ydnacyw
链接:https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/solutions/2539356/java-tan-xin-tu-jie-yi-dong-by-cao-yang-yjv4c/
七、栈
52. 有效的括号 ①
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()" 输出:true
示例 2:
输入:s = "()[]{}" 输出:true
示例 3:
输入:s = "(]" 输出:false
提示:
1 <= s.length <= 104
s
仅由括号'()[]{}'
组成
public static boolean isValid(String s) {Stack<Character> stack = new Stack<>();for (int i = 0; i < s.length(); i++) {char c = s.charAt(i);if (stack.size() == 0 || c == '(' || c == '[' || c == '{'){stack.push(c);}else {if (c == ')' && stack.peek() == '('){stack.pop();}else if (c == ']' && stack.peek() == '['){stack.pop();}else if (c == '}' && stack.peek() == '{'){stack.pop();}else {stack.push(c);}}}return stack.size() == 0? true : false;}
方法2:
public boolean isValid(String s) {if ((s.length() & 1) != 0 || s.length() == 1) {return false;}int max = s.length() / 2;char[] chars = new char[max];int index = 0;for (int i = 0; i < s.length(); i++) {char c = s.charAt(i);if ('(' == c || '[' == c || '{' == c) {if (index >= max) {return false;}chars[index] = c;index++;} else if (index-- > 0) {char aChar = chars[index];if (')' == c) {if (aChar != '(') {return false;}} else if (']' == c) {if (aChar != '[') {return false;}} else if ('}' == c) {if (aChar != '{') {return false;}}} else {return false;}}return index==0;}
方法3:
public boolean isValid(String s) {char[] l = {'(','[','{'};char[] r = {')',']','}'};char[] ss = s.toCharArray();int n = ss.length;char[] st = new char[10010];int top = -1;st[++top] = ss[0];boolean flag = true;for(int i = 1; i < n; i++){char c = ss[i];if(c == '(' || c == '[' || c == '{')st[++top] = c;else{if(top < 0){flag = false;break;}if(c == ')' && st[top] != '('){flag = false;break;}if(c == ']' && st[top] != '['){flag = false;break;}if(c == '}' && st[top] != '{'){flag = false;break;}top--;}}if(top >= 0)flag = false;return flag;}
53. 简化路径 ② ×
给你一个字符串 path
,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/'
开头),请你将其转化为更加简洁的规范路径。
在 Unix 风格的文件系统中,一个点(.
)表示当前目录本身;此外,两个点 (..
) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,'//'
)都被视为单个斜杠 '/'
。 对于此问题,任何其他格式的点(例如,'...'
)均被视为文件/目录名称。
请注意,返回的 规范路径 必须遵循下述格式:
- 始终以斜杠
'/'
开头。 - 两个目录名之间必须只有一个斜杠
'/'
。 - 最后一个目录名(如果存在)不能 以
'/'
结尾。 - 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含
'.'
或'..'
)。
返回简化后得到的 规范路径 。
示例 1:
输入:path = "/home/" 输出:"/home" 解释:注意,最后一个目录名后面没有斜杠。
示例 2:
输入:path = "/../" 输出:"/" 解释:从根目录向上一级是不可行的,因为根目录是你可以到达的最高级。
示例 3:
输入:path = "/home//foo/" 输出:"/home/foo" 解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:
输入:path = "/a/./b/../../c/" 输出:"/c"
提示:
1 <= path.length <= 3000
path
由英文字母,数字,'.'
,'/'
或'_'
组成。path
是一个有效的 Unix 风格绝对路径。
方法2:(1ms)
public String simplifyPath(String path) {String[] arr = new String[path.length()];int index = 0,i=0;while (index < path.length()) {while (index < path.length() && path.charAt(index) == '/') {index++;}if (index == path.length()) break;int start = index;while (index < path.length() && path.charAt(index) != '/') {index++;}String s = path.substring(start,index);if ("..".equals(s)) {if (i > 0) {i--;}}else if (!".".equals(s)) {arr[i++] = s;}}StringBuilder sb = new StringBuilder();for (int j = 0; j < i; j++) {sb.append("/").append(arr[j]);}return sb.length() == 0 ? "/" : sb.toString();}
方法3:(3ms)
public String simplifyPath(String path) {Deque<String> deque = new LinkedList<>();int n = path.length();int start = 0, end = 1;while (end < n) {while (end < n && path.charAt(end) != '/') {end++;}String subString = path.substring(start, end);//认为是空目录if (subString.equals("/")) {start = end;end++;continue;}//当前目录if (subString.equals("/.")) {start = end;end++;continue;}//if (subString.equals("/..")) {if(!deque.isEmpty()) {deque.pollLast();}start = end;end++;continue;}deque.offerLast(subString.substring(1));start = end;end++;}StringBuffer stringBuffer = new StringBuffer();for (String s : deque) {stringBuffer.append("/");stringBuffer.append(s);}return stringBuffer.length() == 0 ? "/" : stringBuffer.toString();}
方法4:(8ms)
public String simplifyPath(String path) {Deque<String> stack = new LinkedList<>();for (String item : path.split("/")) {if (item.equals("..")) {if (!stack.isEmpty()) stack.pop();} else if (!item.isEmpty() && !item.equals(".")) stack.push(item);}String res = "";for (String d : stack) res = "/" + d + res;return res.isEmpty() ? "/" : res; }
54. 最小栈 ② ×
设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack
类:
MinStack()
初始化堆栈对象。void push(int val)
将元素val推入堆栈。void pop()
删除堆栈顶部的元素。int top()
获取堆栈顶部的元素。int getMin()
获取堆栈中的最小元素。
示例 1:
输入: ["MinStack","push","push","push","getMin","pop","top","getMin"] [[],[-2],[0],[-3],[],[],[],[]]输出: [null,null,null,null,-3,null,0,-2]解释: MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.getMin(); --> 返回 -3. minStack.pop(); minStack.top(); --> 返回 0. minStack.getMin(); --> 返回 -2.
提示:
-231 <= val <= 231 - 1
pop
、top
和getMin
操作总是在 非空栈 上调用push
,pop
,top
, andgetMin
最多被调用3 * 104
次
方法2(4ms)
// 数组栈, [当前值, 当前最小值]private Stack<int[]> stack = new Stack<>();public MinStack() {}public void push(int x) {if (stack.isEmpty()){stack.push(new int[]{x, x});}else {stack.push(new int[]{x, Math.min(x, stack.peek()[1])});}}public void pop() {stack.pop();}public int top() {return stack.peek()[0];}public int getMin() {return stack.peek()[1];}
方法3 :(6ms)
private Node head;public void push(int x) {if(head == null) head = new Node(x, x);else head = new Node(x, Math.min(x, head.min), head);}public void pop() {head = head.next;}public int top() {return head.val;}public int getMin() {return head.min;}private class Node {int val;int min;Node next;private Node(int val, int min) {this(val, min, null);}private Node(int val, int min, Node next) {this.val = val;this.min = min;this.next = next;}}
55. 逆波兰表达式求值 ② √-
给你一个字符串数组 tokens
,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
- 有效的算符为
'+'
、'-'
、'*'
和'/'
。 - 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是 向零截断 。
- 表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
示例 1:
输入:tokens = ["2","1","+","3","*"] 输出:9 解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入:tokens = ["4","13","5","/","+"] 输出:6 解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] 输出:22 解释:该算式转化为常见的中缀算术表达式为:((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = ((10 * (6 / (12 * -11))) + 17) + 5 = ((10 * (6 / -132)) + 17) + 5 = ((10 * 0) + 17) + 5 = (0 + 17) + 5 = 17 + 5 = 22
方法1:(18ms)
public static int evalRPN(String[] tokens) {Stack<String> stack = new Stack<>();for (int i = 0; i < tokens.length; i++) {String token = tokens[i];if (stack.size() == 0 ||token.matches("\\d+") || (token.charAt(0) == '-' && token.length() > 1)){stack.add(token);}else {int num2 = Integer.parseInt(stack.pop());int num1 = Integer.parseInt(stack.pop());int res = cal(num1, num2, token);stack.push(res + "");}}return Integer.parseInt(stack.peek());}public static int cal(int num1, int num2, String ope){int res = 0;switch (ope){case "+":res = num1 + num2;break;case "-":res = num1 - num2;break;case "*":res = num1 * num2;break;case "/":res = num1 / num2;break;}return res;}}