本次题目
-
-
- 337 打家劫舍 III
- 338 比特位计数
- 347 前 K 个高频元素
- 394 字符串解码
- 399 除法求值
- 406 根据身高重建队列
- 416 分割等和子集
- 437 路径总和 III
- 438 找到字符串中所有字母异位词
- 448 找到所有数组中消失的数字
337 打家劫舍 III
- 动态规划,之前做过,后序遍历,递归时计算当前节点的最大偷窃金额,偷当前节点则不能偷左右孩子,偷左右孩子则不能偷当前节点
class Solution {
public int rob(TreeNode root) {
int[] res = dfs(root);
return Math.max(res[0], res[1]);
}
private int[] dfs(TreeNode root){
int[] res = new int[2];
if(root == null) return res;
int[] left = dfs(root.left);
int[] right = dfs(root.right);
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
res[1] = root.val + left[0] + right[0];
return res;
}
}
338 比特位计数
Brian Kernighan
算法,参考JZ15 二进制中1的个数,使用n&(n-1)
class Solution {
public int[] countBits(int n) {
int [] res = new int[n + 1];
for(int i = 0; i <= n; i++){
res[i] = count(i);
}
return res;
}
private int count(int n){
int res = 0;
while(n != 0){
res++;
n &= (n - 1);
}
return res;
}
}
- 动态规划,x通过右移一位去掉最低位得到y,若x为偶数,则最低位为0,1比特位和y相同;若x为奇数,则最低位为1,1比特位比y多1。递推:
bits[x]=bits[x>>1]+(x & 1)
class Solution {
public int[] countBits(int n) {
int [] res = new int[n + 1];
for(int i = 0; i <= n; i++){
res[i] = res[i >> 1] + (i & 1);
}
return res;
}
}
347 前 K 个高频元素
- 优先级队列(前k大用小顶堆,前k小用大顶堆),之前做过,先统计数组中的数出现频率,然后使用小顶堆,复习:手动实现优先级队列
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
int[] res = new int[k];
for(int num : nums){
if(!map.containsKey(num)){
map.put(num, 0);
}
map.put(num, map.get(num) + 1);
}
PriorityQueue<Map.Entry<Integer, Integer>> priorityQueue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
if(priorityQueue.size() < k){
priorityQueue.offer(entry);
}else{
if(priorityQueue.peek().getValue() < entry.getValue()){
priorityQueue.poll();
priorityQueue.offer(entry);
}
}
}
for(int i = 0; i < k; i++){
res[i] = priorityQueue.poll().getKey();
}
return res;
}
}
394 字符串解码
- 考虑到嵌套问题使用栈,类似150 逆波兰表达式求值,定义两个栈,一个保存数字,一个保存字符串,遍历字符串,当为数字时计算其值(可能有两位数),当为字母时拼接字符串之后,当为左括号时将当前字符串和数字入栈,当为右括号时,数字和字符串出栈,当前字符串重复后拼接到字符串(上一个字符串)之后。
- 注意:出栈时的字符串为上一字符串,当前未入栈的字符串和出栈的数字对应。
class Solution {
public String decodeString(String s) {
Deque<String> stackS = new LinkedList<>();
Deque<Integer> stackN = new LinkedList<>();
int num = 0;
StringBuilder sb = new StringBuilder();
for(char c : s.toCharArray()){
if(c >= '0' && c <= '9'){
num = 10 * num + (c - '0');
}else if(c == '['){
stackS.push(sb.toString());
stackN.push(num);
sb = new StringBuilder();
num = 0;
}else if(c == ']'){
int cycleNum = stackN.pop();
String preString = stackS.pop();
StringBuilder temp = new StringBuilder();
for(int i = 0; i < cycleNum; i++){
temp.append(sb);
}
sb = new StringBuilder(preString + temp);
}else{
sb.append(c);
}
}
return sb.toString();
}
}
399 除法求值
- 图论问题,广度优先搜索,遍历输入数组,使用哈希表将点映射为正整数标号(key:点对应字符串,value:正整数递增标号),定义边数组存储每个点到其连接的点的标号及权重(下标位置为起点标号)。然后遍历查询数组,从哈希表中查询点对应标号,若不存在标号,则为-1;若存在标号,则再根据标号到边数组中查询,一一对比得到查询两点的比值。
- 时间复杂度O(
(|输入方程长度|+|查询方程长度|)*|方查询次数| + 输入方程长度 * 查询方程长度
),空间复杂度O(|方程中不同字符的个数|
)
class Solution {
public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
Map<String, Integer> nodeMap = new HashMap<>();
int count = 0;
for(List<String> equ : equations){
if(!nodeMap.containsKey(equ.get(0))){
nodeMap.put(equ.get(0), count++);
}
if(!nodeMap.containsKey(equ.get(1))){
nodeMap.put(equ.get(1), count++);
}
}
List<Pair>[] edgeList = new List[count];
for(int i = 0; i < count; i++){
edgeList[i] = new ArrayList<Pair>();
}
for(int i = 0; i < equations.size(); i++){
int src = nodeMap.get(equations.get(i).get(0));
int dst = nodeMap.get(equations.get(i).get(1));
edgeList[src].add(new Pair(dst, values[i]));
edgeList[dst].add(new Pair(src, 1.0 / values[i]));
}
double[] result = new double[queries.size()];
Arrays.fill(result, -1.0);
for(int i = 0; i < queries.size(); i++){
List<String> que = queries.get(i);
double v = -1.0;
if(nodeMap.containsKey(que.get(0)) && nodeMap.containsKey(que.get(1))){
int src = nodeMap.get(que.get(0));
int dst = nodeMap.get(que.get(1));
Queue<Integer> queue = new LinkedList<>();
queue.offer(src);
double[] target = new double[count];
Arrays.fill(target, -1.0);
target[src] = 1.0;
while(!queue.isEmpty() && target[dst] < 0){
int cur = queue.poll();
for(Pair pair : edgeList[cur]){
int next = pair.dst;
double val = pair.value;
if(target[next] < 0){
target[next] = target[cur] * val;
queue.offer(next);
}
}
}
result[i] = target[dst];
}
}
return result;
}
class Pair {
int dst;
double value;
Pair(int dst, double value){
this.dst = dst;
this.value = value;
}
}
}
- floyd算法*
- 带权并查集*
- 时间复杂度O(
(|输入方程长度|+|查询方程长度|)*log|方程中不同字符的个数|
),空间复杂度O(|方程中不同字符的个数|
)
406 根据身高重建队列
- 贪心,之前做过,使用list,身高从高到底排列,身高相同的前面人数更少的优先,然后按顺序插入队列中,插入时根据前面人数进行插入即可(队列中的都是比当前高的)
class Solution {
public int[][] reconstructQueue(int[][] people) {
ArrayList<int[]> res = new ArrayList<>();
Arrays.sort(people, (o1, o2) -> {
if(o1[0] == o2[0]) return o1[1] - o2[1];
return o2[0] - o1[0];
});
for(int[] p : people){
res.add(p[1], p);
}
return res.toArray(new int[people.length][]);
}
}
416 分割等和子集
- 动态规划,01背包,之前做过,相当于target为数组总和的一半,物品和能否达到target。定义dp数组
dp[i]
表示容量为i的背包放入数之和的最大值(数的重量=其值)。注意:一维滚动数组优化先物品后背包(大到小)
- 时间复杂度O(n*target),空间复杂度O(target)
class Solution {
public boolean canPartition(int[] nums) {
if(nums.length == 1) return false;
int sum = 0;
for(int num : nums){
sum += num;
}
if(sum % 2 == 1) return false;
int target = sum / 2;
int[] dp = new int[target + 1];
for(int i = 0; i < nums.length; i++){
for(int j = target; j >= nums[i]; j--){
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}
437 路径总和 III
- 递归同时计算路径和,参考112 路径总和、113 路径总和II,从每个节点出发进行前序遍历
class Solution {
public int pathSum(TreeNode root, int targetSum) {
if(root == null) return 0;
int res = preOrder(root, targetSum);
res += pathSum(root.left, targetSum);
res += pathSum(root.right, targetSum);
return res;
}
private int preOrder(TreeNode root, int targetSum){
if(root == null) return 0;
int res = 0;
if(root.val == targetSum){
res++;
}
res += preOrder(root.left, targetSum - root.val);
res += preOrder(root.right, targetSum - root.val);
return res;
}
}
- 前缀和,定义hashmap保存root到当前节点的前缀和,遍历到当前节点时判断是否存在
cur - target
的前缀和,相当于以当前节点为终点,向上查找起点。补充:getOrDefault()
可以自动判断map是否存在key
class Solution {
public int pathSum(TreeNode root, int targetSum) {
if(root == null) return 0;
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
return preOrder(root, targetSum, map, 0);
}
private int preOrder(TreeNode root, int targetSum, Map<Integer, Integer> map, int cur){
if(root == null) return 0;
int res = 0;
cur += root.val;
if(map.containsKey(cur - targetSum)){
res += map.get(cur - targetSum);
}
if(!map.containsKey(cur)){
map.put(cur, 0);
}
map.put(cur, map.get(cur) + 1);
res += preOrder(root.left, targetSum, map, cur);
res += preOrder(root.right, targetSum, map, cur);
map.put(cur, map.get(cur) - 1);
return res;
}
}
438 找到字符串中所有字母异位词
- 滑动窗口,之前做过,设置滑动窗口长度与p长度相同,不断移动窗口,定义数组作为哈希表(一共26个字母)存储字符出现次数,若窗口内字符出现次数符合条件,则记录窗口左下标。
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> res = new ArrayList<>();
if(s.length() < p.length()) return res;
int[] fre = new int[26];
for(char c : p.toCharArray()){
fre[c - 'a']++;
}
for(int i = 0; i < s.length() - p.length() + 1; i++){
int[] fre2 = new int[26];
for(int j = i; j < i + p.length(); j++){
fre2[s.charAt(j) - 'a']++;
}
if(Arrays.equals(fre, fre2)){
res.add(i);
}
}
return res;
}
}
448 找到所有数组中消失的数字
- 将原数组当作哈希表,遍历一遍数组,将当前数对应位置上的数加n,第二次遍历时若某个位置上的数不大于n,则说明该数未出现过。
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> res = new ArrayList<>();
int n = nums.length;
for(int num : nums){
int i = (num - 1) % n;
nums[i] += n;
}
for(int i = 0; i < n; i++){
if(nums[i] <= n){
res.add(i + 1);
}
}
return res;
}
}