LeetCode每日一题(22年1月7日-1月16日)

目录(每日一题)

  • 1614. 括号的最大嵌套深度
  • 89. 格雷编码
  • 1629. 按键持续时间最长的键
  • 306. 累加数
  • 1036. 逃离大迷宫
  • 334. 递增的三元子序列
  • 747. 至少是其他数字两倍的最大数
  • 373. 查找和最小的K对数字
  • 1716. 计算力扣银行的钱
  • 382. 链表随机节点

1614. 括号的最大嵌套深度

如果字符串满足以下条件之一,则可以称之为 有效括号字符串(valid parentheses string,可以简写为 VPS):

  • 字符串是一个空字符串 “”,或者是一个不为 “(” 或 “)” 的单字符。
  • 字符串可以写为 AB(A 与 B 字符串连接),其中 A 和 B 都是 有效括号字符串 。
  • 字符串可以写为 (A),其中 A 是一个 有效括号字符串 。

类似地,可以定义任何有效括号字符串 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;
}

89. 格雷编码

n 位格雷码序列 是一个由 2n 个整数组成的序列,其中:

  • 每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1)
  • 第一个整数是 0
  • 一个整数在序列中出现不超过一次
  • 每对相邻整数的二进制表示恰好一位不同
  • 第一个和最后一个整数的二进制表示恰好一位不同

给你一个整数 n ,返回任一有效的 n 位格雷码序列 。

示例:

输入:n = 2
输出:[0,1,3,2]
解释:
[0,1,3,2] 的二进制表示是 [00,01,11,10]- 0001 有一位不同
- 0111 有一位不同
- 1110 有一位不同
- 1000 有一位不同
[0,2,3,1] 也是一个有效的格雷码序列,其二进制表示是 [00,10,11,01]- 0010 有一位不同
- 1011 有一位不同
- 1101 有一位不同
- 0100 有一位不同

方法一:根据格雷码的定义求解
第 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;
}

1629. 按键持续时间最长的键

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; 
}

306. 累加数

累加数是一个字符串,组成它的数字可以形成累加序列。
一个有效的累加序列必须至少包含 3 个数。除了最开始的两个数以外,字符串中的其他数都等于它之前两个数相加的和。
给你一个只包含数字 ‘0’-‘9’ 的字符串,编写一个算法来判断给定输入是否是 累加数 。如果是,返回 true ;否则,返回 false 。
说明:累加序列里的数 不会 以 0 开头,所以不会出现 1, 2, 03 或者 1, 02, 3 的情况。

示例:

输入:"199100199"
输出:true 
解释:累加序列为: 1, 99, 100, 1991 + 99 = 100, 99 + 100 = 199

提示:

  • 1 <= num.length <= 35
  • num 仅由数字(0 - 9)组成

解题思路: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;
}

1036. 逃离大迷宫

在一个 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
解释:
从源方格无法到达目标方格,因为我们无法在网格中移动。
无法向北或者向东移动是因为方格禁止通行。
无法向南或者向西移动是因为不能走出网格。

提示:

  • 0 <= blocked.length <= 200
  • blocked[i].length == 2
  • 0 <= xi, yi < 106
  • source.length == target.length == 2
  • 0 <= sx, sy, tx, ty < 106
  • source != target
  • 题目数据保证 source 和 target 不在封锁列表内

解题思路:使用 BFS,从 source点和target点同时向外搜索。对于不联通的情况,是blocked 中的点将一个点包围起来,另一个点在包围圈外面。blocked 中的点最大为200个,能包围的最大点数为 200*(200-1)/2个。
如图:借助两个边,包围面积达到最大值。
LeetCode每日一题(22年1月7日-1月16日)_第1张图片
所以在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;
}

334. 递增的三元子序列

给你一个整数数组 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

提示:

  • 1 <= nums.length <= 5 * 105
  • -231 <= nums[i] <= 231 - 1

进阶:你能实现时间复杂度为 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;
}

747. 至少是其他数字两倍的最大数

给你一个整数数组 nums ,其中总是存在 唯一的 一个最大整数 。

请你找出数组中的最大元素并检查它是否 至少是数组中每个其他数字的两倍 。如果是,则返回 最大元素的下标 ,否则返回 -1 。

示例:

输入:nums = [3,6,1,0]
输出:1
解释:6 是最大的整数,对于数组中的其他整数,6 大于数组中其他元素的两倍。
	 6 的下标是 1 ,所以返回 1

提示:

  • 1 <= nums.length <= 50
  • 0 <= nums[i] <= 100
  • nums 中的最大元素是唯一的

解题思路:遍历,找到最大的数和次大的数。然后判断最大的数,是不是次大的数的二倍。

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;
}

373. 查找和最小的K对数字

给定两个以升序排列的整数数组 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]

提示:

  • 1 <= nums1.length, nums2.length <= 104
  • -109 <= nums1[i], nums2[i] <= 109
  • nums1, nums2 均为升序排列
  • 1 <= k <= 1000

这个题跟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;
}

1716. 计算力扣银行的钱

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;
}

382. 链表随机节点

给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。

实现 Solution 类:

  • Solution(ListNode head) 使用整数数组初始化对象。
  • int getRandom() 从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等。

示例:
请添加图片描述

输入
["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;
    }
}

你可能感兴趣的:(LeetCode刷题,leetcode,算法,职场和发展)