目录
六、区间
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;
}
}