LeetCode每日一题(22年1月27日-2月5日)

2047. 句子中的有效单词数

句子仅由小写字母(‘a’ 到 ‘z’)、数字(‘0’ 到 ‘9’)、连字符(’-’)、标点符号(’!’、’.’ 和 ‘,’)以及空格(’ ')组成。每个句子可以根据空格分解成 一个或者多个 token ,这些 token 之间由一个或者多个空格 ’ ’ 分隔。
如果一个 token 同时满足下述条件,则认为这个 token 是一个有效单词:

  • 仅由小写字母、连字符和/或标点(不含数字)。
  • 至多一个 连字符 ‘-’ 。如果存在,连字符两侧应当都存在小写字母(“a-b” 是一个有效单词,但 “-ab” 和 “ab-” 不是有效单词)。
  • 至多一个 标点符号。如果存在,标点符号应当位于 token 的 末尾 。

给你一个字符串 sentence ,请你找出并返回 sentence 中 有效单词的数目 。

示例:

输入:sentence = "he bought 2 pencils, 3 erasers, and 1  pencil-sharpener."
输出:6
解释:句子中的有效单词是"he""bought""pencils,""erasers,""and""pencil-sharpener."

解题思路:根据条件进行单词识别即可。统计单词数量

public int countValidWords(String sentence) {
	// 将单词按空格分开
    String[] tokens = sentence.split(" ");
    int ans = 0;
    // f为连接符 ‘-’的标记
    boolean f;
    // 循环将每个字符串进行单词识别
    for(String token: tokens){
        char[] chars = token.toCharArray();
        f = false;
        // 循环识别每个字符
        for(int i = 0;i < chars.length;i++){
        	// 出现数字直接break
            if(chars[i] >= '0' && chars[i] <= '9'){
                break;
            }else if(chars[i] == '-'){
            	// 出现两个连接符break
                if(f == true){
                    break;
                }else{
                	// 连接符 两边不为字母,break
                    if(i-1 >= 0 && ((i+1 < chars.length) && 
                    				(chars[i+1]>='a' && chars[i+1]<='z'))){
                    	// f记录出现连接符
                        f = true;
                    }else{
                        break;
                    }
                }
            }else if((chars[i] == '!' || chars[i] == '.' || chars[i] == ',')&& 
            		  i != chars.length - 1){
            	// 出现符号,不为最后一个字符,break
                break;
            }
            // 到了最后一个字符,该字符串为合格的单词
            if(i == chars.length - 1){
                ans++;
        	}
        }
    }
    return  ans;
}

1996. 游戏中弱角色的数量

你正在参加一个多角色游戏,每个角色都有两个主要属性:攻击 和 防御 。给你一个二维整数数组 properties ,其中 properties[i] = [attacki, defensei] 表示游戏中第 i 个角色的属性。

如果存在一个其他角色的攻击和防御等级都严格高于该角色的攻击和防御等级,则认为该角色为弱角色 。更正式地,如果认为角色 i 弱于 存在的另一个角色 j ,那么 attackj > attacki 且 defensej > defensei 。
返回弱角色的数量。

方法一:按照某一个属性排序,使用单调栈进行统计。

public int numberOfWeakCharacters(int[][] properties) {
	//按照属性0进行排序,从小到大,如果属性0相等,则按属性1进行排序,从大到小
    Arrays.sort(properties,(o1,o2)->o1[0]==o2[0]?(o2[1]-o1[1]):(o1[0]-o2[0]));
    int ans = 0;
    Stack<Integer> stack = new Stack<>();
    for(int i = 0;i < properties.length;i++){
    	// 当前角色的属性1比之前的角色的属性1大,之前的角色为弱角色,出栈,并统计
        while(!stack.isEmpty() && stack.peek() < properties[i][1]){
            stack.pop();
            ans++;
        }
        // 当前角色入栈
        stack.push(properties[i][1]);
    }
    return ans;
}

方法二:按照某一个属性排序,遍历统计数量。

public int numberOfWeakCharacters(int[][] properties) {
	// 属性0从大到小排序,如果属性0相同,属性1从小到大排序
    Arrays.sort(properties,(o1,o2)->o1[0]==o2[0]?(o1[1]-o2[1]):(o2[0]-o1[0]));
    int max = 0;
    int ans = 0;
    for(int i = 0;i < properties.length;i++){
    	// max为之前的最大属性1的值
        if(max > properties[i][1]){
        	// 如果比max小证明该角色为弱角色。
            ans++;
        }else{
        	// 更新max值
            max = properties[i][1];
        }
    }
    return ans;
}

1765. 地图中的最高点

给你一个大小为 m x n 的整数矩阵 isWater ,它代表了一个由陆地和水域单元格组成的地图。

  • 如果 isWater[i][j] == 0 ,格子 (i, j) 是一个陆地格子。
  • 如果 isWater[i][j] == 1 ,格子 (i, j) 是一个水域格子。

你需要按照如下规则给每个单元格安排高度:

  • 每个格子的高度都必须是非负的。
  • 如果一个格子是是 水域 ,那么它的高度必须为 0 。
  • 任意相邻的格子高度差 至多 为 1 。当两个格子在正东、南、西、北方向上相互紧挨着,就称它们为相邻的格子。(也就是说它们有一条公共边)

找到一种安排高度的方案,使得矩阵中的最高高度值最大。

请你返回一个大小为 m x n 的整数矩阵 height ,其中 height[i][j] 是格子 (i, j) 的高度。如果有多种解法,请返回任意一个 。

示例:
请添加图片描述

输入:isWater = [[0,0,1],[1,0,0],[0,0,0]]
输出:[[1,1,0],[0,1,1],[1,2,2]]
解释:所有安排方案中,最高可行高度为 2 。
任意安排方案中,只要最高高度为 2 且符合上述规则的,都为可行方案。

提示:

  • m == isWater.length
  • n == isWater[i].length
  • 1 <= m, n <= 1000
  • isWater[i][j] 要么是 0 ,要么是 1 。
  • 至少有 1 个水域格子。

解题思路:BFS,从水域的点出发,向四周扩散至整个矩阵。在给定的整数矩阵上进行高度修改。

public int[][] highestPeak(int[][] isWater) {
    int row = isWater.length;
    int col = isWater[0].length;
    // 标记是否访问过该点
    boolean[][] vised = new boolean[row][col];
    // 队列
    Queue<int[]> queue = new ArrayDeque<>();
    // 将所有的水域节点入队列
    for(int i = 0;i < row;i++){
        for(int j = 0;j < col;j++){
            if(isWater[i][j] == 1){
                // 原地修改,水域节点高度为0
                isWater[i][j] = 0;
                // 将节点坐标加入队列
                queue.add(new int[]{i,j});
                // 访问标志设为true
                vised[i][j] = true;
            }
        }
    }
    // 扩散位置数组
    int[][] dir = new int[][]{{0,1},{0,-1},{1,0},{-1,0}};
    while(!queue.isEmpty()){
        // 从队列中的所有节点向外扩散
        int size = queue.size();
        for(int i = 0;i < size;i++){
            int[] tmp = queue.poll();
            // 向四个方向扩散
            for(int di = 0;di < 4;di++){
                // 判断是否越界
                if(tmp[0]+dir[di][0] < 0 || tmp[0]+dir[di][0] >= row ||
                   tmp[1]+dir[di][1] < 0 || tmp[1]+dir[di][1] >= col){
                    continue;
                }
                // 判断是否访问过
                if(vised[tmp[0] + dir[di][0]][tmp[1] + dir[di][1]] == true){
                    continue;
                }
                vised[tmp[0] + dir[di][0]][tmp[1] + dir[di][1]] = true;
                // 设置高度
                isWater[tmp[0] + dir[di][0]][tmp[1] + dir[di][1]] = 
                									isWater[tmp[0]][tmp[1]] + 1;
                // 加入队列
                queue.add(new int[]{tmp[0] + dir[di][0],tmp[1] + dir[di][1]});
            }
        }
    }
    return isWater;
}

884. 两句话中的不常见单词

句子是一串由空格分隔的单词。每个单词仅由小写字母组成。
如果某个单词在其中一个句子中恰好出现一次,在另一个句子中却 没有出现 ,那么这个单词就是 不常见的 。
给你两个句子s1和s2,返回所有不常用单词的列表。返回列表中单词可以按任意顺序组织。

示例:

输入:s1 = "this apple is sweet", s2 = "this apple is sour"
输出:["sweet","sour"]

解题思路:题目要求找出在 s1与s2中出现一次的单词,将s1与s2合并,根据空格划分单词,用哈希表统计单词出现次数,次数为1的,为要找的单词,返回即可。

public String[] uncommonFromSentences(String s1, String s2) {
    String s = s1 +" "+ s2;
    HashMap<String,Integer> hashMap = new HashMap<>();
    // 根据空格划分单词
    String[] strings = s.split(" ");
    // 哈希表统计单词出现的次数
    for(int i = 0;i<strings.length;i++){
        hashMap.put(strings[i],hashMap.getOrDefault(strings[i],0)+1);
    }
    Set<Map.Entry<String, Integer>> entries = hashMap.entrySet();
    List<String> ans = new ArrayList<>();
    for(Map.Entry<String,Integer> entry:entries){
    	// 将次数为1的单词,加入结果集合
        if(entry.getValue() == 1){
            ans.add(entry.getKey());
        }
    }
    // 返回结果
    return ans.toArray(new String[ans.size()]);
}

1342. 将数字变成 0 的操作次数

给你一个非负整数 num ,请你返回将它变成 0 所需要的步数。 如果当前数字是偶数,你需要把它除以 2 ;否则,减去 1 。

示例:

输入:num = 14
输出:6
解释:
步骤 1) 14 是偶数,除以 2 得到 7 。
步骤 27 是奇数,减 1 得到 6 。
步骤 36 是偶数,除以 2 得到 3 。
步骤 43 是奇数,减 1 得到 2 。
步骤 52 是偶数,除以 2 得到 1 。
步骤 61 是奇数,减 1 得到 0

