需要开通vip的题目暂时跳过
点击链接可跳转到所有刷题笔记的导航链接
删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。
说明: 输入可能包含了除 (
和 )
以外的字符。
解答
private HashSet<String> set = new HashSet<>();
int minModify = Integer.MAX_VALUE;
public List<String> removeInvalidParentheses(String s) {
dfs(s,0,0,0,0,new StringBuilder());
return new ArrayList<String>(set);
}
public void dfs(String s,int index,int leftCount,int rightCount,int remove,StringBuilder temp){
if(rightCount > leftCount)return;
if(index == s.length()){
if(leftCount == rightCount){
if(remove <= minModify){
if(remove < minModify){
set.clear();
minModify = remove;
}
set.add(temp.toString());
}
}
return;
}
char currentChar = s.charAt(index);
if(currentChar != ')' && currentChar != '('){
temp.append(currentChar);
dfs(s,index+1,leftCount,rightCount,remove,temp);
temp.deleteCharAt(temp.length()-1);
}else{
dfs(s,index+1,leftCount,rightCount,remove+1,temp);
temp.append(currentChar);
if(currentChar == '(')
dfs(s,index+1,leftCount+1,rightCount,remove,temp);
else
dfs(s,index+1,leftCount,rightCount+1,remove,temp);
temp.deleteCharAt(temp.length()-1);
}
}
分析
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
class NumArray {
private int[] sum;
public NumArray(int[] nums) {
sum = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) {
sum[i + 1] = sum[i] + nums[i];
}
}
public int sumRange(int i, int j) {
return sum[j + 1] - sum[i];
}
}
分析
提交结果
给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2)。
上图子矩阵左上角 (row1, col1) = (2, 1) ,右下角(row2, col2) = (4, 3),该子矩形内元素的总和为 8。
int[][] sum;
public NumMatrix(int[][] matrix) {
if(matrix.length == 0 || matrix[0].length == 0)return;
sum = new int[matrix.length + 1][matrix[0].length + 1];
sum[1][1] = matrix[0][0];
for (int i = 2; i < sum.length; i++) {
sum[i][1] = sum[i - 1][1] + matrix[i - 1][0];
}
for (int i = 2; i < sum[0].length; i++) {
sum[1][i] = sum[1][i - 1] + matrix[0][i - 1];
}
for (int i = 2; i < sum.length; i++) {
for (int j = 2; j < sum[0].length; j++) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + matrix[i - 1][j - 1];
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
if(row1+1 >= sum.length || row2 >= sum.length || col1 >= sum[0].length || col2 >= sum[0].length)return 0;
return sum[row2 + 1][col2 + 1] - (sum[row1][col2 + 1] + sum[row2 + 1][col1] - sum[row1][col1]);
}
分析
累加数是一个字符串,组成它的数字可以形成累加序列。
一个有效的累加序列必须至少包含 3 个数。除了最开始的两个数以外,字符串中的其他数都等于它之前两个数相加的和。
给定一个只包含数字 ‘0’-‘9’ 的字符串,编写一个算法来判断给定输入是否是累加数。
说明: 累加序列里的数不会以 0 开头,所以不会出现 1, 2, 03 或者 1, 02, 3 的情况。
public boolean isAdditiveNumber(String num) {
if (num == null || num.length() < 3)
return false;
return backtrack(0, num, new ArrayList<>());
}
private boolean backtrack(int start, String num, List<String> tmp) {
if (start == num.length() && tmp.size() > 2)
return true;
for (int i = start; i < num.length(); i++) {
String s = num.substring(start, i + 1);
if ((s.length() > 1 && s.charAt(0) == '0'))
return false;
if (s.length() > num.length() / 2) //剪枝
return false;
int size = tmp.size();
if (size < 2 || s.equals(addStrNum(tmp.get(size - 1), tmp.get(size - 2)))) {
tmp.add(s);
if (backtrack(i + 1, num, tmp)) //找到一个结果就返回
return true;
tmp.remove(tmp.size() - 1);
}
}
return false;
}
private static String addStrNum(String a, String b) {
//两数相加
StringBuilder sum = new StringBuilder();
int c = 0; //进位
for (int ai = a.length() - 1, bi = b.length() - 1; ai >= 0 || bi >= 0; ) {
int s = 0;
if (ai >= 0) s += a.charAt(ai--) - '0';
if (bi >= 0) s += b.charAt(bi--) - '0';
sum.append((s + c) % 10);
c = (s + c) >= 10 ? 1 : 0;
}
if (c > 0) sum.append(1);
return sum.reverse().toString();
}
分析
提交结果
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
update(i, val) 函数可以通过将下标为 i 的数值更新为 val,从而对数列进行修改。
//方法一
class NumArray {
int[] dp;
int[] numbers;
int len;
public NumArray(int[] nums) {
numbers = nums;
len = nums.length;
dp = new int[len+1];
if(nums.length == 0)return;
dp[1] = nums[0];
for(int i = 2;i<len+1;i++){
dp[i] = dp[i-1] + nums[i-1];
}
}
public void update(int i, int val) {
for(int j = i;j<len;j++){
dp[j+1] = dp[j+1] - numbers[i] + val;
}
numbers[i] = val;
}
public int sumRange(int i, int j) {
return dp[j+1] - dp[i];
}
}
//方法二
class NumArray {
private int[] nums;
public NumArray(int[] nums) {
this.nums = nums;
}
public int sumRange(int i, int j) {
int sum = 0;
for (int l = i; l <= j; l++) {
sum += nums[l];
}
return sum;
}
public void update(int i, int val) {
nums[i] = val;
}
}
//方法三
class NumArray {
private int[] b;
private int len;
private int[] nums;
public NumArray(int[] nums) {
this.nums = nums;
double l = Math.sqrt(nums.length);
len = (int) Math.ceil(nums.length/l);
b = new int [len];
for (int i = 0; i < nums.length; i++)
b[i / len] += nums[i];
}
public int sumRange(int i, int j) {
int sum = 0;
int startBlock = i / len;
int endBlock = j / len;
if (startBlock == endBlock) {
for (int k = i; k <= j; k++)
sum += nums[k];
} else {
for (int k = i; k <= (startBlock + 1) * len - 1; k++)
sum += nums[k];
for (int k = startBlock + 1; k <= endBlock - 1; k++)
sum += b[k];
for (int k = endBlock * len; k <= j; k++)
sum += nums[k];
}
return sum;
}
public void update(int i, int val) {
int b_l = i / len;
b[b_l] = b[b_l] - nums[i] + val;
nums[i] = val;
}
}
//方法四
class NumArray {
int[] tree;
int n;
public NumArray(int[] nums) {
n = nums.length;
tree = new int[n * 2];
for(int i = n; i < 2*n; i++){
//叶子节点
tree[i] = nums[i-n];
}
for(int i = n-1; i >= 0; i--){
//建树
tree[i] = tree[i*2] + tree[i*2+1];
}
}
public void update(int i, int val) {
int pos = n + i;//找到叶子节点
tree[pos] = val;//叶子节点的值修改
while(pos > 0){
int left = pos%2==0? pos: pos-1;
int right = pos%2==0? pos+1: pos;
tree[pos/2] = tree[left] + tree[right];//修改他的父亲的值
pos /= 2;//指向父亲节点
}
}
public int sumRange(int i, int j) {
int sum = 0;
int l = n + i;//初始化第一个叶子
int r = n + j;//初始化第二个叶子
while(r >= l){
if(l % 2 == 1){
sum += tree[l];
l++;
}
if(r % 2 == 0){
sum += tree[r];
r--;
}
l /= 2;//向上移动一层
r /= 2;//向上移动一层
}
return sum;
}
}
分析
提交结果
方法二
方法三
方法四
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
public int maxProfit(int[] prices) {
int days = prices.length;
if(days==0||days == 1)return 0;
int[][] dp = new int[days][3];
dp[0][0] = -prices[0];
for(int i = 1;i<days;i++){
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][2]-prices[i]);
dp[i][1] = dp[i-1][0] + prices[i];
dp[i][2] = Math.max(dp[i-1][2],dp[i-1][1]);
}
return Math.max(dp[days-1][0],Math.max(dp[days-1][1],dp[days-1][2]));
}
分析
dp[i] [j]表示第i天结束后的状态j下的最大利润。
状态分为三种
状态转移方程如下
dp[i] [0]表示第i天结束后持有股票 持有股票可以分为两种情况 第一种是第i天没有买入股票 保持的是第i-1天买入的股票;第二种是第i-1天不持有股票且处于冷冻期,第i天买入股票。所以可以列出如下的动态转移方程
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][2]-prices[i]);
dp[i] [1]表示第i天结束后不持有股票且处于冷冻期。表示i-1天持有股票,第i天卖出,所以可以列出如下的动态转移方程
dp[i][1] = dp[i-1][0] + prices[i];
dp[i] [2]表示第i天结束后不持有股票且不处于冷冻期。分为两种情况。第一种 第i-1天不持有股票处于冷冻期;第二种第i-1天不持有股票且不处于冷冻期。所以可以列出如下的动态转移方程
dp[i][2] = Math.max(dp[i-1][2],dp[i-1][1]);
最后返回最后一天结束后 3种状态中的最大值。
对于一个具有树特征的无向图,我们可选择任何一个节点作为根。图因此可以成为树,在所有可能的树中,具有最小高度的树被称为最小高度树。给出这样的一个图,写出一个函数找到所有的最小高度树并返回他们的根节点。
格式
该图包含 n 个节点,标记为 0 到 n - 1。给定数字 n 和一个无向边 edges 列表(每一个边都是一对标签)。
你可以假设没有重复的边会出现在 edges 中。由于所有的边都是无向边, [0, 1]和 [1, 0] 是相同的,因此不会同时出现在 edges 里。
说明:
根据树的定义,树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
树的高度是指根节点和叶子节点之间最长向下路径上边的数量。
解答
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
List<Integer> res = new ArrayList<>();
if (n == 1) {
res.add(0);
return res;
}
int[] degree = new int[n];
List<List<Integer>> map = new ArrayList<>();
for (int i = 0; i < n; i++) {
map.add(new ArrayList<>());
}
for (int[] edge : edges) {
degree[edge[0]]++;
degree[edge[1]]++;
map.get(edge[0]).add(edge[1]);
map.get(edge[1]).add(edge[0]);
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (degree[i] == 1) queue.offer(i);
}
while (!queue.isEmpty()) {
res = new ArrayList<>();
int size = queue.size();
for (int i = 0; i < size; i++) {
int cur = queue.poll();
res.add(cur);
List<Integer> neighbors = map.get(cur);
for (int neighbor : neighbors) {
degree[neighbor]--;
if (degree[neighbor] == 1) {
queue.offer(neighbor);
}
}
}
}
return res;
}
分析
有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。如果你戳破气球 i ,就可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。
求所能获得硬币的最大数量。
public int maxCoins(int[] nums) {
int n = nums.length;
int[] points = new int[n + 2];
points[0] = points[n + 1] = 1;
for (int i = 1; i <= n; i++) {
points[i] = nums[i - 1];
}
int[][] dp = new int[n + 2][n + 2];
for (int i = n; i >= 0; i--) {
for (int j = i + 2; j < n + 2; j++) {
for (int k = i + 1; k < j; k++) {
dp[i][j] = Math.max(
dp[i][j],
dp[i][k] + dp[k][j] + points[i]*points[j]*points[k]
);
}
}
}
return dp[0][n + 1];
}
分析
编写一段程序来查找第 n 个超级丑数。
超级丑数是指其所有质因数都是长度为 k 的质数列表 primes 中的正整数。
解答
public int nthSuperUglyNumber(int n, int[] primes) {
int[] dp = new int[n];
dp[0] = 1;
int[] indexs = new int[primes.length];
for(int i = 0;i< n - 1;i++){
int min = Integer.MAX_VALUE;
for(int j = 0;j<primes.length;j++){
if(min > primes[j] * dp[indexs[j]])
min = primes[j] * dp[indexs[j]];
}
for (int j = 0; j < primes.length; j++) {
if(min == primes[j] * dp[indexs[j]])
indexs[j]++;
}
dp[i+1] = min;
}
return dp[n-1];
}
分析
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
public List<Integer> countSmaller(int[] nums) {
TreeSet<Integer> set = new TreeSet<>();
for (int i = 0; i < nums.length; i++) {
set.add(nums[i]);
}
int[] numbers = new int[set.size() + 1];
int index = 1;
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : set) {
map.put(num, index++);
}
LinkedList<Integer> res = new LinkedList<>();
for (int i = nums.length - 1; i >= 0; i--) {
Integer ind = map.get(nums[i]);
res.addFirst(numbers[ind - 1]);
for (int j = ind; j < numbers.length; j++) {
numbers[j]++;
}
}
return res;
}
//方法二
public List<Integer> countSmaller(int[] nums) {
List<Integer> result = new ArrayList<>();
int len = nums.length;
if (len == 0) {
return result;
}
int[] indexes = new int[len];
int[] temp = new int[len];
int[] res = new int[len];
for (int i = 0; i < len; i++) {
indexes[i] = i;
}
mergeAndCountSmaller(nums, 0, len - 1, indexes, temp, res);
for (int i = 0; i < len; i++) {
result.add(res[i]);
}
return result;
}
// 归并排序
private void mergeAndCountSmaller(int[] nums, int left, int right, int[] indexes, int[] temp, int[] res) {
if (left == right) {
return;
}
int mid = left + (right - left) / 2;
//拆分
mergeAndCountSmaller(nums, left, mid, indexes, temp, res);
mergeAndCountSmaller(nums, mid + 1, right, indexes, temp, res);
// 归并排序的优化。如果索引数组有序,则不存在逆序关系,没有必要合并。
if (nums[indexes[mid]] <= nums[indexes[mid + 1]]) {
return;
}
//合并
mergeOfTwoSortedArrAndCountSmaller(nums, left, mid, right, indexes, temp, res);
}
private void mergeOfTwoSortedArrAndCountSmaller(int[] nums, int left, int mid, int right, int[] indexes, int[] temp, int[] res) {
for (int i = left; i <= right; i++) {
temp[i] = indexes[i];//记录下索引改变前的状态
}
int i = left;
int j = mid + 1;
for (int k = left; k <= right; k++) {
if (i > mid) {
//前一部分数组以归并 第二个数组直接接在后面即可。
indexes[k] = temp[j];
j++;
} else if (j > right) {
//后一个部分的数组以归并,第一个数组接在后面即可。此时要计算逆序对
indexes[k] = temp[i];
i++;
res[indexes[k]] += (right - mid);
} else if (nums[temp[i]] <= nums[temp[j]]) {
// 前一数组中的小于第二个数组中的 小的归并 计算逆序对
indexes[k] = temp[i];
i++;
res[indexes[k]] += (j - mid - 1);
}else {
// 后面的值大
indexes[k] = temp[j];
j++;
}
}
}
分析
例如 nums数组中有这几个数字 然后从小到大的排列。
从后往前遍历。数字是3 那么3 对应的位置极其之后的桶都加1。
这个位置之后有多少个比它小的数 就看这个数字的桶的前一个桶的数字是多少即可。
以此类推
方法二
归并排序+索引数组来寻找逆序对。
在合并的时候 计算逆序对。
给你一个仅包含小写字母的字符串,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
解答
public static String removeDuplicateLetters(String s) {
HashMap<Character, Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
map.put(s.charAt(i), map.getOrDefault(s.charAt(i), 0) + 1);
}
LinkedList<Character> linkedList = new LinkedList<>();
Set<Character> set = new HashSet<>();
for (int i = 0; i < s.length(); i++) {
int size = linkedList.size();
char current = s.charAt(i);
if (!set.contains(current)) {
while (size > 0) {
char number = linkedList.getLast();
if (current < number && map.get(number) > 0) {
linkedList.removeLast();
set.remove(number);
size--;
} else break;
}
linkedList.addLast(current);
set.add(current);
}
map.put(current, map.get(current) - 1);
}
StringBuilder stringBuilder = new StringBuilder();
while (linkedList.size() > 0)
stringBuilder.append(linkedList.pop());
return stringBuilder.toString();
}
分析
提交结果
给定一个字符串数组 words,找到 length(word[i]) * length(word[j]) 的最大值,并且这两个单词不含有公共字母。你可以认为每个单词只包含小写字母。如果不存在这样的两个单词,返回 0。
解答
public int maxProduct(String[] strings) {
int n = strings.length;
int[] masks = new int[n];
int[] lens = new int[n];
int bitmask = 0;
for (int i = 0; i < n; i++) {
bitmask = 0;
for (char ch : strings[i].toCharArray()) {
bitmask |= 1 << bitNumber(ch);
}
masks[i] = bitmask;
lens[i] = strings[i].length();
}
int max = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if ((masks[i] & masks[j]) == 0)
max = Math.max(max, lens[i] * lens[j]);
}
}
return max;
}
public int bitNumber(char ch) {
return (int) ch - (int) 'a';
}
分析
初始时有 n 个灯泡关闭。 第 1 轮,你打开所有的灯泡。 第 2 轮,每两个灯泡你关闭一次。 第 3 轮,每三个灯泡切换一次开关(如果关闭则开启,如果开启则关闭)。第 i 轮,每 i 个灯泡切换一次开关。 对于第 n 轮,你只切换最后一个灯泡的开关。 找出 n 轮后有多少个亮着的灯泡。
解答
//超时
public int bulbSwitch(int n) {
int[] dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
dp[i] = 1;
}
int res = 0;
for (int i = 2; i <= n / 2; i++) {
int j = n / i;
for (int k = 1; k <= j && k * i <= n; k++) {
dp[k * i] = ~dp[k * i];
if (k == 1 && dp[k * i] == 1) res++;
}
}
for (int i = n / 2 + 1; i <= n; i++) {
dp[i] = ~dp[i];
if (dp[i] == 1) res++;
}
return res + 1;
}
public int bulbSwitch(int n) {
return (int)Math.sqrt(n);
}
分析