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.
*/
posted @ 2023-05-27 23:58  编程爱好者-java  阅读(25)  评论(0)    收藏  举报