解题思路:模拟过程,步骤计数。

public int numberOfSteps(int num) {
    int ans = 0;
    while(num != 0){
        ans++;
        if(num % 2 == 0){
            num = num/2;
        }else{
            num--;
        }
    }
    return ans;
}

1763. 最长的美好子字符串

当一个字符串 s 包含的每一种字母的大写和小写形式 同时 出现在 s 中,就称这个字符串 s 是美好字符串。比方说,“abABB” 是美好字符串,因为 ‘A’ 和 ‘a’ 同时出现了,且 ‘B’ 和 ‘b’ 也同时出现了。然而,“abA” 不是美好字符串因为 ‘b’ 出现了,而 ‘B’ 没有出现。

给你一个字符串 s ,请你返回 s 最长的美好子字符串 。如果有多个答案,请你返回最早出现的一个。如果不存在美好子字符串,请你返回一个空字符串。
示例 :

输入:s = "YazaAay"
输出:"aAa"
解释:"aAa"是一个美好字符串,因为这个子串中仅含一种字母,其小写形式'a'和
	  大写形式'A'也同时出现了。"aAa"是最长的美好子字符串。

提示:

  • 1 <= s.length <= 100
  • s 只包含大写和小写英文字母。

解题思路:分治,在没有同时出现的小写字母和大写字母的字母中,分治处理,直到找到美好字符串,并记录字符串的起始位置和长度,找到最长的一个美好子字符串。

