如果字符串满足以下条件之一,则可以称之为 有效括号字符串(valid parentheses string,可以简写为 VPS):
类似地,可以定义任何有效括号字符串 S 的 嵌套深度 depth(S):
depth("") = 0
depth© = 0,其中 C 是单个字符的字符串,且该字符不是 “(” 或者 “)”
depth(A + B) = max(depth(A), depth(B)),其中 A 和 B 都是 有效括号字符串
depth("(" + A + “)”) = 1 + depth(A),其中 A 是一个 有效括号字符串
例如:""、"()()"、"()(()())" 都是 有效括号字符串(嵌套深度分别为 0、1、2),而 “)(” 、"(()" 都不是 有效括号字符串 。
给你一个 有效括号字符串 s,返回该字符串的 s 嵌套深度 。
public int maxDepth(String s) {
// dep保存当前嵌套的数量
int dep = 0;
// 保存最大括号嵌套深度
int maxDep = 0;
char[] chars = s.toCharArray();
for(int i = 0;i<chars.length;i++){
if(chars[i] == '('){
//当前深度++
dep++;
// 如果大于最大深度,则更新最大深度
if(maxDep < dep){
maxDep = dep;
}
}else if(chars[i] == ')'){
// 遇到右括号,当前深度--
dep--;
}
}
return maxDep;
}
n 位格雷码序列 是一个由 2n 个整数组成的序列,其中:
给你一个整数 n ,返回任一有效的 n 位格雷码序列 。
示例:
输入:n = 2
输出:[0,1,3,2]
解释:
[0,1,3,2] 的二进制表示是 [00,01,11,10] 。
- 00 和 01 有一位不同
- 01 和 11 有一位不同
- 11 和 10 有一位不同
- 10 和 00 有一位不同
[0,2,3,1] 也是一个有效的格雷码序列,其二进制表示是 [00,10,11,01] 。
- 00 和 10 有一位不同
- 10 和 11 有一位不同
- 11 和 01 有一位不同
- 01 和 00 有一位不同
方法一:根据格雷码的定义求解
第 i 位的格雷码为 i >> 1 ^ i
如第五位: 101 >> 1 ^ 101 = 010 ^ 101 = 111
public List<Integer> grayCode(int n) {
List<Integer> ret = new ArrayList<>();
for (int i = 0; i < 1 << n; i++) {
ret.add((i >> 1) ^ i);
}
return ret;
}
方法二:镜像求解,最高位 | 1
例:n = 2
格雷码为 0 1 3 2,二进制为 00,01,11,10
分为两部分 00,01和 11,10。将第二部分反转变成 10,11观察与第一部分区别,只要在第一部分最高为 | 1即可得到。
public List<Integer> grayCode(int n) {
List<Integer> res = new ArrayList<>();
res.add(0);
res.add(1);
for(int i = 1;i < n;i++){
for(int j = res.size() - 1;j >= 0;j--){
res.add(res.get(j)| 1 << i);
}
}
return res;
}
LeetCode 设计了一款新式键盘,正在测试其可用性。测试人员将会点击一系列键(总计 n 个),每次一个。
给你一个长度为 n 的字符串 keysPressed ,其中 keysPressed[i] 表示测试序列中第 i 个被按下的键。releaseTimes 是一个升序排列的列表,其中 releaseTimes[i] 表示松开第 i 个键的时间。字符串和数组的 下标都从 0 开始 。第 0 个键在时间为 0 时被按下,接下来每个键都 恰好 在前一个键松开时被按下。
测试人员想要找出按键持续时间最长的键。第 i 次按键的持续时间为releaseTimes[i]-releaseTimes[i - 1] ,第 0 次按键的持续时间为 releaseTimes[0] 。
注意,测试期间,同一个键可以在不同时刻被多次按下,而每次的持续时间都可能不同。
请返回按键 持续时间最长 的键,如果有多个这样的键,则返回 按字母顺序排列最大 的那个键。
(注意,单次按下持续时间最长的键)
示例:
输入:releaseTimes = [9,29,49,50], keysPressed = "cbcd"
输出:"c"
解释:按键顺序和持续时间如下:
按下 'c' ,持续时间 9(时间 0 按下,时间 9 松开)
按下 'b' ,持续时间 29 - 9 = 20(松开上一个键的时间 9 按下,时间 29 松开)
按下 'c' ,持续时间 49 - 29 = 20(松开上一个键的时间 29 按下,时间 49 松开)
按下 'd' ,持续时间 50 - 49 = 1(松开上一个键的时间 49 按下,时间 50 松开)
按键持续时间最长的键是 'b' 和 'c'(第二次按下时),持续时间都是 20
'c' 按字母顺序排列比 'b' 大,所以答案是 'c'
public char slowestKey(int[] releaseTimes, String keysPressed) {
// 记录最大持续时间
int maxTime = -1;
char c = 'a';
int pre = 0;
// 记录单次持续时间
int releaseTime;
for(int i = 0; i < releaseTimes.length;i++) {
releaseTime = releaseTimes[i] - pre;
pre = releaseTimes[i];
// 如果单次持续按键时间大于历史最大时间
// 或等于历史最大时间,如果字母序列大于持续最大时间的字母按键,则进行更新
if (releaseTime > maxTime || (releaseTime == maxTime && keysPressed.charAt(i) > c)) {
maxTime = releaseTime;
c = keysPressed.charAt(i);
}
}
return c;
}
累加数是一个字符串,组成它的数字可以形成累加序列。
一个有效的累加序列必须至少包含 3 个数。除了最开始的两个数以外,字符串中的其他数都等于它之前两个数相加的和。
给你一个只包含数字 ‘0’-‘9’ 的字符串,编写一个算法来判断给定输入是否是 累加数 。如果是,返回 true ;否则,返回 false 。
说明:累加序列里的数 不会 以 0 开头,所以不会出现 1, 2, 03 或者 1, 02, 3 的情况。
示例:
输入:"199100199"
输出:true
解释:累加序列为: 1, 99, 100, 199。1 + 99 = 100, 99 + 100 = 199
提示:
解题思路:dfs
public boolean isAdditiveNumber(String num) {
long first,second;
// 遍历第一个数
for(int i = 0;i < num.length() - 1;i++){
// 不能为前导0的数
if(num.charAt(0) == '0' && i > 0){
return false;
}
first = Long.parseLong(num.substring(0, i + 1));
// 遍历第二个数
for(int j = i + 1; j < num.length() - 1;j++){
// 不能为前导0的数
if(num.charAt(i + 1) == '0' && j > i + 1){
break;
}
second = Long.parseLong(num.substring(i + 1, j + 1));
// 判断当前第一个和第二个是满足累加序列
if(dfs(num,first,second,j + 1)) {
return true;
}
}
}
return false;
}
public boolean dfs(String nums,long first,long second,int index){
// 走到头,满足累加序列,返回true
if(index == nums.length()){
return true;
}
// 遍历第三个数
for(int i = index;i < nums.length();i++){
// 不能为前导0的数
if(nums.charAt(index) == '0' && i > index){
return false;
}
long thirdly = Long.parseLong(nums.substring(index,i + 1));
// 第三个数大于 第一个数与第二个数之和,不能再往后遍历,进行剪枝
if(thirdly > first + second){
// 剪枝
return false;
}
// 第三个数等于第一个数与第二个数之和,继续dfs
if(thirdly == first + second){
if(dfs(nums,second,thirdly,i + 1)){
return true;
}else{
return false;
}
}
}
return false;
}
在一个 106 x 106 的网格中,每个网格上方格的坐标为 (x, y) 。
现在从源方格 source = [sx, sy] 开始出发,意图赶往目标方格 target = [tx, ty] 。数组 blocked 是封锁的方格列表,其中每个 blocked[i] = [xi, yi] 表示坐标为 (xi, yi) 的方格是禁止通行的。
每次移动,都可以走到网格中在四个方向上相邻的方格,只要该方格不在给出的封锁列表 blocked 上。同时,不允许走出网格。
只有在可以通过一系列的移动从源方格 source 到达目标方格 target 时才返回 true。否则,返回 false。
示例:
输入:blocked = [[0,1],[1,0]], source = [0,0], target = [0,2]
输出:false
解释:
从源方格无法到达目标方格,因为我们无法在网格中移动。
无法向北或者向东移动是因为方格禁止通行。
无法向南或者向西移动是因为不能走出网格。
提示:
解题思路:使用 BFS,从 source点和target点同时向外搜索。对于不联通的情况,是blocked 中的点将一个点包围起来,另一个点在包围圈外面。blocked 中的点最大为200个,能包围的最大点数为 200*(200-1)/2个。
如图:借助两个边,包围面积达到最大值。
所以在BFS时进行优化,只要从 source开始能访问的点的个数比最大包围的点数大,就说明source点没有被包围,同理 target也是。只要两个点都没有被包围,则可以联通。
public boolean isEscapePossible(int[][] blocked, int[] source, int[] target) {
// 记录不能走的点
HashSet<Long> blockedSet = new HashSet<>();
// 分别记录source访问过的位置,target访问过的位置。
HashSet<Long> sourceSet = new HashSet<>();
HashSet<Long> targetSet = new HashSet<>();
// 使用 BFS用到的两个队列
Queue<Long> sourceQueue = new ArrayDeque<>();
Queue<Long> targetQueue = new ArrayDeque<>();
// 200个障碍最大的包围的面积
int MAXSIZE = 200*(200-1)/2;
// 边界值
Long BORDER = 1000000L;
// 位置
int[][] dir = new int[][]{{0,1},{0,-1},{1,0},{-1,0}};
for(int i = 0; i<blocked.length;i++){
blockedSet.add(blocked[i][0] * BORDER + blocked[i][1]);
}
sourceQueue.add(source[0] * BORDER + source[1]);
targetQueue.add(target[0] * BORDER + target[1]);
sourceSet.add(source[0] * BORDER + source[1]);
targetSet.add(target[0] * BORDER + target[1]);
while(!sourceQueue.isEmpty() && !targetQueue.isEmpty()){
// 从 source和target出发同时BFS
Long sourceVis = sourceQueue.poll();
Long targetVis = targetQueue.poll();
Long sx = sourceVis / BORDER;
Long sy = sourceVis % BORDER;
Long tx = targetVis / BORDER;
Long ty = targetVis % BORDER;
for(int di = 0;di < 4;di++){
Long newsx = sx + dir[di][0];
Long newsy = sy + dir[di][1];
Long newtx = tx + dir[di][0];
Long newty = ty + dir[di][1];
// 判断位置是否合法,且没有被访问过或被封锁
if(newsx < 0 || newsx >= BORDER || newsy < 0 || newsy >= BORDER ||
sourceSet.contains(newsx*BORDER+newsy) ||
blockedSet.contains(newsx*BORDER+newsy)){
}else {
// 判断该位置target是否已经访问过,若访问过,说明联通
if(targetSet.contains(newsx*BORDER+newsy)){
return true;
}
sourceQueue.add(newsx*BORDER+newsy);
sourceSet.add(newsx*BORDER+newsy);
}
// 判断位置是否合法,且没有被访问过或被封锁
if(newtx < 0 || newtx >= BORDER || newty < 0 || newty >= BORDER ||
targetSet.contains(newtx*BORDER+newty) ||
blockedSet.contains(newtx*BORDER+newty)){
}else {
// 判断该位置source是否已经访问过,若访问过,说明联通
if(sourceSet.contains(newtx*BORDER+newty)){
return true;
}
targetQueue.add(newtx*BORDER+newty);
targetSet.add(newtx*BORDER+newty);
}
}
// 判断是否都没有被包围,是则联通
if(sourceSet.size() > MAXSIZE && targetSet.size() > MAXSIZE){
return true;
}
}
return false;
}
给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。
如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。
示例:
输入:nums = [2,1,5,0,4,6]
输出:true
解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6
提示:
进阶:你能实现时间复杂度为 O(n) ,空间复杂度为 O(1) 的解决方案吗?
解题思路:记录当前序列中最小的数和最大的数,如果出现比最大的数还大的数,则找到递增的三元子序列,返回true。
public boolean increasingTriplet(int[] nums) {
int len = nums.length;
int min = Integer.MAX_VALUE,mid = Integer.MAX_VALUE;
for(int i = 0;i < len;i++){
if(nums[i] <= min){
min = nums[i];
}else if(nums[i] < mid){
mid = nums[i];
}else if(nums[i] > mid){
return true;
}
}
return false;
}
给你一个整数数组 nums ,其中总是存在 唯一的 一个最大整数 。
请你找出数组中的最大元素并检查它是否 至少是数组中每个其他数字的两倍 。如果是,则返回 最大元素的下标 ,否则返回 -1 。
示例:
输入:nums = [3,6,1,0]
输出:1
解释:6 是最大的整数,对于数组中的其他整数,6 大于数组中其他元素的两倍。
6 的下标是 1 ,所以返回 1 。
提示:
解题思路:遍历,找到最大的数和次大的数。然后判断最大的数,是不是次大的数的二倍。
public int dominantIndex(int[] nums) {
int len = nums.length;
if(len == 1){
return 0;
}
// 初始化最大数和次大数的下标
int first,second;
if(nums[0] > nums[1]){
first = 0;
second = 1;
}else{
first = 1;
second = 0;
}
// 遍历
for(int i = 2; i < len;i++){
if(nums[i] > nums[first]){
second = first;
first = i;
} else if(nums[i] > nums[second]){
second = i;
}
}
// 判断结果并返回
return nums[first] >= nums[second] * 2 ? first : -1;
}
给定两个以升序排列的整数数组 nums1 和 nums2 , 以及一个整数 k 。
定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。
请找到和最小的 k 个数对 (u1,v1), (u2,v2) … (uk,vk) 。
示例:
输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
输出: [1,2],[1,4],[1,6]
解释: 返回序列中的前 3 对数:
[1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]
提示:
这个题跟378. 有序矩阵中第 K 小的元素解法一样。所有的nums1中的元素和nums2中的元素,两两相加,组成了一个二维矩阵。nums1的长度为 n,nums2的长度为 m。那么矩阵的大小为 n*m。
如 示例中的nums1和nums2组成的结果矩阵为:
matrix[n][m] = 2 4 6 // 横为nums2中的所有元素,nums1中的所有元素
1 [ 3 6 9
4 5 8 11
7 7 10 13 ]
从结果矩阵中取出前k个最小的元素。
解题思路:使用优先队列存储结果矩阵值组成的两个下标,多路归并,归并到第k个最小的值时停止。
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
int n = nums1.length;
int m = nums2.length;
// 使用优先队列对nums1+nums2组成的值进行排序
PriorityQueue<int[]> priorityQueue = new PriorityQueue<>(k,Comparator.comparingInt(a -> (nums1[a[0]] + nums2[a[1]])));
// 将每一行看成一个数组,对多个数组进行归并,现将所有的数组的头元素入队列
for(int i = 0;i < Math.min(n,k);i++){
priorityQueue.add(new int[]{i,0});
}
List<List<Integer>> ans = new ArrayList<>();
List<Integer> tmp;
// 多路归并
for(int i = 0; i < k && !priorityQueue.isEmpty();i++){
// 取出队列中最小的元素
int[] t = priorityQueue.poll();
tmp = new ArrayList<>();
tmp.add(nums1[t[0]]);
tmp.add(nums2[t[1]]);
// 加入结果集
ans.add(tmp);
// 如果数组还有下一个元素,继续入队列
if(t[1] + 1 < m){
priorityQueue.add(new int[]{t[0],t[1] + 1});
}
}
// 返回结果
return ans;
}
Hercy 想要为购买第一辆车存钱。他 每天 都往力扣银行里存钱。
最开始,他在周一的时候存入 1 块钱。从周二到周日,他每天都比前一天多存入 1 块钱。在接下来每一个周一,他都会比 前一个周一 多存入 1 块钱。
给你 n ,请你返回在第 n 天结束的时候他在力扣银行总共存了多少块钱。
示例:
输入:n = 20
输出:96
解释:第 20 天后,总额为 (1 + 2 + 3 + 4 + 5 + 6 + 7) + (2 + 3 + 4 + 5 + 6 + 7 + 8) +
(3 + 4 + 5 + 6 + 7 + 8) = 96 。
解题思路:模拟法。
public int totalMoney(int n) {
// 多少周
int a = n / 7;
// 剩余天数
int b = n % 7;
// 先算一共有多少周,第一周为 28,以后每周+7
int ans = 0;
for (int i = 0; i < a; i++) {
ans += 28 + i * 7;
}
// 再算剩余的天数
for (int i = 0; i < b; i++) {
ans += ++a;
}
return ans;
}
给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。
实现 Solution 类:
输入
["Solution", "getRandom", "getRandom", "getRandom", "getRandom", "getRandom"]
[[[1, 2, 3]], [], [], [], [], []]
输出
[null, 1, 3, 2, 2, 3]
解释
Solution solution = new Solution([1, 2, 3]);
solution.getRandom(); // 返回 1
solution.getRandom(); // 返回 3
solution.getRandom(); // 返回 2
solution.getRandom(); // 返回 2
solution.getRandom(); // 返回 3
// getRandom() 方法应随机返回 1、2、3中的一个,每个元素被返回的概率相等。
方法一:在类中使用数组将链表中的数据记录下来,在获得随机元素时候,将数组中的对应下标元素返回即可。
class Solution {
List<Integer> list = new ArrayList<>();
Random random = new Random();
public Solution(ListNode head) {
while(head != null){
list.add(head.val);
head = head.next;
}
}
public int getRandom() {
return list.get(random.nextInt(list.size()));
}
}
方法二:水塘抽样
从链表头开始,遍历整个链表,对遍历到的第 i 个节点,随机选择区间 [0,i) 内的一个整数,如果其等于 0,则将答案置为该节点值,否则答案不变。
该算法会保证每个节点的值成为最后被返回的值的概率均为 1/n。
每次只保留一个数,当遇到第 i 个数时,以 1/i的概率保留它,(i-1)/i的概率保留原来的数。
举例说明: 1 - 10
遇到1,概率为1,保留第一个数。
遇到2,概率为1/2,这个时候,1和2各1/2的概率被保留
遇到3,3被保留的概率为1/3,(之前剩下的数假设1被保留),2/3的概率 1 被保留,(此时1被保留的总概率为 2/3 * 1/2 = 1/3)
遇到4,4被保留的概率为1/4,(之前剩下的数假设1被保留),3/4的概率 1 被保留,(此时1被保留的总概率为 3/4 * 2/3 * 1/2 = 1/4)
以此类推,每个数被保留的概率都是1/N。
class Solution {
ListNode head;
Random random;
public Solution(ListNode head) {
this.head = head;
random = new Random();
}
public int getRandom() {
int i = 1;
int result = 0;
for(ListNode node = head; node != null;node = node.next){
if(random.nextInt(i) == 0){
result = node.val;
}
i++;
}
return result;
}
}