LeetCode Weekly Contest 175 解题报告

卖大米 2020-2-13 735


No.1 Check If N and Its Double Exist

解题思路

该题目比较简单, 遍历一次整个数组, 使用 Set 记录已经出现过的数字, 在遍历到一个新的元素 num 时, 检查 Set 中是否存在 num * 2 或者 num / 2

[注意一个细节]: 当 num 是奇数的时候, 不能检查 num / 2

代码展示


class Solution {
   public boolean checkIfExist(int[] arr) {
       Set<Integer> set = new HashSet<>();
       for (int num : arr) {
           if (set.contains(num * 2) || (num % 2 == 0 && set.contains(num / 2))) {
               return true;
           }
           set.add(num);
       }
       return false;
   }
}

No.2 Minimum Number of Steps to Make Two Strings Anagram

解题思路

通过修改使得两个 String 包含的每一种字符的数量都相等, 所以我们只需要知道两个字符串的差别有多少个字符即可. 使用 Map 统计出 String 中包含的每一种字符的数量, 然后计算差异.

比如对于 s = "leetcode", t = "practice"

那么 s 中每种字符的数量是: {'l': 1, 'e': 3, 't': 1, 'c': 1, 'o': 1, 'd': 1}

而 t 中每种字符的数量是: {'p': 1, 'r': 1, 'a': 1, 'c': 2, 't': 1, 'i': 1, 'e': 1}

s 中比 t 多字符有 'l', 'e', 'o', 'd', 分别多 1, 2, 1, 1 共 5 个字符, 那么我们只需要把这 5 个字符改成 t 中对应的字符即可.

s 比 t 多的字符共有 5 个, 反过来 t 比 s 多的字符也一定有 5 个.

代码展示


class Solution {
   public int minSteps(String s, String t) {
       Map<Character, Integer> map = new HashMap<>();
       // 统计出 s 中每种字符的数量
       for (char c : s.toCharArray()) {
           map.put(c, 1 + map.getOrDefault(c, 0));
       }
       // 计算 t 和 s 的差异
       int diff = 0;
       for (char c : t.toCharArray()) {
           int cnt = map.getOrDefault(c, 0);
           // 如果 s 中有字符 c, 那么先减去; 反之 diff++, 代表 t 比 s 多一个字符 c
           if (cnt > 0) {
               map.put(c, cnt - 1);    
           } else {
               diff++;
           }
       }
       return diff;
   }
}

No.3 Tweet Counts Per Frequency

解题思路

首先根据样例输入注意到 recordTweet() 的调用顺序与 time 无关.

我们使用 Map<String, List> 来维护数据. key 为 tweetName, value 为该用户发过推文的时间点列表.

在记录时, 只需要向 List 中 add 一个时间点即可; 在查询时, 遍历该用户对应的 List 即可.

在查询时, 出于便利, 先使用数组 int[] 记录每个时间段的推文数, 最后再转换成 List. 数组的初值全是 0, 数组的长度为时间段的数量, 即向上取整的除法: (endTime - startTime + 1) / duration (duration 表示时间段长度, 取值为 {60, 3600, 86400}). 在遍历时, 如果时间点 time 在 [startTime, endTime] 范围内, 那么通过计算 (time - startTime) / duration 得到它属于哪个时间段, 更新数组内对应的元素.

代码展示


class TweetCounts {

   private Map<String, List<Integer>> records;

   public TweetCounts() {
       records = new HashMap<>();
   }

   public void recordTweet(String tweetName, int time) {
       if (!records.containsKey(tweetName)) {
           records.put(tweetName, new ArrayList<>());
       }
       records.get(tweetName).add(time);
   }