class Solution {
	// 记录最长字符串的位置和长度
    private int maxPos;
    private int maxLen; 
    public String longestNiceSubstring(String s) {
        this.maxPos = 0;
        this.maxLen = 0;
        // 分治处理
        dfs(s, 0, s.length() - 1);
        return s.substring(maxPos, maxPos + maxLen);
    }
    public void dfs(String s, int start, int end) {
        if(start >= end){
            return;
        }
        // 记录出现的大写字母和小写字母
        int lower = 0, upper = 0;
        for(int i = start; i <= end; ++i){
            if(s.charAt(i) - 'a' >= 0){
            	// 位运算,使用第i为记录出现过第i个字母,第i位为1,出现了第i位字母。
                lower |= 1 << (s.charAt(i) - 'a');
            }else{
           		// 记录出现过的大写字母
                upper |= 1 << (s.charAt(i) - 'A');
            }
        }
        // 如果相等,证明大写字母和小写字母同时出现,为最美字符串
        if(lower == upper){
            if(end - start + 1 > maxLen){
                maxPos = start;
                maxLen = end - start + 1;
            }
            return;
        }
        // 记录出现了大写和小写的字母
        int valid = lower & upper;
        int pos = start;
        while(pos <= end){
        	// 记录字符串的起始位置
            start = pos;
            // 找到没有大写小写同时出现的字母的位置,做分治
            while(pos <= end && (valid & (1 << Character.toLowerCase(s.charAt(pos)) - 'a')) != 0){
                ++pos;
            }
            // pos为没有同时出现大写小写字母的位置,以pos做分治处理
            dfs(s, start, pos - 1);
            ++pos;
        }
    }
}

2000. 反转单词前缀

给你一个下标从 0 开始的字符串 word 和一个字符 ch 。找出 ch 第一次出现的下标 i ,反转 word 中从下标 0 开始、直到下标 i 结束(含下标 i )的那段字符。如果 word 中不存在字符 ch ,则无需进行任何操作。

示例 :

输入:word = "abcdefd", ch = "d"
输出:"dcbaefd"
解释:"d" 第一次出现在下标 3 。 
反转从下标 0 到下标 3(含下标 3)的这段字符,结果字符串是 "dcbaefd"

解题思路:找到字符位置,直接反转。

public String reversePrefix(String word, char ch) {     
    int index = word.indexOf(ch);
    if(index >= 0){
        int i = 0;
        char[] chars = word.toCharArray();
        while(i<index){
            char t = chars[i];
            chars[i] = chars[index];
            chars[index] = t;
            i++;
            index--;
        }
        word = new String(chars);
    }
    return word;
}

1414. 和为 K 的最少斐波那契数字数目

