目录
- 1351. 统计有序矩阵中的负数
- 题目链接
- 标签
- 简答二分查找
- 思路
- 代码
- 优化
- 思路
- 代码
- 1. 两数之和
- 题目链接
- 标签
- 思路
- 代码
- 208. 实现 Trie (前缀树)
- 题目链接
- 标签
- 思路
- 代码
1351. 统计有序矩阵中的负数
题目链接
1351. 统计有序矩阵中的负数
标签
数组 二分查找 矩阵
简答二分查找
思路
由于矩阵每行都是按 降序 排列的,所以可以针对每行都使用二分查找来查找最后一个不是负数的元素的索引,然后给 总数 加上 本行元素个数 减去 这个索引 再 减一,不过要注意的一点是,最后一个不是负数的元素可能是重复的,所以不能在找到最后一个不是负数的元素后就退出查找,而应该继续到 右子区间 查找,直到找到 最右边的 不是最后一个负数的元素。
代码
class Solution {public int countNegatives(int[][] grid) {int m = grid[0].length; // m是矩阵的列数int left, right;int cnt = 0; // 统计负数的个数for (int[] row : grid) { // 针对矩阵的每行进行操作// 每次都把查找区间限制在 [0, m - 1] 之间left = 0;right = m - 1;while (left <= right) {int mid = left + (right - left >> 1);if (0 > row[mid]) { // 如果 row[mid] < 0right = mid - 1; // 则到左子区间查询(降序数组)} else if (0 < row[mid]) { // 如果 row[mid] > 0left = mid + 1; // 则到右子区间查询(降序数组)} else { // 如果 row[mid] == 0left = mid + 1; // 则到右子区间查询(降序数组)}}cnt += m - right - 1; // 统计本行的负数个数}return cnt;}
}
优化
思路
像上面的多个二分查找之间没有任何联系,每次都是在 [ 0 , m − 1 ] [0, m - 1] [0,m−1] 这个大区间进行查询,这样就浪费了这个矩阵的另一个特性:每列也是按降序排列的。
那么该如何利用这个特性呢?很简单,有这个特性再加上每行都是降序排列的,就意味着 在上一行中最后一个0出现的位置 一定比 本行中最后一个0出现的位置 要更靠右或相同,所以二分查找本行时不需要将右端点重置为 m - 1
,继承上一次的右端点即可。
代码
class Solution {public int countNegatives(int[][] grid) {int m = grid[0].length;int left, right = m - 1;int cnt = 0;for (int[] row : grid) {left = 0;while (left <= right) {int mid = left + (right - left >> 1);if (0 > row[mid]) {right = mid - 1;} else if (0 < row[mid]) {left = mid + 1;} else {left = mid + 1;}}cnt += m - right - 1;}return cnt;}
}
1. 两数之和
题目链接
1. 两数之和
标签
数组 哈希表
思路
本题需要返回元素对应的索引,而不是只返回真假值,所以可以使用 Map
来存储元素的值和索引的键值对,key
为元素的值,value
为元素的索引。每遍历到一个元素就在 Map
中查找是否有 目标元素 减去 当前元素 的元素,如果有,则返回当前元素和这个元素的索引组成的数组;否则就把当前元素的值和索引存储到 Map
中,遍历下一个元素。
代码
class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer, Integer> map = new HashMap<>();for (int i = 0; i < nums.length; i++) {int curr = nums[i]; // 当前遍历到的元素int need = target - curr; // 需要的元素,满足 当前元素 + 需要元素 = 目标元素if (map.containsKey(need)) {return new int[]{i, map.get(need)};}map.put(curr, i);}return null;}
}
208. 实现 Trie (前缀树)
题目链接
208. 实现 Trie (前缀树)
标签
设计 字典树 哈希表 字符串
思路
本题和 二叉树 有些类似,不过本题是以 小写字符 作为区分子节点的标识,故应该叫 二十六叉树,每个节点都有26个指针,分别指向从 'a'
到 'z'
的所有字符的节点。
可以把字符串想像成一串字符序列,而这个字符序列就是二十六叉树的 一条路径,比如对于字符串word = "apple"
,它的路径就是a -> p -> p -> l -> e
,按照路径把这个字符串添加到各层节点上。这就是添加方法 void insert(String word)
。
在查找时,可以查找这个字符串的字符序列对应的路径是否存在与这个二十六叉树中,即按照这个路径遍历,如果有一个节点不存在,则这个字符串不存在;否则就代表字符串一定存在吗?不一定,例如只添加了一个字符串"apple"
,而现在要查询"app"
是否存在。
那么就不能只保存路径,应该还要保存字符串的结尾字符,也就是保存这个字符是否是字符串的结尾,这样在查找时,就可以通过是否是字符串的结尾来判断这个单词是否存在了。这就是查询方法boolean search(String word)
。
此外,还要实现boolean startsWith(String prefix)
方法。这个方法就是 没添加是否是字符串的结尾的标记前 的查找方法,只要存在这条路径就满足条件,返回true
;否则就返回false
。
代码
class Trie {private static class Node {private static final int SIZE = 26; // 字符数Node[] nexts = new Node[SIZE]; // 存储下一字母的指针数组boolean isEnd; // 记录本节点是否是单词的结尾}private Node root; // 前缀树的根节点public Trie() {root = new Node();}public void insert(String word) {char[] str = word.toCharArray();Node curr = root;for (char c : str){int idx = c - 'a'; // 获取在指针数组中的索引if (curr.nexts[idx] == null) { // 如果这个位置没有字符节点curr.nexts[idx] = new Node(); // 则新建一个字符节点}curr = curr.nexts[idx]; // 往下一个字符节点移动}curr.isEnd = true; // 保存该字符是字符串的最后一个字符}public boolean search(String word) {char[] str = word.toCharArray();Node curr = root;for (char c : str) {int idx = c - 'a'; // 获取在指针数组中的索引if (curr.nexts[idx] == null) { // 如果这个位置没有字符节点return false; // 则说明字符串不存在该树中,返回false}curr = curr.nexts[idx]; // 往下一个字符节点移动}return curr.isEnd; // 返回这个字符是否是字符串的结尾}public boolean startsWith(String prefix) {char[] str = prefix.toCharArray();Node curr = root;for (char c : str) {int idx = c - 'a'; // 获取在指针数组中的索引if (curr.nexts[idx] == null) { // 如果这个位置没有字符节点return false; // 则说明该树中的字符串不以 prefix 为前缀,返回false}curr = curr.nexts[idx]; // 往下一个字符节点移动}return true; // 该树中一定有字符串以prefix为前缀,返回true}
}