   public List<Integer> getTweetCountsPerFrequency(String freq, String tweetName, int startTime, int endTime) {
       if (!records.containsKey(tweetName)) {
           return new ArrayList<>();
       }
       int duration = "minute".equals(freq) ? 60 : ("hour".equals(freq) ? 3600 : 86400);
       int[] res = new int[(endTime - startTime + duration) / duration];
       for (int t : records.get(tweetName)) {
           if (startTime <= t && t <= endTime) {
               res[(t - startTime) / duration]++;
           }
       }
       return Arrays.stream(res).boxed().collect(Collectors.toList());
   }
}

No.4 Maximum Students TakingExam

解题思路

这是一道比较难的动态规划问题, 需要进行状态压缩.

普通的按照座位定义状态是想不通的, 所以我们按照行来定义状态: 设 dp[i][j] 表示第 i 行的座位按照 j 坐人的情况下, 前 i 行最多能坐多少人. 注意 j 是一个集合, 表示在哪些位置坐人.

这样我们就很容易得到状态转移方程: dp[i][j] = max{ dp[i - 1][k] + size(j) }, 其中 k, j 需要满足一些条件 (size(j) 表示 j 集合中坐了多少人):

k, j 指定的坐人的位置都必须有好的座位, 也就是 .

k, j 这相邻的两列人之间无法作弊, 也就是说 j 的位置都无法偷窥到 k

k, j 这两列本身也无法作弊, 即它们本身不能安排相邻的座位 (其实这样的 dp[i][j] 可以被认为是非法状态)

初始化 dp[0][0] = 0, 而其它均为负无穷, 最终答案是 max{ dp[n][i] }, 即最后一排的所有坐人情况中取最大值.

明白了动态规划思路之后, 还有一个很大的问题, 就是怎么高效地表示 (i, j), 用 Map 套 Set 或是自定义类型不太现实. 我们注意到 n, m <= 8, 那么其实我们可以使用一个 8 位二进制数来表示 j, 也就是开数组最大只会访问到 dp[8][255], 占用内存很少.

然而, 既然我们使用二进制数来表示 j, 那么运算过程需要很多位运算, 具体运算以及含义见代码及注释.

代码展示


class Solution {
   public int maxStudents(char[][] seats) {
       int n = seats.length;
       int m = seats[0].length;
       int[] bit = new int[1 << m];     // bit[i] 表示 i 的二进制有多少个 1
       int[] bitseat = new int[n + 1];  // bitseat[i] 表示第 i 行的座位的二进制表示
       int[][] dp = new int[n + 1][1 << m]; // j 的范围是 0 ~ 2^m - 1, 数组开 1 << m 即可

       // 预处理 bit 数组, t & -t 得到的是 t 的二进制最低位的 1
       for (int i = 1; i < (1 << m); i++) {
           for (int t = i; t > 0; t -= t & -t) {
               bit[i]++;
           }
       }
       // 预处理 bitseat 数组(1-indexed)
       for (int i = 0; i < n; i++) {
           for (int j = 0; j < m; j++) {
               if (seats[i][j] == '.') {
                   bitseat[i + 1] |= (1 << j);
               }
           }
       }
       // 给 dp 数组赋初始值
       for (int i = 0; i <= n; i++) {
           Arrays.fill(dp[i], -0x3f3f3f3f);
       }
       dp[0][0] = 0;

       // 动态规划计算
       int res = 0;
       for (int i = 1; i <= n; i++) {
           for (int j = 0; j < (1 << m); j++) {
               // 前半部分判断保证 j 指定的坐人的位置都必须有好的座位
               // 后半部分判断保证 j 指定的坐人位置没有相邻的
               if ((j | bitseat[i]) != bitseat[i] || (j & (j << 1)) > 0) {
                   continue;
               }
               for (int k = 0; k < (1 << m); k++) {
                   // 这个判断保证 j 和 k 没有斜向上的冲突
                   if (((k << 1) & j) == 0 && ((k >> 1) & j) == 0) {
                       dp[i][j] = Math.max(dp[i][j], dp[i - 1][k] + bit[j]);
                   }
               }
               res = Math.max(res, dp[i][j]);
           }
       }
       return res;
   }
}

最新回复 (1)
返回