给你数字 k ,请你返回和为 k 的斐波那契数字的最少数目,其中,每个斐波那契数字都可以被使用多次。
斐波那契数字定义为:

  • F1 = 1
  • F2 = 1
  • Fn = Fn-1 + Fn-2 , 其中 n > 2 。

数据保证对于给定的k,一定能找到可行解。

示例:

输入:k = 7
输出:2 
解释:斐波那契数字为:11235813,……
	 对于 k = 7 ,我们可以得到 2 + 5 = 7

提示:

  • 1 <= k <= 109

解题思路:贪心思路,k 每次减去最大的斐波那契数字,直到 k 为0为止,记录下次数。

public int findMinFibonacciNumbers(int k) {
    int ans = 0;
    int a = 1,b = 1,c = 2;
    // 找到自身最大的斐波那契数列中的最大数字
    while(c <= k){
        a = b;
        b = c;
        c = a + b;
    }
    // 贪心思想
    while(k != 0){
        c = b;
        b = a;
        a = c - b;
        // 每次减去最大的数字
        if(k >= c){
            k = k - c;
            ans++;
        }
    }
    return ans;
}

1725. 可以形成最大正方形的矩形数目

给你一个数组 rectangles ,其中 rectangles[i] = [li, wi] 表示第 i 个矩形的长度为 li 、宽度为 wi 。
如果存在 k 同时满足 k <= li 和 k <= wi ,就可以将第 i 个矩形切成边长为 k 的正方形。例如,矩形 [4,6] 可以切成边长最大为 4 的正方形。

设 maxLen 为可以从矩形数组 rectangles 切分得到的最大正方形的边长。
请你统计有多少个矩形能够切出边长为 maxLen 的正方形,并返回矩形数目 。

示例:

输入:rectangles = [[5,8],[3,9],[5,12],[16,5]]
输出:3
解释:能从每个矩形中切出的最大正方形边长分别是[5,3,5,5]。
最大正方形的边长为5,可以由3个矩形切分得到。

解题思路:一次遍历,找到最大的正方形和对应的次数,返回最大正方形的次数。

public int countGoodRectangles(int[][] rectangles) {
    int maxLen = 0;
    int ans = 0;
    for(int[] rectangle:rectangles){
        int min = Math.min(rectangle[0],rectangle[1]);
        if(maxLen == min){
            ans++;
        }else if(maxLen < min){
            maxLen = min;
            ans = 1;
        }
    }
    return ans;
}

1219. 黄金矿工

你要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 m * n 的网格 grid 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量;如果该单元格是空的,那么就是 0。

为了使收益最大化,矿工需要按以下规则来开采黄金:

  • 每当矿工进入一个单元,就会收集该单元格中的所有黄金。
  • 矿工每次可以从当前位置向上下左右四个方向走。
  • 每个单元格只能被开采(进入)一次。
  • 不得开采(进入)黄金数目为 0 的单元格。
  • 矿工可以从网格中 任意一个 有黄金的单元格出发或者是停止。

示例:

输入:grid = [[0,6,0],[5,8,7],[0,9,0]]
输出:24
解释:
[[0,6,0],
 [5,8,7],
 [0,9,0]]
一种收集最多黄金的路线是:9 -> 8 -> 7

解题思路:回溯,以每个不为零的单元格为起点,向四周做深度优先遍历,并将单元格置零,求出这条路径的值与历史最大值比较,将所有的路径搜索一遍,就能求出挖得最大的黄金数量。

class Solution {
	// 保存最大的黄金数量
    int maxGold = 0;
    public int getMaximumGold(int[][] grid) {
        int row,col;
        row = grid.length;
        col = grid[0].length;
        for(int i = 0; i < row; i++){
            for(int j = 0;j < col; j++){
            	// 以每个不为零的单元格为起点向四周遍历,路径起始数量为0.
                if(grid[i][j] != 0){
                    bfs(grid,i,j,0);
                }
            }
        }
        return maxGold;
    }
    int[][] dir = new int[][]{{0,1},{0,-1},{1,0},{-1,0}};
    public void bfs(int[][] grid,int i,int j,int gold){
    	// 保存当前数量
        int currGold = grid[i][j];
        // 置零
        grid[i][j] = 0;
        for(int di = 0;di<4;di++){
            int newi,newj;
            newi = dir[di][0]+i;
            newj = dir[di][1]+j;
            // 不满足条件,不进行扩散
            if(newi < 0 || newi >= grid.length ||
               newj < 0 || newj >= grid[0].length ||
                    grid[newi][newj] == 0){
                continue;
            }
            // 继续扩散
            bfs(grid,newi,newj,gold + currGold);
        }
        // 还原回去,以便下一次遍历
        grid[i][j] = currGold;
        // 更新最大值
        maxGold = Math.max(maxGold,gold + currGold);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值