23-05-27 刷题
23-05-27 刷题
384. 打乱数组 - 力扣(LeetCode) 【mid】
-
shuffle算法的证明很重要:
n个元素的全排列有:A(n,n) = n!
例如3个元素,算法是依次枚举每个位置,对于第一个位置,可以从3个数中选,第2个位置可以从剩下的2个中选,第3个位置没得选。这样对于任意一种排列,它出现的概率是:1/3 * 1/2 * 1 = 1/6 (总共全排列个数3! = 6)
class Solution {
private int[] originNums;
private int[] currentNums;
public Solution(int[] nums) {
originNums = nums;
currentNums = Arrays.copyOf(nums, nums.length);
}
public int[] reset() {
currentNums = Arrays.copyOf(originNums, originNums.length);
return currentNums;
}
public int[] shuffle() {
int n = currentNums.length;
Random random = new Random();
for (int i = n - 1; i > 0; i--) {
int j = random.nextInt(i + 1);
swap(currentNums, i, j);
}
return currentNums;
}
void swap(int[] A, int i, int j) {
int t = A[i];
A[i] = A[j];
A[j] = t;
}
}
1375. 二进制字符串前缀一致的次数 - 力扣(LeetCode) [mid]
分析:
- 题目理解起来稍微有点复杂,但是想清楚后很简单。秒杀。
class Solution {
public int numTimesAllBlue(int[] flips) { // time: O(n). space: O(1)
int accSum = 0;
int expectedSum = 0;
int ans = 0;
for (int i = 0; i < flips.length; i++) {
accSum += flips[i];
expectedSum += i + 1;
if (accSum == expectedSum) {
ans++;
}
}
return ans;
}
}
看了答案,还有一种思路也很好,不用累加。而是看遍历到当前为止的最大值是否等于索引下标 + 1,如果等于,则说明该数左边,已经有了1~curMax这么多个数。
class Solution {
public int numTimesAllBlue(int[] flips) {
int ans = 0, curMax = 0;
for (int i = 0; i < flips.length; i++) {
curMax = Math.max(curMax, flips[i]);
if (curMax == i + 1) {
ans++;
}
}
return ans;
}
}
971. 翻转二叉树以匹配先序遍历 - 力扣(LeetCode) 【mid】
分析:
- 思路很简单,直接写的。没有提前完整想好。一遍写一遍想怎么解决。花了13min. 代码有点丑漏,但是时间上击败100%. 内存击败98%的用户。
class Solution {
public List<Integer> flipMatchVoyage(TreeNode root, int[] voyage) {
if (root.val != voyage[0]) {
return Arrays.asList(-1);
}
int n = voyage.length;
swapped = new boolean[n + 1];
curIndex = 0;
preOrder(root, null, voyage);
if (curIndex != n) return Arrays.asList(-1);
List<Integer> ans = new ArrayList<>();
for (int i = 1; i <= n; i++) {
if (swapped[i]) {
ans.add(i);
}
}
return ans;
}
boolean[] swapped;
int curIndex;
void preOrder(TreeNode root, TreeNode parent, int[] voyage) {
if (root != null) {
// visit root
if (root.val != voyage[curIndex]) {
if (swapped[parent.val]) {
return;
}
swap(parent);
preOrder(parent.left, parent, voyage);
return;
} else {
curIndex++;
}
preOrder(root.left, root, voyage);
preOrder(root.right, root, voyage);
}
}
void swap(TreeNode root) {
swapped[root.val] = true;
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
}
稍微优化了一下:
class Solution {
public List<Integer> flipMatchVoyage(TreeNode root, int[] voyage) {
if (root.val != voyage[0]) {
return Arrays.asList(-1);
}
int n = voyage.length;
swapped = new boolean[n + 1];
expected = voyage;
curIndex = 0;
preOrder(root, null);
if (curIndex != n) return Arrays.asList(-1);
List<Integer> ans = new ArrayList<>();
for (int i = 1; i <= n; i++) {
if (swapped[i]) {
ans.add(i);
}
}
return ans;
}
boolean[] swapped;
int curIndex;
int[] expected;
void preOrder(TreeNode root, TreeNode parent) {
if (root == null) return;
if (root.val == expected[curIndex]) {
curIndex++;
preOrder(root.left, root);
preOrder(root.right, root);
return;
}
if (swapped[parent.val]) {
return;
}
swap(parent);
preOrder(parent.left, parent);
}
void swap(TreeNode root) {
swapped[root.val] = true;
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
}
2597. 美丽子集的数目 - 力扣(LeetCode)【mid】
- 思路1:在枚举 78. 子集 的基础上加个判断, 在选择x=nums[i] 的时候,如果之前选过x−k 或x+k,则不能选,否则可以选。
- 疑问:这题可以有重复元素,为什么求子集时不需要特殊处理?因为题目中子集的概念是删除不同的数组下标,即使元素有重复,只要下标不同,也属于不同的子集。
class Solution {
public int beautifulSubsets(int[] A, int k) { // time: O(2^n), space: O(n)
selected = new HashSet<>();
this.k = k;
ans = 0;
dfs(A, 0);
return ans - 1; // remove empty set
}
Set<Integer> selected;
int k;
int ans;
void dfs(int[] A, int i) {
if (i == A.length) {
ans++;
return;
}
// not select
dfs(A, i + 1);
// select
if (selected.contains(A[i] - k) || selected.contains(A[i] + k)) {
return;
}
if (!selected.contains(A[i])) {
selected.add(A[i]);
dfs(A, i + 1);
selected.remove(A[i]);
} else {
dfs(A, i + 1);
}
}
}
-
思路2: DP. 看到O(2^n)复杂度 或者 n! 复杂度,都可以想一下是否可以使用DP来优化。参考英文讨论区的算法。
-
先预处理,将n个数,对k求余,按余数分组。然后每一组进行排序。
最后将这些数合并到一个数组,这样同一组的数就相邻了。
使用dfs对新数组进行处理,采用记忆化搜索(DP)
如果相邻两个数之差等于k,那么就不能选择当前的数,要跳过它。分成两段。 dfs()没看懂
class Solution {
public int beautifulSubsets(int[] nums, int k) {
Map<Integer, List<Integer>> group = new HashMap<>();
for (int x : nums) {
int idx = x % k;
group.putIfAbsent(idx, new ArrayList<>());
group.get(idx).add(x);
}
List<Integer> sorted = new ArrayList<>();
for(Map.Entry<Integer, List<Integer>> item : group.entrySet()) {
Collections.sort(item.getValue());
sorted.addAll(item.getValue());
}
int n = nums.length;
memo = new int[n][n];
return dfs(0, n - 1, sorted, k) - 1; // remove empty set
}
int[][] memo;
int dfs(int i, int prev, List<Integer> sorted, int k) {
if (i == sorted.size()) return 1;
if (memo[i][prev] == 0) {
memo[i][prev] = dfs(i + 1, prev, sorted, k); // not use sorted[i]
if (sorted.get(i) - sorted.get(prev) != k) {
memo[i][prev] += dfs(i + 1, i, sorted, k); // use sorted[i]
}
}
return memo[i][prev];
}
}
参考:非常好的一个题解:两种方法:回溯/动态规划(Python/Java/C++/Go) - 美丽子集的数目 - 力扣(LeetCode)
复习子集(无重复元素):78. 子集 - 力扣(LeetCode)
class Solution {
// time: O(2^n * n), last n for clone array; space: O(n) other than the result List
public List<List<Integer>> subsets(int[] A) {
ans = new ArrayList<>();
path = new ArrayList<>();
dfs(A, 0);
return ans;
}
List<List<Integer>> ans;
List<Integer> path;
void dfs(int[] A, int i) {
if (i == A.length) {
ans.add(new ArrayList<>(path));
return;
}
// not select
dfs(A, i + 1);
// select
path.add(A[i]);
dfs(A, i + 1);
path.remove(path.size() - 1);
}
}
/*
use recursive and regression.
for each element, there are two options: select or not.
enumerate each element, if we reach the end, then we found one subset.
after all element's options have been enumerated, we will get all answers.
*/

浙公网安备 33010602011771号