避免重复刷题每次都两眼一抹黑咩都不记得,简单记一下思路。大概每天*10的进度。
题目:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
输入:nums = [3,2,4], target = 6
输出:[1,2]
输入:nums = [3,3], target = 6
输出:[0,1]
思路:哈希表
遍历数组,若此时哈希表中有有当前元素的另一半,直接返回;
若没有,将当前元素存入哈希表。
其中哈希表的结构,以数组元素为索引(因为是不重复的),数组元素对应的下标作值。
注意要求返回的是数组的下标,不然Hashset就行。
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap hash = new HashMap<>();
for(int i = 0;i < nums.length;i++){
if(hash.containsKey(target - nums[i]))
return new int[]{i, hash.get(target - nums[i])};
else
hash.put(nums[i],i);
}
return new int[]{};
}
}
题目:给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
输入:l1 = [0], l2 = [0]
输出:[0]
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
思路:
就是链表遍历吧,注意一下进位和链表长度不等时多出来的部分。
流程就是遍历两个链表,注意记录进位,然后当一个链表指针指向空时,作为零值计算。
一些注意点见注释。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode p = new ListNode(0); //指向头结点
ListNode q = p; //遍历结果链表的指针
int m = 0; //进位
while (l1 != null || l2 != null || m != 0){ //这里容易忘记链表遍历完但是还有进位的情况
int l1_nunm = l1 != null ? l1.val : 0;
int l2_nunm = l2 != null ? l2.val : 0;
int sum = l1_nunm + l2_nunm + m;
m = sum / 10;
sum = sum % 10;
ListNode s = new ListNode(sum);
q.next = s;
q = q.next;
//这里注意判断条件是l1!=null而不是l1.next,因为必须到指向null,作为零值
if(l1 != null) l1 = l1.next;
if(l2 != null) l2 = l2.next;
}
return p.next;
}
}
题目:给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
输入: s = ""
输出: 0
思路:滑动窗口
遍历元素,当前元素已出现时,可能需要移动左边界。
至于为什么是可能,因为元素是以hashmap存储的,在此过程中map里不删,所以有可能遍历到的元素是在left边界之前出现过的,并不影响当前在计算的子串。设查找到的已存在元素位于j处。
因此需要判断,这个已经在map里的元素是不是比left小,若该重复元素确实在边界以内,则需要移动边界至j+1,并将该元素的位置更新为i,保证子串内只有一个这个元素。
长度的计算通过i和left就行。
class Solution {
public int lengthOfLongestSubstring(String s) {
HashMap hash = new HashMap<>();
int max = 0;
int left = 0;
for(int i = 0;i < s.length();i++){
if(hash.containsKey(s.charAt(i))){ //已经出现过
left = Math.max(left, hash.get(s.charAt(i)) + 1);
}
hash.put(s.charAt(i),i);
max = Math.max(max, i - left + 1);
}
return max;
}
}
之前做的,这一次先不看困难的,回头再说
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int p = 0;
int q = 0;
boolean flag = true;
double x = 0,y = 0;
if ((nums1.length + nums2.length)%2 == 0)
flag = false;
int m = (nums1.length + nums2.length)/2;
for(int i = 0;i <= m ;i++){
x = y;
if(p == nums1.length)
y = nums2[q++];
else if (q == nums2.length)
y = nums1[p++];
else if(nums1[p] <= nums2[q])
y = nums1[p++];
else
y = nums2[q++];
}
if(!flag)
x = (x + y)/2;
else
x = y;
return x;
}
}
题目:给你一个字符串 s
,找到 s
中最长的回文子串。
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
输入:s = "cbbd"
输出:"bb"
输入:s = "a"
输出:"a"
输入:s = "ac"
输出:"a"
思路:
回文,即正反序相同。
中心扩散法。即以中心位置向两边发散,直至找到最大长度。
先向左找和中心元素一样的,再向右找和中心元素一样的,再同时向两边扩展。
不是最优解法,只是比较好理解。
class Solution {
public String longestPalindrome(String s) {
if(s.isEmpty() || s.length() < 2){
return s;
}
int left_max = 0;
int right_max = 0;
int len_max = 1;
boolean[][] dp = new boolean[s.length()][s.length()];
for (int i = 1; i < s.length(); i++){
int left = i - 1;
int right = i + 1;
int len = 1;
while (left >= 0 && s.charAt(left) == s.charAt(i)){
left--;
len++;
}
while (right <= s.length() - 1 && s.charAt(right) == s.charAt(i)){
right++;
len++;
}
while (left >= 0 && right <= s.length() - 1 && s.charAt(left) == s.charAt(right)){
left--;
right++;
len += 2;
}
if(len > len_max){
left_max = left;
len_max = len;
}
}
return s.substring(left_max + 1,left_max + len_max + 1);
}
}
暂略
题目:给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器。
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
之前做过一次。
思路大概是不断收缩长度,并且向短板的那边收缩。
应该不是最优。
class Solution {
public int maxArea(int[] height) {
int left = 0;
int right = height.length - 1;
int max_v = 0;
while (left < right){
int v = Math.min(height[left],height[right]) * (right - left);
max_v = v > max_v ? v : max_v;
if(height[left] < height[right]) //左边界是短板
left++;
else
right--;
}
return max_v;
}
}
题目:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
思路:排序,遍历数组,配合双指针,双指针分别指向剩余数组的左右边界
若和为0 ,返回;
若和小于0,是左边界太小了,左边界++;
若和大于0,是右边界太大了,右边界--。
注意由于要找的是不重复的,所以遍历的元素不能和上一个相同,找到一组之后要把左右边界都移动到元素不重复的位置。
class Solution {
public List> threeSum(int[] nums) {
List res = new ArrayList();
Arrays.sort(nums);
for(int i = 0;i < nums.length - 1;i++){
if(i > 0 && nums[i] == nums[i - 1]) continue;
int left = i + 1;
int right = nums.length - 1;
while (left < right){
int sum = nums[i] + nums[left] + nums[right];
if(sum == 0){ //找到了
List list = new ArrayList();
list.add(nums[i]);
list.add(nums[left]);
list.add(nums[right]);
res.add(list);
//注意以下,要跳过重复的元素
while (left < right && nums[left] == nums[left+1]) left++;
while (left < right && nums[right] == nums[right - 1]) right--;
//然后这个重复的其实已经计算过了,下一个
left++;
right--;
}
else if(sum < 0) //左边界值太小
left++;
else //右边界值太大
right--;
}
}
return res;
}
}
题目:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
思路:排列组合问题,递归。
解法不太优。
class Solution {
private String[] nummap = {
" ", //0
"", //1
"abc", //2
"def", //3
"ghi", //4
"jkl", //5
"mno", //6
"pqrs", //7
"tuv", //8
"wxyz" //9
};
private ArrayList res;
public List letterCombinations(String digits) {
res = new ArrayList();
if(digits.isEmpty()) return res;
findCombination(digits,0,""); //index标记找到digits哪一位了,s是当前的结果序列
return res;
}
private void findCombination(String digits, int index, String s){
if(index == digits.length()){ //根据index判断是否找完digits了,若找完,s就是这次的结果
res.add(s);
return;
}
String code = nummap[digits.charAt(index) - '0']; //取出本次要处理的字母
for(int i = 0;i < code.length();i++){
findCombination(digits, index + 1, s + code.charAt(i)); //把当前字母加进去,找下一位数字
}
}
}
题目:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
思路:快慢指针。
两个指针之间相差n,快指针遍历完的时候,慢指针走到倒数第n个结点,即p指向最后一个结点时,q指向要删结点的上一个。
注意一下如果要删的点就是第一个结点的话,q指向的不是要删的上一个结点,需要直接操作head
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode p = head; //快
ListNode q = p; //慢
//让快指针先跑n
for(int i = 0;i < n;i++){
p = p.next;
}
//p跑完了,开始一起跑
if(p == null) //不用跑了,要删的就是第一个元素
return head.next;
while (p.next != null){
p = p.next;
q = q.next;
}
p = q.next.next;
q.next.next = null;
q.next = p;
return head;
}
}
题目:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
思路:递归
终止条件:当两个链表都为空。
如何递归:判断 l1 和 l2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果,返回这个较小的结点给上一层进行连接。
注意一个为空了之后要返回另一个链表继续递归,直至两个链表都为空。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null) return l2;
else if(l2 == null) return l1;
else if(l1.val <= l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}
else{
l2.next = mergeTwoLists(l2.next,l1);
return l2;
}
}
}
题目:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
输入:n = 1
输出:["()"]
(一开始还没看懂怎么这么多括号,后来才发现结果是String数组的意思……)
思路:dfs
左右括号数量相同。终止条件是左右括号都没有了。
对于有左括号的情况,优先添加左括号。左括号多于右括号时,可以添加右括号。
class Solution {
List res = new LinkedList<>();
public List generateParenthesis(int n) {
dfs(n, n, "");
return res;
}
private void dfs(int left, int right, String str){
if(left == 0 && right == 0){
res.add(str);
return;
}
if(left > 0)
dfs(left - 1, right, str + "(");
if(right > left)
dfs(left, right - 1, str + ")");
}
}
暂略
题目:实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列(即,组合出下一个更大的整数)。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须 原地 修改,只允许使用额外常数空间。
输入:nums = [1,2,3]
输出:[1,3,2]
思路:从后往前找,对于每一位,他后面有没有比他更大的可以替换过来。
注意交换后要将交换的高位后面排序,以及如果不存在要重新排序成最小序列。
因为题目中条件1 <= nums.length <= 100,因此可以将101作为是否找到了的标志。
class Solution {
public void nextPermutation(int[] nums) {
for(int i = nums.length - 2;i >= 0;i--){
int min = 101;
int k = 0;
for(int j = nums.length - 1;j > i;j--){
if(nums[j] > nums[i] && nums[j] < min){
min = nums[j];
k = j;
}
}
if(min != 101){
int t = nums[i];
nums[i] = nums[k];
nums[k] = t;
Arrays.sort(nums,i + 1,nums.length);
return;
}
}
Arrays.sort(nums);
//System.out.println(Arrays.toString(nums));
}
}
上次写的,思路是一样的,但是上次写注释了↓
class Solution {
public static void nextPermutation(int[] nums) {
boolean flag = false; //标记是否有更大序列
for(int i = nums.length - 2;i >= 0;i--){ //从倒数第二个开始找
int t = 101; //标记最小的比nums[i]大的数,比元素最大为100,要是找完还是101就是没找着
int k = nums.length - 1; //范围是i+1~nums.length-1
for(int j = nums.length - 1;j > i;j--){
if(nums[j] > nums[i] && nums[j] < t){ //比nums[i]大且比当前最小的替换数小
t = nums[j]; //更新最小数
k = j; //更新最小数位置
}
}
if(t != 101){ //不是101就是找着了,对换
nums[k] = nums[i];
nums[i] = t;
Arrays.sort(nums,i+1,nums.length); //换完之后i之后的要排序成最小序列
flag = true;
break;
}
}
if(!flag) Arrays.sort(nums); //没找到,没有最大序列,排序输出最小序列
//System.out.println(Arrays.toString(nums));
}
}
暂略
题目:整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
思路:二分
二分查找,根据mid和左右边界判断哪边有序。
若左边有序,且在左边范围里,向左边收缩;若不在左边范围里,向右边收缩。
若右边有序,且在右边范围里,向右边收缩;若不在右边范围里,向左边收缩。
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid;
while (left <= right && left >=0){
mid = (left + right) / 2;
if(nums[mid] == target) return mid;
//左边有序
if(nums[left] <= nums[mid]){
//在左边
//这里判断的时候不要拿mid-1去判断,可能会越界,下同
if(target >= nums[left] && target < nums[mid])
right = mid - 1;
//在右边
else
left = mid + 1;
}
//右边有序
else {
//在右边
if(target > nums[mid] && target <= nums[right])
left = mid + 1;
//在左边
else
right = mid - 1;
}
}
return -1;
}
}
题目:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
思路:二分
mid == target 往左右找;
mid > target 在左边找;
mid < target 在右边找。
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid;
while (left <= right){
mid = (left + right) / 2;
//找到了,向两边找
if(nums[mid] == target){
int p = mid;
int q = mid;
//注意一下这里先判断p >= left,不然可能会越界异常
while (p >= left && nums[p] == target) p--;
while (q <= right && nums[q] == target) q++;
return new int[]{p + 1, q - 1};
}
//mid大,在左边
else if(nums[mid] > target)
right = mid - 1;
else
left = mid + 1;
}
return new int[]{-1,-1};
}
}
题目:给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。
对于给定的输入,保证和为 target 的唯一组合数少于 150 个。
输入: candidates = [2,3,6,7], target = 7
输出: [[7],[2,2,3]]
思路:dfs。
直接看注释吧。
class Solution {
private List> res = new ArrayList<>();
public List> combinationSum(int[] candidates, int target) {
Deque dq = new ArrayDeque<>();
dfs(candidates,0,target,dq);
return res;
}
//参数:数组、本次开始找的下标(为了避免重复解)、剩余目标值、当前序列
private void dfs(int[] candidates, int index,int target,Deque dq){
//结束条件:和为负值或找到了序列
if(target < 0) return;
if(target == 0){
res.add(new ArrayList<>(dq));
return;
}
//这里从index开始,就不会产生重复的解
for(int i = index;i < candidates.length;i++){
dq.addLast(candidates[i]);
//从i开始是因为每个值可以使用多次
dfs(candidates,i,target - candidates[i],dq);
dq.removeLast();
}
}
}
暂略
题目:给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
思路:跟上面的17差不多啊,全排列,dfs
主要注意一下终止条件、回溯要求
class Solution {
private List> res;
private boolean[] flag; //全排列,需要标记哪个数没用过
public List> permute(int[] nums) {
res = new ArrayList<>();
flag = new boolean[nums.length];
Deque list = new ArrayDeque<>();
dfs(nums,0,list);
return res;
}
void dfs(int[] nums,int index,Deque list){
//终止条件:index == nums,length
if(index == nums.length){
res.add(new ArrayList<>(list));
return;
}
for(int i = 0;i < nums.length;i++){
if(!flag[i]){
list.add(nums[i]);
flag[i] = true;
dfs(nums,index + 1,list);
flag[i] = false;
list.removeLast();
}
}
}
}
题目:给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
思路:先左右水平翻转,再沿右上左下对角线翻转。注意沿对角线翻转时下标的关系。
class Solution {
public void rotate(int[][] matrix) {
//水平翻转
//方阵,就不特别去区分行列数了
for(int i = 0;i < matrix.length;i++){
for(int j = 0;j < matrix.length/2;j++){
int t = matrix[i][j];
matrix[i][j] = matrix[i][matrix.length - 1 - j];
matrix[i][matrix.length - 1 - j] = t;
}
}
//沿右上-左下对角线翻转
for(int i = 0;i < matrix.length;i++){
for(int j = 0;j < matrix.length - 1 - i;j++){
int t = matrix[i][j];
matrix[i][j] = matrix[matrix.length - 1 - j][matrix.length - 1 - i];
matrix[matrix.length - 1 - j][matrix.length - 1 - i] = t;
}
}
/* for (int i = 0;i < matrix.length;i++)
System.out.println(Arrays.toString(matrix[i]));*/
}
}
题目:给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母都恰好只用一次。
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
题目解读:就是把由相同字母组成的串分类到一起的意思。
思路:排序,由相同字母组成的串排序后相同,可作为哈希的键值。主要注意使用的集合框架的操作和一些数据转换就行。
class Solution {
public List> groupAnagrams(String[] strs) {
Map> map = new HashMap<>();
for(String i : strs){
char[] t = i.toCharArray();
Arrays.sort(t);
String key = new String(t);
List list = map.getOrDefault(key,new ArrayList());
list.add(i);
map.put(key,list);
}
return new ArrayList>(map.values());
}
}
题目:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
思路:设当前遍历到i,前i-1个和为pre,若nums[i] + pre < nums[i],舍弃前i-1个,重新开始计和
class Solution {
public int maxSubArray(int[] nums) {
int pre = nums[0];
int max = pre;
for(int i = 1;i < nums.length;i++){
pre = pre + nums[i] > nums[i] ? pre + nums[i] : nums[i];
max = max > pre ? max : pre;
}
return max;
}
}
题目:给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
思路:贪心,每次都跳最大的,记录能跳的最远的。如果该点在之前能跳的最远范围以内,且能跳的更远,更新。
class Solution {
public boolean canJump(int[] nums) {
int end = 0;
for (int i = 0;i < nums.length;i++){
if(i <= end)
end = Math.max(end,i + nums[i]);
if(end >= nums.length - 1) return true;
}
return false;
}
}
题目:以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
思路:排序+双指针
排序后,可以合并的区间会靠在一起。
排序后可以找到左指针固定不动,向右找右区间,找完这个之后移动左指针即可。
这道本来想在原数组上改动来着,但是对于最后一个元素的判断老有问题,就改成存在结果数组里了。注释写的比较详细流程直接看吧,注意一下Arrays的排序和截取操作。
class Solution {
public int[][] merge(int[][] intervals) {
//这个的意思是按照intervals[0]来排序intervals
Arrays.sort(intervals, (x, y) -> x[0] - y[0]);
//结果数组
int[][] res = new int[intervals.length][2];
int left = 0; //左指针,指向当前在寻找的区间的左边界“下标”
int k = 0; //结果数组指针,记录一共有有几个合并区间
while (left < intervals.length){
int right_val = intervals[left][1]; //右边界的“值”,初始是左指针位置处的
int j = left + 1; //开始找最大右边界的下标
while (j < intervals.length && right_val >= intervals[j][0]){
//如果仍取right_val,区间是包含关系,否则可扩展右边界
right_val = Math.max(right_val, intervals[j][1]);
j++;
}
res[k][0] = intervals[left][0];
res[k][1] = right_val;
left = j;
k++;
}
/* for(int i = 0;i < k ;i++)
System.out.println(Arrays.toString(res[i]));*/
return Arrays.copyOf(res,k);
}
}
题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
输入:m = 3, n = 7
输出:28
思路:好标准的动态规划哦……本科算法分析课讲动态规划的时候例子好像就是个差不多的。
倒推理解,对于终点,可能是从左或上来的,对于左和上又分别可能是左和上……可以建立动态规划数组dp[m][n], dp[i][j]表示从起点到(i,j)这个位置的格子有几条路径。
dp[i][j] = dp[i-1][j] + dp[i][j-1]。
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for (int i = 0;i < m;i++) dp[i][0] = 1;
for (int i = 0;i < n;i++) dp[0][i] = 1;
for(int i = 1;i < m;i++){
for(int j = 1;j < n;j++) dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
return dp[m - 1][n - 1];
}
}
一种优化思路:相当于逐行算,有空再看看
class Solution {
public int uniquePaths(int m, int n) {
int[] cur = new int[n];
Arrays.fill(cur,1);
for (int i = 1; i < m;i++){
for (int j = 1; j < n; j++){
cur[j] += cur[j-1] ;
}
}
return cur[n-1];
}
}
题目:给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
思路:还是动态规划,但是dp数组里不存路径存最大和。可以在原数组上操作。
class Solution {
public int minPathSum(int[][] grid) {
for(int i = 1;i < grid[0].length;i++)
grid[0][i] += grid[0][i - 1];
for(int i = 1;i < grid.length;i++)
grid[i][0] += grid[i - 1][0];
for(int i = 1;i < grid.length;i++){
for(int j = 1;j < grid[0].length;j++)
grid[i][j] += Math.min(grid[i - 1][j],grid[i][j - 1]);
}
return grid[grid.length - 1][grid[0].length - 1];
}
}
题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
思路:也是动态规划。一维dp,dp[i] = dp[i - 1] + dp[i - 2]
唯一需要注意下的是第0层,到第0层有一种方法。
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n; i++)
dp[i] = dp[i - 1] + dp[i - 2];
return dp[n];
}
}
优化一下动态规划数组
class Solution {
public int climbStairs(int n) {
//f(0) = 1
//f(1) = 1
//f(2) = f(n - 1) + f(n - 2) 第一次跳一阶或者第一次跳两阶
int[] dp = {1, 1};
for(int i = 2;i <= n;i++){
int t = dp[0] + dp[1];
dp[0] = dp[1];
dp[1] = t;
}
return dp[1];
}
}
暂略
题目:给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
思路:呃,好质朴的排序题…第一反应直接Arrays.sort(nums)……
不过因为元素只有三个,可以用指针交换,把0不停的往前交换。或者双指针,一个不停交换0,一个指向当前已交换过去0的下一个,交换1,都交换完2就在最后了。
需要注意的是,若待交换元素为1,直接交换该位与1指针,1指针自增即可;
若待交换元素为0,1指针与0指针重叠(即还没换过来1)时,直接交换0指针与该位,两个指针自增;
若待交换元素为0且1指针大于0指针,则该位与0指针交换后,换过去的是1,要将该位与1指针再次交换换回来。
class Solution {
public void sortColors(int[] nums) {
int flag_0 = 0;
int flag_1 = 0;
for(int i = 0;i < nums.length;i++){
if(nums[i] == 0){ //如果是0
nums[i] = nums[flag_0];
nums[flag_0] = 0;
if(flag_0 < flag_1){
nums[i] = nums[flag_1];
nums[flag_1] = 1;
}
flag_0++;
flag_1++;
}
else if(nums[i] == 1){
nums[i] = nums[flag_1];
nums[flag_1] = 1;
flag_1++;
}
}
//System.out.println(Arrays.toString(nums));
}
}
还有个刷漆思路:将数组全设置为2,统计0和1的个数和刷上1,再统计0的个数和刷上。刷漆是从左往右刷。注意可以在一次循环中实现,即先刷上2,若该位小于等于1,扩展1的边界,若该位为0,扩展0的边界。
class Solution {
public void sortColors(int[] nums) {
int flag_1 = 0;
int flag_0 = 0;
for(int i = 0;i < nums.length;i++){
int t = nums[i];
nums[i] = 2; //刷2
if(t <= 1) nums[flag_1++] = 1;
if(t == 0) nums[flag_0++] = 0;
}
//System.out.println(Arrays.toString(nums));
}
}
暂略
题目:给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
思路:dfs
递归过程:(以nums[] = {1,2,3}为例)
index = 0 dq = {1}
index = 1 dq = {1,2}
index = 2 dq = {1,2,3}
index = 3 res = {1,2,3}
index = 2 dq = {1,2}
index = 3 res = {1,2,3},{1,2}
index = 2
index = 1 dq = {1}
index = 2 dq = {1,3}
index = 3 res = {1,2,3},{1,2},{1,3}
……
class Solution {
//dfs吧
List> res;
public List> subsets(int[] nums) {
res = new ArrayList<>();
Deque dq = new ArrayDeque<>();
dfs(nums, 0 ,dq);
return res;
}
private void dfs(int[] nums, int index, Deque dq){
if(index == nums.length){
res.add(new ArrayList<>(dq));
return;
}
dq.addLast(nums[index]);
dfs(nums,index + 1,dq);
dq.removeLast();
dfs(nums,index + 1,dq);
}
}
题目:给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
思路:dfs+回溯
看的别人的,详细写了下注释,有空再看看
class Solution {
public boolean exist(char[][] board, String word) {
int h = board.length, w = board[0].length;
//标记该位置是否被访问
boolean[][] visited = new boolean[h][w];
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
//遍历每个位置,都开始一下
boolean flag = check(board, visited, i, j, word, 0);
if (flag) {
return true;
}
}
}
return false;
}
//参数:字符、字符是否搜寻过、当前找到的字符下标、目标字符串、寻找到目标字符串的第几位了
public boolean check(char[][] board, boolean[][] visited, int i, int j, String s, int k) {
//判断该位与目标字符串对应位置是否相等
if (board[i][j] != s.charAt(k)) {
return false; //不等直接返回错误
} else if (k == s.length() - 1) {
return true; //目标字符串已经找完了,所以已经找到了
}
//目标字符串没找完,但是该位对上了的情况
visited[i][j] = true;
//该位可搜寻的下一个位置的四个方向
int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
boolean result = false; //标记在这四个方向里有没有找到
for (int[] dir : directions) { //向四个方向找
int newi = i + dir[0], newj = j + dir[1];
//还没超出边界就找
if (newi >= 0 && newi < board.length && newj >= 0 && newj < board[0].length) {
//该位还没有访问过
if (!visited[newi][newj]) {
boolean flag = check(board, visited, newi, newj, s, k + 1);
if (flag) { //找到了
result = true;
break;
}
}
}
}
//清除该位的访问,向上返回一层
visited[i][j] = false;
return result;
}
}
暂略
暂略
就是中序遍历,不抄题了,总之:
前序遍历:父 左 右
中序遍历:左 父 右
后序遍历:左 右 父区别就是访问父节点的顺序,命名也是这样,父节点在中间访问就是中序,左右的相对位置关系不变。
class Solution {
public List inorderTraversal(TreeNode root) {
List res = new ArrayList<>();
dfs(res,root);
return res;
}
public void dfs(List res, TreeNode root){
if(root == null ) return;
dfs(res,root.left);
res.add(root.val);
dfs(res,root.right);
}
}
题目:给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
输入:n = 3
输出:5
(应该复习了树再刷……二叉搜索都不太记得了)
定义:
二叉搜索树是一种节点值之间具有一定数量级次序的二叉树,对于树中每个节点:
若其左子树存在,则其左子树中每个节点的值都不大于该节点值;
若其右子树存在,则其右子树中每个节点的值都不小于该节点值。
所以从1~n中,分别都作一次父结点,则二叉搜索树总数为:
其中,若i为根节点,比i小的为左子树结点,共i-1个,比i大的为右子树结点,共n-i个
代入可得二叉搜索树的计算公式。
class Solution {
public int numTrees(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2;i <= n ;i++){
for(int j = 0;j <= i - 1;j++){
dp[i] += dp[j] * dp[i - j - 1];
}
}
return dp[n];
}
}
题目:给定一个二叉树,判断其是否是一个有效的二叉搜索树。
思路:中序遍历,判断上一个是不是比这个大。
中序遍历搜索树应该是个排序序列。麻烦一点的是先中序遍历再判断是否有序,但是实际上只需要前一个点的值。记一下这个Long的用法。
class Solution {
long pre = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
//left
if (!isValidBST(root.left)) return false;
if (pre >= root.val) return false;
pre = root.val;
return isValidBST(root.right);
}
}
题目:给定一个二叉树,检查它是否是镜像对称的。
思路:左右双指针
class Solution {
public boolean isSymmetric(TreeNode root) {
return dfs(root.left,root.right);
}
private boolean dfs(TreeNode L,TreeNode R){
if (L == null && R == null) return true;
else if(L == null || R == null) return false;
return (L.val == R.val)&&dfs(L.left,R.right)&&dfs(R.left,L.right);
}
}
题目:给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。队列用来放在同一层的点,每层进列表前先统计该层结点数目,弹出后该结点的左右儿子进队,直到该层的结点都出来为止(所以要先统计有多少个)。
层次遍历就是BFS……BFS的主要应用场景就是层次遍历和最短路径。一般DFS用递归,BFS用队列。
这个题测试用例里有空,要处理一下。
class Solution {
public List> levelOrder(TreeNode root) {
List> res = new ArrayList<>();
if (root == null) return res;
Queue queue = new ArrayDeque<>();
queue.add(root);
while (!queue.isEmpty()){
int size = queue.size(); //本层结点数
List list = new ArrayList<>();
//对于本层每个点,拿出来放结果里,左右儿子进队
for (int i = 0;i < size;i++){
TreeNode t = queue.poll();
list.add(t.val);
if (t.left != null)
queue.add(t.left);
if (t.right != null)
queue.add(t.right);
}
res.add(list);
}
return res;
}
}
题目:给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
思路:dfs
class Solution {
int max;
public int maxDepth(TreeNode root) {
max = 0;
int len = 0;
dfs(root,len);
return max;
}
public void dfs(TreeNode root, int len){
if (root == null) return;
len++;
max = max > len ? max : len;
dfs(root.left,len);
dfs(root.right,len);
}
}
题目:给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。
笔试里经常遇到这种选择题。解题思路大概是前序中父结点总在第一个,在中序中父结点左边为左子树,右边为右子树。所以是递归。
注意Arrays.copyOfRange函数右边界是不包含的。
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length == 0 || inorder.length == 0) return null;
TreeNode t = new TreeNode(preorder[0]); //前序的第一个是父结点
//然后要根据父结点在中序中的位置,划分左右子树
for (int i = 0;i < inorder.length;i++){
if(inorder[i] == t.val){
//先划分中序的吧,第i个是父结点,则0~i-1是左子树,i+1~inorder.length - 1是右子树
int[] in_left = Arrays.copyOfRange(inorder,0,i);
int[] in_right = Arrays.copyOfRange(inorder, i + 1,inorder.length);
//划分前序。前序第一个是父结点已经拿出来了,由中序得左子树结点数为i
int[] pre_left = Arrays.copyOfRange(preorder,1,i + 1);
int[] pre_right = Arrays.copyOfRange(preorder,i + 1,preorder.length);
t.left = buildTree(pre_left,in_left);
t.right = buildTree(pre_right,in_right);
break;
}
}
return t;
}
}
题目:给你二叉树的根结点 root ,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。
思路:前序遍历一遍,存下结点,再连起来。
class Solution {
public void flatten(TreeNode root) {
List list = new ArrayList<>();
dfs(root, list);
for (int i = 1; i< list.size();i++){
TreeNode pre = list.get(i - 1);
TreeNode now = list.get(i);
pre.left = null;
pre.right = now;
}
}
public void dfs(TreeNode root, List list){
if (root == null) return;
list.add(root);
dfs(root.left, list);
dfs(root.right, list);
}
}
题目:给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
思路:暴力解很明显就不说了,主要说动态规划法。
dp[i][j] 为第i天持股状态为j时的现金数。
dp[i][0] 当天不持股,那么如果昨天持股今天要卖出;
dp[i][1] 当天持股,那么如果昨天不持股今天要买入。
规划过程中取昨天两种情况的最大值。
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][2];
//初始化第一天
dp[0][0] = 0; //第一天不买
dp[0][1] = -prices[0]; //第一天买入
//遍历后面的,从第二天开始
for (int i = 1;i < prices.length;i++){
//今天不持股,昨天持股的话要卖出,或者昨天就没持股
dp[i][0] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][0]);
//今天持股,昨天不持股的话要买入,或者昨天就持股了
dp[i][1] = Math.max(-prices[i], dp[i - 1][1]);
}
return dp[prices.length - 1][0];
}
}
暂略
题目:给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
思路:放入哈希表后遍历。注意一下要先找到可以开始的序列值。
class Solution {
public int longestConsecutive(int[] nums) {
HashSet hash = new HashSet<>();
for (int i = 0;i < nums.length; i++){
hash.add(nums[i]);
}
int max = 0;
for(int x : hash){
//找到可以作为序列起点的值
if(hash.contains(x) && !hash.contains(x - 1)){
int t = x;
while (hash.contains(t + 1)) t++;
max = max > t - x + 1 ? max : t - x + 1;
}
}
return max;
}
}
题目:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
输入: [2,2,1]
输出: 1
思路:相等的值异或为0。0和任何数异或为该数本身。所以直接异或整个数组就行。
class Solution {
public int singleNumber(int[] nums) {
int res = 0;
for (int i : nums)
res ^= i;
return res;
}
}
题目:给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
0-1背包问题。
动态规划数组dp[i]定义为字符串0-i部分是否能拆分为字典子串,初始化dp[0]=true
dp[i]为0-j是字典子串,且j-i是字典子串。
class Solution {
public boolean wordBreak(String s, List wordDict) {
boolean[] dp = new boolean[s.length()+1];
dp[0] = true;
for (int i = 1;i <= s.length();i++){
for (int j = 0; j < i;j++){
if(dp[j] && wordDict.contains(s.substring(j,i)))
dp[i] = true;
}
}
return dp[s.length()];
}
}
题目:给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。
进阶:
你能用 O(1)(即,常量)内存解决此问题吗?
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
快慢指针,对于有环的链表,快指针一次跑两步,慢指针一次跑一步,有环的话两个指针会相遇。补充:快慢指针相遇后继续移动,直到第二次相遇。两次相遇间的移动次数即为环的长度。
这里注意下快指针跑第二步之前先判断下是否为空了
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode p1 = head;
ListNode p2 = head;
while (p1 != null){
p1 = p1.next;
if (p1 != null)
p1 = p1.next;
if(p1 == p2) return true;
p2 = p2.next;
}
return false;
}
}
题目:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
思路:还是快慢指针,这里看一下快慢指针相遇时的情况。
设head到入口有a个结点,环上有b个结点,相遇时快指针走了f步,慢指针走了s步。因为快指针一次走两步,因此f=2s。又相遇时快指针比慢指针多走了一圈,则第一次相遇时,f=s+b,求得s=b。
因为从头结点第二次到入口结点是a+b步,因此慢指针仍要走a步可回到入口结点。又一个指针从头结点走到入口结点是a步,所以设置一个指针(这里因为快指针用完了,就用快指针做这个计数指针了)和慢指针一起走a步,相遇在入口结点。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode s = head;
ListNode f = head;
while (true){
if (f == null || f.next == null) return null;
f = f.next.next;
s = s.next;
if (s == f) break;
}
//f和s相遇,s走了nb步,还需走a步到入口
f = head;
while (s != f){
s = s.next;
f = f.next;
}
return s;
}
}
集合类……记记算了
class LRUCache extends LinkedHashMap{
private int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75F, true);
this.capacity = capacity;
}
public int get(int key) {
return super.getOrDefault(key, -1);
}
// 这个可不写
public void put(int key, int value) {
super.put(key, value);
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > capacity;
}
}
排序问题……改天把排序算法整理过来再写
题目:
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
解析:前面做过一个最大和的,那个只需要看乘上这个数和这个数本身哪个大就行,但是乘法要考虑负数乘最小数才大,所以还要维护一个最小值,当出现负数时大小互换。
class Solution {
public int maxProduct(int[] nums) {
int imax = nums[0], imin = nums[0];
int max = imax;
for (int i = 1;i < nums.length; i++){
if (nums[i] < 0){
int t = imax;
imax = imin;
imin = t;
}
imax = Math.max(nums[i],imax * nums[i]);
imin = Math.min(nums[i],imin * nums[i]);
max = Math.max(imax,max);
}
return max;
}
}
题目:
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
思路:如果来的是新的最小值,存起来之前先把之前的最小值存一下,这样当最小值弹出后,栈顶就是上一个最小值。
class MinStack {
/** initialize your data structure here. */
public Stack st;
public int min = Integer.MAX_VALUE;
public MinStack() {
st = new Stack<>();
}
public void push(int val) {
if (val <= min){
st.push(min);
min = val;
}
st.push(val);
}
public void pop() {
if (st.pop() == min){
min = st.pop();
}
}
public int top() {
return st.peek();
}
public int getMin() {
return min;
}
}
题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
可以去看看题解,画的挺明白的。要让两者起点一致,需短指针走完后指向长队列,长队列走完后指向短队列,此时二者对齐了。这里注意判断条件不要用.next,因为如果没有交点会死循环,会跳过a==null && b == null的情况。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
ListNode a = headA;
ListNode b = headB;
while (a != b){
a = a == null ? headB : a.next;
b = b == null ? headA : b.next;
}
return a;
}
}
题目:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
思路:动态规划。设d[k]是偷窃范围为0~k的最大偷窃金额。
对于k号屋有两种情况,若偷,上一间不能偷,该问题为0~k-2范围内能偷的最大金额加k号的
若不偷,上一间可以偷,则和0~k-1范围内能偷的最大金额相同。
两者取大。
这里可以把动态规划数组优化为两个值。
class Solution {
public int rob(int[] nums) {
int[] dp = new int[nums.length + 1];
dp[0] = 0;
dp[1] = nums[0];
int max = dp[1];
for (int i = 2; i <= nums.length; i++){
dp[i] = Math.max(dp[i - 2] + nums[i - 1], dp[i - 1]);
max = Math.max(max, dp[i]);
}
return max;
}
}
题目:给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
岛屿问题都是深度优先搜索 。题解里有个用二叉树类比的写得挺好的。
找到一个陆地(1)就把它相邻的都遍历一遍,同时把已遍历的标位2,遍历完后计数+1,继续找其他的1,此时仍未标记为2的陆地和之前肯定不是一个岛屿。
class Solution {
public int res = 0;
public int numIslands(char[][] grid) {
for (int i = 0;i < grid.length; i++){
for (int j = 0;j < grid[0].length;j++){
if (grid[i][j] == '1'){
dfs(grid, i, j);
res++;
}
}
}
return res;
}
public void dfs(char[][] grid, int i, int j){
if (i < 0 || j < 0 || i > grid.length - 1 || j > grid[0].length - 1){
return;
}
if (grid[i][j] != '1') return;
grid[i][j] = '2';
dfs(grid, i - 1, j);
dfs(grid, i + 1, j);
dfs(grid, i, j - 1);
dfs(grid, i, j + 1);
}
}
题目:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
双指针
class Solution {
public ListNode reverseList(ListNode head) {
ListNode p = head;
ListNode q = null;
while (p != null){
ListNode s = p.next;
p.next = q;
q = p;
p = s;
}
return q;
}
}
递归
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null)
return head;
ListNode newhead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newhead;
}
}
题目:你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
思路:课程安排图是否是 有向无环图(DAG)。
拓扑排序:对 DAG 的顶点进行排序,使得对每一条有向边 (u, v)(u,v),均有 uu(在排序记录中)比 vv 先出现。亦可理解为对某点 vv 而言,只有当 vv 的所有源点均出现了,vv 才能出现。
流程(广度优先):
1. 统计每个节点的入度
2. 入度0的节点入队
3. 队非空,出队,该节点指向的节点入度减一。若减一后入度为0,进队。
4. 节点出队时课程总数减一,队列空时判断课程总数是否为零
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[] in = new int[numCourses];
//1. 统计每个节点的入度
for (int i = 0; i q = new ArrayDeque<>();
for (int i = 0; i < in.length; i++){
if (in[i] == 0) q.add(i);
}
//System.out.println(q.toString());
//3. 队非空,出队,指向节点入度减一,若入度减为0,入队
while (!q.isEmpty()){
int t = q.poll();
numCourses--;
for (int i = 0; i
题目:Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:
Trie() 初始化前缀树对象。
void insert(String word) 向前缀树中插入字符串 word 。
boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
输入
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
输出
[null, null, true, false, true, null, true]解释
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 True
trie.search("app"); // 返回 False
trie.startsWith("app"); // 返回 True
trie.insert("app");
trie.search("app"); // 返回 True
参考图片:力扣
每个节点都是一个26字母的数组
功能实现看注释
class Trie {
//前缀树节点,每个节点都是一个26字母的数组,同时要标记一下是不是结束
//标志位与后面的两种搜索有关
class TrieNode{
boolean end;
TrieNode[] t = new TrieNode[26];
}
/** Initialize your data structure here. */
TrieNode root; //根结点
public Trie() {
root = new TrieNode(); //构造函数即初始化根结点
}
/** Inserts a word into the trie. */
//插入,先计算当前字母在结点中的下标,若为空,将该节点初始化
//移动指针
public void insert(String word) {
TrieNode p = root;
for (int i = 0; i < word.length(); i++){
int n = word.charAt(i) - 'a';
if (p.t[n] == null) p.t[n] = new TrieNode();
p = p.t[n];
}
p.end = true;
}
/** Returns if the word is in the trie. */
//搜索该单词是否存在于前缀树,即完整的存在,该单词是树的一条路
//搜索到最后要判断结束的那个位置是不是该路的终点
public boolean search(String word) {
TrieNode p = root;
for (int i = 0; i < word.length(); i++){
int n = word.charAt(i) - 'a';
if (p.t[n] == null) return false;
p = p.t[n];
}
return p.end;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
//搜索该单词是否是前缀树的一个前缀,即允许部分存在,该单词是路的一部分(必须从头开始
//搜索到最后不用判断是否是终点
public boolean startsWith(String prefix) {
TrieNode p = root;
for (int i = 0; i < prefix.length(); i++){
int n = prefix.charAt(i) - 'a';
if (p.t[n] == null) return false;
p = p.t[n];
}
return true;
}
}
题目:给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
思路:排序后倒着找,标记一下找到第几大了。
注意一下值相等时,如 1 2 2 3,这里3算第四大,即第二个2虽然也是第2大,但是计数要+1
class Solution {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
int n = 1;
int pre = nums[nums.length - 1];
for (int i = nums.length - 2; i >= 0; i --){
if (n == k) return pre;
n++;
if (nums[i] == pre) continue;
pre = nums[i];
}
return pre;
}
}
题目:在一个由 '0' 和 '1' 组成的二维矩阵内,找到只包含 '1' 的最大正方形,并返回其面积。
输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]
输出:4
动态规划:dp(i, j) 是以 matrix(i - 1, j - 1) 为 右下角 的正方形的最大边长。
对于每个dp(i, j),
class Solution {
public int maximalSquare(char[][] matrix) {
int height = matrix.length;
int width = matrix[0].length;
int max = 0;
int[][] dp = new int[height + 1][width + 1];
for (int i = 0;i < height; i++){
for (int j = 0;j < width; j++){
if (matrix[i][j] == '1'){
dp[i + 1][j + 1] = Math.min(dp[i][j], Math.min(dp[i + 1][j], dp[i][j + 1])) + 1;
max = Math.max(max, dp[i + 1][j + 1]);
}
}
}
return max * max;
}
}
递归:先往下找左右儿子,交换后向上返回,直到返回根结点
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
}
}
题目:给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
输入:head = [1,2,2,1]
输出:true
思路:用栈,先遍历入栈,出栈顺序即逆序,和链表比对。
class Solution {
public boolean isPalindrome(ListNode head) {
Stack st = new Stack<>();
ListNode p = head;
ListNode q = head;
while (p != null){
st.push(p.val);
p = p.next;
}
while (!st.isEmpty()){
if (st.pop() != q.val) return false;
q = q.next;
}
return true;
}
}
题目:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == p || root == q) return root;
if (root != null){
TreeNode l = lowestCommonAncestor(root.left,p,q);
TreeNode r = lowestCommonAncestor(root.right,p,q);
if (l != null && r != null) return root; //左右子树各有一个
else if (l == null) return r; //左子树找不到,都在右子树
else return l; //右子树找不到,都在左子树
}
return null;
}
}
题目:给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
输入: [1,2,3,4]
输出: [24,12,8,6]
思路:除自身外的乘积可看作其左边的乘积与右边的乘积相乘,即先遍历出左乘积数组,再乘以右乘积。其中右乘积可以不用再开辟数组,节省空间。
class Solution {
public int[] productExceptSelf(int[] nums) {
//左右乘积表
int[] res = new int[nums.length];
//左乘积
res[0] = 1;
for (int i = 1;i < nums.length;i++){
res[i] = res[i - 1] * nums[i - 1];
}
//右乘积
int R = 1;
for (int i = nums.length - 2;i >= 0;i--){
R *= nums[i + 1];
res[i] = res[i] * R;
}
return res;
}
}
暂略
题目:编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true
要找到一个起始点,这里选择左下角,向右走增加,向上走减小。
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int i = matrix.length - 1;
int j = 0;
while (i >=0 && j < matrix[0].length){
if (matrix[i][j] == target) return true;
else if (matrix[i][j] > target) i--;
else j++;
}
return false;
}
}
题目:给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
动态规划:dp[i]表示正整数n最少可以用几个完全平方数和表示,dp[n]即需返回的结果。
对于dp[i],最大值为i,即i个1的和。或大于某数j的平方和加上另一部分,其中另一部分即dp[i - j^2]。取最小值。
class Solution {
public int numSquares(int n) {
int[] dp = new int[n + 1];
dp[0] = 0;
for (int i = 0; i <= n; i++){
dp[i] = i;
for (int j = 1;i - j * j >= 0;j++)
dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
}
return dp[n];
}
}
题目:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
思路:首先想到的是从前向后遍历数组,找到非0的就向前冒
class Solution {
public void moveZeroes(int[] nums) {
//不是0的往前冒
int k = 0;
for (int i = 0;i < nums.length;i++){
if (nums[i] != 0){
for (int j = i - 1;j >= k;j--){
int t = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = t;
}
k++;
}
}
}
}
当然复杂度很高……其实没必要冒,只需要两个指针分别指向从前向后第一个为0的位置,另一个遍历数组找不为0的元素和第一个指针交换即可。
class Solution {
public void moveZeroes(int[] nums) {
int k = 0;
while (k < nums.length && nums[k] != 0) k++;
for (int i = k + 1;i < nums.length;i++){
if (nums[i] != 0){
nums[k++] = nums[i];
nums[i] = 0;
}
}
}
}
题目:给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。
你设计的解决方案必须不修改数组 nums 且只用常量级 O(1) 的额外空间。
输入:nums = [1,3,4,2,2]
输出:2
类比160。注意先跑一次再进循环。
class Solution {
public int findDuplicate(int[] nums) {
//快慢指针
int f = 0, s = 0;
s = nums[s];
f = nums[nums[f]];
//快跑两步,慢跑一步
while (s != f){
s = nums[s];
f = nums[nums[f]];
}
//从头和慢指针一起走,相遇在交点
f = 0;
while (f != s){
s = nums[s];
f = nums[f];
}
return s;
}
}
暂略
题目:给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
思路:动态规划。dp[i]表示从0~i的最长递增子序列。
对于dp[i],至少为1,然后从头开始找0~i - 1范围里如果有比nums[i]小的数,在dp[i]和dp[j]+1里取小,dp[j]+1表示0~j的最长递增子序列且其最大值小于nums[i],所以+1(nums[i])
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
int res = 0;
for (int i = 0;i < nums.length;i++){
dp[i] = 1;
for (int j = 0;j < i;j++){
if (nums[j] < nums[i])
dp[i] = Math.max(dp[i], dp[j] + 1);
}
res = Math.max(dp[i], res);
}
return res;
}
}
暂略
题目:给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
class Solution {
public int maxProfit(int[] prices) {
int[][] f = new int[prices.length][3];
//f[i][0] 手上持有时的最大收益
//f[i][1] 手上不持有且冷冻时最大收益,即没有还不买
//f[i][2] 手上不持有且不冷冻时最大收益,即买入
f[0][0] = -prices[0];
for (int i = 1;i < prices.length;i++){
//若持有,i-1就持有或者i买入
f[i][0] = Math.max(f[i - 1][0], f[i - 1][2] - prices[i]);
//若不持有且冷冻,是i-1卖出了
f[i][1] = f[i - 1][0] + prices[i];
//若不持有且不冷冻,i-1冷冻或者i-1不冷冻(因为上一天买入的话会冷冻
f[i][2] = Math.max(f[i - 1][1], f[i - 1][2]);
}
return Math.max(f[prices.length - 1][1], f[prices.length - 1][2]);
}
}
暂略
题目:给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
完全背包。
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int i = 1;i <= amount;i++){
for (int j = 0;j < coins.length;j++){
if (coins[j] <= i)
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
题目:在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
任何一个节点能偷到的最大钱的状态可以定义为
1、当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
2、当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数
class Solution {
public int rob(TreeNode root) {
int[] res = dfs(root);
return Math.max(res[0], res[1]);
}
public int[] dfs(TreeNode root){
if (root == null) return new int[2];
int[] res = new int[2];
int[] left = dfs(root.left);
int[] right = dfs(root.right);
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
res[1] = left[0] + right[0] + root.val;
return res;
}
}
题目:给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。
输入:n = 2
输出:[0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10
class Solution {
public int[] countBits(int n) {
int[] dp = new int[n + 1];
for (int i = 0;i <= n ;i++){
dp[i] = dp[i >> 1] + (i & 1);
}
return dp;
}
}
题目:给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
class Solution {
public int[] topKFrequent(int[] nums, int k) {
HashMap map = new HashMap<>();
for (int i : nums){
map.put(i, map.getOrDefault(i, 0) + 1);
}
List[] list = new List[nums.length + 1];
for (Map.Entry entry : map.entrySet()) {
int num = entry.getKey(), count = entry.getValue();
if (list[count] == null) {
ArrayList temp = new ArrayList();
temp.add(num);
list[count] = temp;
}else {
list[count].add(num);
}
}
int[] result = new int[k];
for (int i = list.length - 1,count = 0; i >=0 && count < k; i--) {
while (list[i] != null && list[i].size() > 0 && count < k) {
result[count++] = list[i].remove(0);
}
}
return result;
}
}
题目:给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
输入:s = "3[a]2[bc]"
输出:"aaabcbc"