# LeetCode 3

2021.4

# 80. Remove Duplicates from Sorted Array II(删除有序数组中的重复项 II)

  • 双指针
  • 快慢指针
  • 有序数组
class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        // 数组小于等于2 直接返回 一定满足题意的重复要求
        if (n <= 2) return n;
        // 上一步排除了前2的问题,因此快慢指针从2开始
        int slow = 2, fast = 2;
        while (fast < n) {
            // slow 代表清除重复元素后的数组长度;fast 代表正在比较的数据
            // 起初,slow、fast在同一位置,只需要与slow-2比较是否相等,不相等则fast位置元素个数一定<=2;
            // 相等则右移fast继续比较,直到找到第一个与slow-2不相等的数,将其与slow交换。
            if (nums[slow-2] != nums[fast]) {
                nums[slow] = nums[fast];
                slow++;
            }
            fast++;
        }
        return slow;
    }
}

# 153. 寻找旋转排序数组中的最小值

// 如何分析二分法中条件的设计
// 循环退出条件:
// 如果只剩最后两个数(二分到最后都会变成2个数,在多个数时,也可以将其看成两个数),mid一定等于left;
// 若nums[left/mid]>nums[right],left右移+1,即left与right相遇了,跳出循环;
// 若nums[left/mid]<nums[right],right被赋值为mid,即left与right同样会相遇,跳出循环。
class Solution {
    public int findMin(int[] nums) {
        int n = nums.length;
        int l = 0, r = n-1;
        // l<r不是l<=r的原因:因为下面赋值是r=mid,所以相等时必须跳出循环
        while (l < r) {
            // 纯粹/2的方式,奇数个数就取中间,偶数个数就取中间靠左的数
            int mid = l + ((r - l) >> 1);
            // 由于/2的方式,mid只会>right或者<right,不会=right,因为mid靠近left
            if (nums[mid] > nums[r]) {
                l = mid+1;
            } else {
                r = mid;
            }
        }
        return nums[r];
    }
}

# 33. 搜索旋转排序数组

由于数组原本是升序的,截断之后,可以发现仍然是两部分保持升序。若将数组二分,其中一部分一定保持有序,则可以判断有序部分元素是否存在,若不存在则缩小二分范围,到无序区间继续二分寻找即可。

  • 二分法,部分有序。
class Solution {
    public int search(int[] nums, int t) {
        int n = nums.length;
        int start = 0, end = n-1;
        int l = 0, r = n-1;
        if (l == r && nums[l] == t) {
            return l;
        }
        while (l < r) {
            int mid = l + ((r - l) >> 1);
            if (t > nums[end]) {
                if (nums[mid] > nums[end]) {
                    if (t < nums[mid]) {
                        r = mid;
                    } else if (t > nums[mid]){
                        l = mid+1;
                    } else {
                        return mid;
                    }
                } else {
                    r = mid;
                }
            } else if (t < nums[end]) {
                if (nums[mid] > nums[end]) {
                    l = mid+1;
                } else {
                    if (t < nums[mid]) {
                        r = mid;
                    } else if (t > nums[mid]){
                        l = mid+1;
                    } else {
                        return mid;
                    }
                }
            } else {
                return end;
            }
        }
        return -1;
    }
}

分类讨论target与mid的大小,如果target与mid不在一个递增的线上,会出现什么。思路不容易清晰,现场ac率较低,具体代码如下:

class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        int l = 0, r = n-1;
        while (l <= r) {
            int mid = l+((r-l)>>1);
            if (target == nums[mid]) {
                return mid;
            } else if (target > nums[mid]) {
                if (nums[mid] <= nums[r] && target > nums[r]) {
                    r = mid-1;
                } else {
                    l = mid+1;
                }
            } else {
                if (nums[mid] >= nums[l] && target < nums[l]) {
                    l = mid+1;
                } else {
                    r = mid-1;
                }
            }
        }
        return -1;
    }
}

递归:二分之后,总有一边是有序的,一边是无序的。判断target是否在有序部分中,若不在则继续递归无序部分,若在则递归有序部分。

class Solution {
    public int search(int[] nums, int target) {
        return binary(nums, target, 0, nums.length-1);
    }

    public int binary(int[] nums, int target, int l, int r) {
        if (l > r) return -1;
        int mid = l + ((r-l)>>1);
        if (target == nums[mid]) return mid;
        if (nums[l] <= nums[mid]) {
            if (target < nums[mid] && target >= nums[l]) {
                return binary(nums, target, l, mid-1);
            } else {
                return binary(nums, target, mid+1, r);
            }
        } else {
            if (target > nums[mid] && target <= nums[r]) {
                return binary(nums, target, mid+1, r);
            } else {
                return binary(nums, target, l, mid-1);
            }
        }
    }
}

代码继续精简,但失去了可读性:

class Solution {
    public int search(int[] nums, int target) {
        return binary(nums, target, 0, nums.length-1);
    }

    public int binary(int[] nums, int target, int l, int r) {
        if (l > r) return -1;
        int mid = l + ((r-l)>>1);
        if (target == nums[mid]) return mid;
        if ((nums[l] <= nums[mid] && target < nums[mid] && target >= nums[l]) 
                || (nums[l] > nums[mid] && !(target > nums[mid] && target <= nums[r]))) {
            return binary(nums, target, l, mid-1);
        } else {
            return binary(nums, target, mid+1, r);
        }
    }
}

# 81. Search in Rotated Sorted Array II(搜索旋转排序数组 II)

class Solution {
    public boolean search(int[] nums, int t) {
        int n = nums.length;
        int start = 0, end = n-1;
        int l = 0, r = n-1;
        if (l == r && nums[l] == t) {
            return true;
        }
        while (l < r) {
            int mid = l + ((r - l) >> 1);
            // System.out.println("mid: "+mid+"; value:"+nums[mid]);
            if (nums[mid] == t) return true;
            if (nums[l] == nums[r] && nums[l] == nums[mid]) {
                l++;
                continue;
            }
            if (t > nums[end]) {
                if (nums[mid] > nums[end]) {
                    if (t < nums[mid]) {
                        r = mid;
                    } else if (t > nums[mid]){
                        l = mid+1;
                    } else {
                        return true;
                    }
                } else {
                    r = mid;
                }
            } else if (t < nums[end]) {
                if (nums[mid] > nums[end]) {
                    l = mid+1;
                } else {
                    if (t < nums[mid]) {
                        r = mid;
                    } else if (t > nums[mid]){
                        l = mid+1;
                    } else {
                        return true;
                    }
                }
            } else {
                return true;
            }
        }
        return false;
    }
}

# 154. 寻找旋转排序数组中的最小值 II

class Solution {
    public int findMin(int[] nums) {
        int n = nums.length;
        int l = 0, r = n-1;
        while (l < r) {
            int mid = l + ((r - l) >> 1);
            if (nums[l] == nums[mid] && nums[r] == nums[mid]) {
                l++;
                continue;
            }
            if (nums[mid] > nums[r]) {
                l = mid+1;
            } else {
                r = mid;
            }
        }
        return nums[r];
    }
}

# 287. 寻找重复数

// 二分法
class Solution {
    public int findDuplicate(int[] nums) {
        int n = nums.length;
        int l = 1, r = n-1;
        while (l < r) {
            int cnt = 0;
            int mid = (r + l) >> 1;
            for (int i = 0; i < n; i++) {
                if (nums[i] <= mid) {
                    cnt++;
                }
            }
            if (cnt > mid) {
                r = mid;
            } else {
                l = mid+1;
            }
        }
        return r;
    }
}

# 1143. 最长公共子序列

经典动态规划题

按照以下图示写出dp方程并画出dp table:

最长公共子序列_1

使用二维dp数组解(在空间上从图中可以看出只当前遍历行,只依赖上一行,因此空间可以优化成两个一位数组):

class Solution {
    public int longestCommonSubsequence(String t1, String t2) {
        int s1 = t1.length(), s2 = t2.length();
        int[][] dp = new int[s1+1][s2+1];
        for (int i = 1; i <= s1; i++) {
            char c1 = t1.charAt(i-1);
            for (int j = 1; j <= s2; j++) {
                char c2 = t2.charAt(j-1);
                if (c1 == c2) {
                    dp[i][j] = dp[i-1][j-1] + 1;
                } else {
                    dp[i][j] =  Math.max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }
        return dp[s1][s2];
    }
}

逻辑无调整,代码结构小优化:

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int n1 = text1.length(), n2 = text2.length();
        int[][] dp = new int[n1+1][n2+1];
        for (int i = 0; i < n1; i++) {
            for (int j = 0; j < n2; j++) {
                if (text1.charAt(i) == text2.charAt(j)) {
                    dp[i+1][j+1] = dp[i][j] + 1;
                } else {
                    dp[i+1][j+1] = Math.max(dp[i][j+1], dp[i+1][j]);
                }
            }
        }
        return dp[n1][n2];
    }
}

# 239. Sliding Window Maximum(滑动窗口最大值)

// 使用优先级队列(最大堆)
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        PriorityQueue<int[]> q = new PriorityQueue<>(new Comparator<int[]>() {
            public int compare(int[] o1, int[] o2) {
                return o1[0]!=o2[0] ? o2[0]-o1[0] : o2[1]-o1[1];
            }
        });

        for (int i = 0; i < k; i++) {
            q.offer(new int[]{nums[i], i});
        }
        int[] res = new int[nums.length - k + 1];
        res[0] = q.peek()[0];
        int l = 1, r = k;
        while (r < nums.length) {
            q.add(new int[]{nums[r], r});
            while (q.peek()[1] < l) {
                q.poll();
            }
            res[l] = q.peek()[0];
            l++;
            r++;
        }

        return res;
    }
}

单调队列:

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int[] res = new int[nums.length-k+1];
        LinkedList<Integer> q = new LinkedList<>();
        int size = k;
        for (int i = 0; i < nums.length; i++) {
            while (q.peekLast()!=null && q.peekLast()<nums[i]) {
                q.pollLast();
            }
            q.offerLast(nums[i]);
            if (size <= 0 && q.peekFirst() == nums[i-k]) {
                q.pollFirst();
            } else if (size > 0) {
                size--;
            }
            if (i >= k-1) {
                res[i-k+1] = q.peek();
            }
        }
        return res;
    }
}

# 128. 最长连续序列

// [100,4,1,3,2] = 1,2,3,4
// {100=1,4=4,1=4,3=2,2=4}
// 由于是查找数字连续序列,并且只能On时间,也就是1~2次遍历。
// 从数据最少的场景开始分析:
// 每放入一个数时,可以使用字典知道前面放入的元素中是否存在它相邻的元素,比如:字段中已经有{1,3},现在放入2,通过cur-1,cur+1到字典中查是否存在相邻元素。
// 并且加入一个数时,有插入到连续数的左、右、中三种场景(要更新字典中保存的长度值):
// 左:{2,3}<1; 更新自己和连续数的右边界;右:{2,3}<4; 更新自己和连续数的左边界;中:{2,4}<3; 更新左右连续数的左右边界。
// 长度是通过边界相减获得的;当前插入数字的连续长度值=字典中左边数相邻连续数长度+字典中右边数相邻连续数长度+1
// 再更新当前数插入后的连续数的边界值的映射长度。
class Solution {
    public int longestConsecutive(int[] nums) {
        int n = nums.length;
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        int res = 0;
        for (int num : nums) {
            if (!map.containsKey(num)) {
                int left = map.get(num - 1) == null ? 0 : map.get(num - 1);
                int right = map.get(num + 1) == null ? 0 : map.get(num + 1);
                int cur = 1 + left + right;
                if (cur > res) {
                    res = cur;
                }
                map.put(num, cur);
                map.put(num - left, cur);
                map.put(num + right, cur);
            }
        }
        return res;
    }
}

哈希表,先将数组加入到哈希表中,再表里检查集合中是否存在i+1的数,如果i+1存在,继续检查i+2...i+n,同时统计最大值。但此时复杂度是n^2,通过观察可以发现,如果i-1存在,就不用统计i了,因为i-1会统计,可以直接跳过,所以复杂度降为On。

class Solution {
    public int longestConsecutive(int[] nums) {
        Set<Integer> set = new HashSet<>();
        for (int n : nums) {
            set.add(n);
        }

        int max = 0;
        for (int i : nums) {
            if (set.contains(i-1)) {
                continue;
            }
            int n = i+1;
            int len = 1;
            while (set.contains(n)) {
                len++;
                n++;
            }
            max = Math.max(max, len);
        }
        return max;
    }
}

# 76. Minimum Window Substring(最小覆盖子串)

// 滑动窗口
// s = "ADOBECODEBANC", t = "ABC"  result: "BANC"
class Solution {
    public String minWindow(String s, String t) {
        // use arrays; t put in array, acsii length;
        // acsii 是0-128大小,包含数字、大小写字母、普通字符
        int[] need = new int[128];
        for (char c : t.toCharArray()) {
            need[c]++;
        }

        int l = 0, r = 0;
        int cnt = t.length();
        int min = Integer.MAX_VALUE;
        int start = 0;
        while (r < s.length()) {
            // 通过cnt计算是否包含所有t字符;不包含直接执行最后一步右移right指针
            char c = s.charAt(r);
            if (cnt > 0 && need[c] > 0) {
                cnt--;
            }
            // 无论s中是否包含t字符,right指针右移过程,都在映射中做减一操作,回头left指针右移时,统一做加一操作,不用区分。
            // 非t字符的计数,就可能是负数
            need[c]--;
            if (cnt <= 0) {
                // 清空左边 不包含t的字符(left右移)
                while (need[s.charAt(l)] < 0) {
                    need[s.charAt(l)]++;
                    l++;
                }
                // 记录最小的长度
                if (r-l+1 < min) {
                    min = r-l+1;
                    // 记录最小长度和起始位置,用于获取对应字符串
                    start = l;
                }
                // left指针接着右移一位(相当于移出一个t中字符)
                need[s.charAt(l)]++;
                l++;
                cnt++;
            }
            // 继续右移right指针
            r++;
        }
        return min == Integer.MAX_VALUE ? "" : s.substring(start, start+min);
    }
}

# 263. Ugly Number(丑数)

class Solution {
    public boolean isUgly(int n) {
        if (n <= 0) {
            return false;
        }
        while (n % 5 == 0) {
            n = n / 5;
        }
        while (n % 3 == 0) {
            n = n / 3;
        }
        while (n % 2 == 0) {
            n = n / 2;
        }
        return n == 1;
    }
}

代码结构优化:

class Solution {
    public boolean isUgly(int n) {
        if (n <= 0) {
            return false;
        }
        int[] ugly = new int[]{5,3,2};
        for (int u : ugly) {
            while (n % u == 0) {
                n /= u;
            }
        }
        return n == 1;
    }
}

# 238. Product of Array Except Self(除自身以外数组的乘积)

// 不符合题意的使用除法实现,注意判断除数为0的场景/by zero
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int mul = 1;
        int zero = 0;
        for (int n : nums) {
            if (n == 0) {
                zero++;
                continue;
            }
            mul *= n;
        }
        int[] res = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            if (zero == 1) {
                if (nums[i] != 0) {
                    res[i] = 0;
                } else {
                    res[i] = mul;
                }
                continue;
            } else if (zero > 1) {
                res[i] = 0;
                continue;
            }else {
                res[i] = mul/nums[i];
            }
        }
        return res;
    }
}

解法一:

// 生成左右乘积
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] left = new int[n];
        int[] right = new int[n];
        // 两遍循环生成left、right记录当前数的左右两边数的乘积
        left[0] = 1;
        for (int i = 1; i < n; i++) {
            left[i] = left[i-1] * nums[i-1];
        }

        right[n-1] = 1;
        for (int i = n-2; i >= 0; i--) {
            right[i] = right[i+1] * nums[i+1];
        }

        int[] res = new int[n];
        for (int i = 0; i < n; i++) {
            res[i] = left[i] * right[i];
        }
        return res;
    }
}

# 454. 四数相加 II(4Sum II)

class Solution {
    public int fourSumCount(int[] a, int[] b, int[] c, int[] d) {
        Map<Integer, Integer> map = new HashMap<>();
        // 将a,b和c,d分为两组,有a+b = -(c+d);
        // 先遍历a+b存入字典,val为值存在的次数
        for (int i = 0; i < a.length; i++) {
            int ta = a[i];
            for (int j = 0; j < b.length; j++) {
                Integer tmp = map.get(ta + b[j]);
                if (tmp != null) {
                    tmp++;
                } else {
                    tmp = 1;
                }
                map.put(ta+b[j], tmp);
            }
        }

        int cnt = 0;
        // 遍历c,d如果等于-(a+b)则计数
        for (int i = 0; i < c.length; i++) {
            int tc = c[i];
            for (int j = 0; j < d.length; j++) {
                Integer tmp = map.get(-(tc + d[j]));
                if (tmp != null) {
                    cnt += tmp;
                }
            }
        }
        return cnt;
    }
}

# 227. Basic Calculator II(基本计算器 II)

class Solution {
    // * 42  / 47
    // + 43  - 45
    public int calculate(String s) {
        Stack<Integer> stack = new Stack<>();
        char pre = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 校验空格
            while (i+1<s.length() && c == ' ') {
                i++;
                c = s.charAt(i);
                continue;
            }

            // 取完整数字
            String num = "";
            while (c > 47 && c < 58) {
                num = num + c;
//                System.out.println("num:"+num);
                if (i+1<s.length()) {
                    i++;
                    c = s.charAt(i);
                } else {
                    break;
                }
            }

            if (num != "") {
                // 是否存在pre符号
                if (pre != 0) {
                    if (pre == 42) {
                        int tmp = Integer.valueOf(num) * stack.pop();
//                        System.out.println("num:"+num+"tmp:"+tmp);
                        stack.push(tmp);
                    } else if (pre == 47) {
//                        System.out.println("peek:"+stack.peek());
                        int tmp = stack.pop() / Integer.valueOf(num);
//                        System.out.println("num:"+num+"tmp:"+tmp);
                        stack.push(tmp);
                    } else if (pre == 45) {
                        // 是否是减号
//                        System.out.println("peek:"+stack.peek());
                        stack.push(Integer.valueOf(num) * -1);
                    }
                    pre = 0;
                } else {
                    stack.push(Integer.valueOf(num));
                }
            }

            // 是否是乘除
            while (i+1<s.length() && c == ' ') {
                i++;
                c = s.charAt(i);
                continue;
            }
            if (c == 42 || c == 47 || c == 45) {
                pre = c;
            }
        }
        int res = 0;
        if (!stack.empty()) {
            res = stack.pop();
        }
        while (!stack.empty()) {
//            System.out.println("res:"+res);
            res += stack.pop();
        }

        return res;
    }
}

# 264. 丑数 II

// 优先级队列(最小堆)
// 丑数除了1,其余都是2x,3x,5x,放入最小堆中,每次取出最小结果,再将结果存入到最小堆中(去重存入)
// 遍历第n次即,第n个丑数
// 注意int溢出
class Solution {
    public int nthUglyNumber(int n) {
        PriorityQueue<Long> q = new PriorityQueue<>();
        Set<Long> set = new HashSet<>();
        q.offer(1L);
        long res = 0L;
        int[] factory = new int[]{2,3,5};
        for (int i = 0; i < n; i++) {
            res = q.poll();
            for (int f : factory) {
                long t = res*f;
                if (!set.contains(t)) {
                    q.offer(t);
                    set.add(t);
                }
            }
        }
        return (int)res;
    }
}
// 动态规划
// p2,p3,p5
// dp[i] = min(dp[p2]*2, dp[p3]*3, dp[p5]*5)
class Solution {
    public int nthUglyNumber(int n) {
        int[] dp = new int[n+1];
        int p2 = 1, p3 = 1, p5 = 1;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
//            System.out.println("p2:"+dp[p2]+"p3:"+dp[p3]+"p5:"+dp[p5]);
            int t2 = dp[p2]*2, t3 = dp[p3]*3, t5 = dp[p5]*5;
            dp[i] = Math.min(Math.min(t2, t3), t5);
            if (dp[i] == t2) {
                p2++;
            }
            if (dp[i] == t3) {
                p3++;
            }
            if (dp[i] == t5) {
                p5++;
            }
        }
        return dp[n];
    }
}

重刷

class Solution {
    public int nthUglyNumber(int n) {
        int[] dp = new int[n];
        dp[0] = 1;
        int two = 0, three = 0, five = 0;
        for (int i = 1; i < n; i++) {
            int a = dp[two]*2;
            int b = dp[three]*3;
            int c = dp[five]*5;
            int min = Math.min(a, Math.min(b, c));
            if (min == a) two++;
            if (min == b) three++;
            if (min == c) five++;
            dp[i] = min;
        }
        return dp[n-1];
    }
}

# 23. Merge k Sorted Lists(合并K个升序链表)

// 最小堆
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> q = new PriorityQueue<ListNode>(new Comparator<ListNode>() {
            public int compare(ListNode o1, ListNode o2) {
                return o1.val - o2.val;
            }
        });
        int n = lists.length;
        if (n < 1) {
            return null;
        }
        for (ListNode node : lists) {
            if (node == null) continue;
            q.offer(node);
        }
        ListNode head = new ListNode(0);
        ListNode curr = head;
        while (!q.isEmpty()) {
            ListNode tmp = q.poll();
            curr.next = tmp;
            curr = curr.next;
            if (tmp.next != null) {
                q.offer(tmp.next);
            }
        }
        return head.next;
    }
}

# 138. 复制带随机指针的链表

// 做映射
class Solution {
    public Node copyRandomList(Node head) {
        Map<Node, Node> map = new HashMap<>();
        Node sentry = new Node(0);
        Node curr = sentry;
        while (head != null) {
            // 从字典中取出对应的拷贝node,如果不存在,new一个;
            // 存在是由于random指针指向了一个字典中不存在的节点,需要new一个。
            Node copier = map.get(head);
            if (copier == null) {
                copier = new Node(head.val);
                map.put(head, copier);
            }
            // curr指针的next指向拷贝node,将拷贝node连接起来。
            curr.next = copier;
            curr = curr.next;
            // 处理random指针
            Node rd = head.random;
            if (rd != null) {
                Node nrd = map.get(rd);
                if (nrd == null) {
                    nrd = new Node(rd.val);
                    map.put(rd, nrd);
                }
                copier.random = nrd;
            }
            head = head.next;
        }
        return sentry.next;
    }
}

重刷,写得更简洁了

class Solution {
    public Node copyRandomList(Node head) {
        Map<Node, Node> map = new HashMap<>();
        Node sentinel = new Node(0);
        Node nh = sentinel;
        while (head != null) {
            nh.next = map.getOrDefault(head, new Node(head.val));
            map.put(head, nh.next);
            if (head.random != null) {            
                Node nr = map.getOrDefault(head.random, new Node(head.random.val));
                nh.next.random = nr;
                map.put(head.random, nr);
            }
            nh = nh.next;
            head = head.next;
        }
        return sentinel.next;
    }
}

# 752. 打开转盘锁

// 单向bfs
// 从0000开始,终点为target,障碍物为deadends,求最短距离。
class Solution {
    public int openLock(String[] deadends, String target) {
        Set<String> deadend = new HashSet<>();
        for (String s : deadends) {
            deadend.add(s);
        }

        return bfs(deadend, target);
    }

    private int bfs(Set<String> deadend, String target) {
        Queue<String> q = new LinkedList<>();
        Set<String> visited = new HashSet<>();
        visited.addAll(deadend);
        q.offer("0000");
        visited.add("0000");
        int step = 0;
        while (!q.isEmpty()) {
            int n = q.size();
            for (int i = 0; i < n; i++) {
                String cur = q.poll();
//                System.out.println("cur:"+cur);
                if (deadend.contains(cur)) {
                    continue;
                }
                if (target.equals(cur)) {
                    return step;
                }
                // +1 -1
                for (int j = 0; j < 4; j++) {
                    String plusOne = plus(cur, j);
                    if (!visited.contains(plusOne)) {
                        q.offer(plusOne);
                        visited.add(plusOne);
                    }

                    String minusOne = minus(cur, j);
                    if (!visited.contains(minusOne)) {
                        q.offer(minusOne);
                        visited.add(minusOne);
                    }
                }
            }
            step++;
        }
        return -1;
    }

    private String plus(String s, int i) {
        char[] ch = s.toCharArray();
        if (ch[i] == '9')
            ch[i] = '0';
        else
            ch[i] += 1;
        return new String(ch);
    }

    private String minus(String s, int i) {
        char[] ch = s.toCharArray();
        if (ch[i] == '0')
            ch[i] = '9';
        else
            ch[i] -= 1;
        return new String(ch);
    }
}
// 双向bfs
class Solution {
    public int openLock(String[] deadends, String target) {
        Set<String> deadend = new HashSet<>();
        for (String s : deadends) {
            deadend.add(s);
        }
        return bfs(deadend, target);
    }

    private int bfs(Set<String> deadend, String target) {
        Set<String> q1 = new HashSet<>();
        Set<String> q2 = new HashSet<>();
        Set<String> visited = new HashSet<>();
        visited.addAll(deadend);
        q1.add("0000");
        q2.add(target);
        int step = 0;

        while (!q1.isEmpty() && !q2.isEmpty()) {
            if (q1.size() > q2.size()) {
                // 交换 q1 和 q2
                Set<String> temp = q1;
                q1 = q2;
                q2 = temp;
            }
            Set<String> temp = new HashSet<>();

            for (String cur : q1) {
                if (deadend.contains(cur)) {
                    continue;
                }
                if (q2.contains(cur)) {
                    return step;
                }

                visited.add(cur);

                // +1 -1
                for (int j = 0; j < 4; j++) {
                    String plusOne = plus(cur, j);
                    if (!visited.contains(plusOne)) {
                        temp.add(plusOne);
                    }

                    String minusOne = minus(cur, j);
                    if (!visited.contains(minusOne)) {
                        temp.add(minusOne);
                    }
                }
            }

            step++;

            q1 = q2;
            q2 = temp;
        }
        return -1;
    }

    private String plus(String s, int i) {
        char[] ch = s.toCharArray();
        if (ch[i] == '9')
            ch[i] = '0';
        else
            ch[i] += 1;
        return new String(ch);
    }

    private String minus(String s, int i) {
        char[] ch = s.toCharArray();
        if (ch[i] == '0')
            ch[i] = '9';
        else
            ch[i] -= 1;
        return new String(ch);
    }
}

两个多月后再次做,朴素做法,但是代码并不是最优的,同时也没有想到双向bfs的方法:

class Solution {
    public int openLock(String[] deadends, String target) {
        Set<String> dd = new HashSet<>();
        for (String d : deadends) {
            dd.add(d);
        }
        String start = "0000";
        if (dd.contains(start)) return -1;
        return bfs(start, target, dd);
    }
    
    private int bfs(String start, String target, Set<String> dd) {
        Queue<String> q = new LinkedList<>();
        Set<String> visited = new HashSet<>();
        
        q.offer(start);
        visited.add(start);
        int cnt = 0;
        
        while (!q.isEmpty()) {
            int size = q.size();
            
            for (int i = 0; i < size; i++) {
                String cur = q.poll();
                if (cur.equals(target)) return cnt;
                for (int j = 0; j < 4; j++) {
                    String s1 = process(cur, j, true);
                    if (!visited.contains(s1) && !dd.contains(s1)) {
                        q.offer(s1);
                        visited.add(s1);
                    }
                    
                    String s2 = process(cur, j, false);
                    if (!visited.contains(s2) && !dd.contains(s2)) {
                        q.offer(s2);
                        visited.add(s2);
                    }
                }
            }
            cnt++;
        }
        return -1;
    }
    
    private String process(String s, int index, boolean add) {
        char c = s.charAt(index);
        char[] cs = s.toCharArray();
        if (add) {
            if (cs[index] == 57) {
                cs[index] = '0';
            } else {
                cs[index]++;
            }
        } else {
            if (cs[index] == 48) {
                cs[index] = '9';
            } else {
                cs[index]--;
            }
        }
        return new String(cs);
    }
}

# 127. Word Ladder(单词接龙)

// bfs
// 找下一个词,从一个词可以得到n(n为词长度)个缺失位词
// 判断在Set中是否存在此缺失位的词,找到存在的放入队列中
class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        // endWord不在list中直接返回0
        Set<String> words = new HashSet<>();
        for (String w : wordList) {
            words.add(w);
        }
        if (!words.contains(endWord)) {
            return 0;
        }
        return bfs(words, beginWord, endWord);
    }

    public int bfs(Set<String> words, String start, String target) {
        Queue<String> q = new LinkedList<>();
        Set<String> visited = new HashSet<>();
        q.offer(start);
        visited.add(start);
        int step = 1;

        while (!q.isEmpty()) {
            int sz = q.size();
            for (int i = 0; i < sz; i++) {
                String cur = q.poll();
//                System.out.println("cur:"+cur);
                if (target.equals(cur)) {
                    return step;
                }

                // 循环单词的每个字符
                for (int j = 0; j < cur.length(); j++) {
                    // 遍历26个字母
                    for (int k = 97; k <= 122; k++) {
                        String newWord = cur.substring(0, j) + String.valueOf((char)k) + cur.substring(j+1);
                        if (words.contains(newWord) && !visited.contains(newWord)) {
//                            System.out.println("contains:"+newWord);
                            q.offer(newWord);
                            visited.add(newWord);
                        }
                    }
                }
            }
            step++;
        }
        return 0;
    }
}

# 130. Surrounded Regions(被围绕的区域)

// bfs
class Solution {
    public void solve(char[][] board) {
        bfs(board);
    }

    private int[][] directions = new int[][]{{-1, 0}, {0, -1}, {1, 0}, {0, 1}};

    public void bfs(char[][] board) {
        Queue<int[]> q = new LinkedList<>();

        int rows = board.length;
        int cols = board[0].length;
        // 放入边界节点
        // 加入列的边界'0'节点
        for (int i = 0; i < rows; i++) {
            if (board[i][0] == 'O') {
                q.offer(new int[]{i, 0});
            }
            if (board[i][cols-1] == 'O') {
                q.offer(new int[]{i, cols-1});
            }
        }
        // 加入行的边界'0'节点
        for (int i = 0; i < cols; i++) {
            if (board[0][i] == 'O') {
                q.offer(new int[]{0, i});
            }
            if (board[rows-1][i] == 'O') {
                q.offer(new int[]{rows-1, i});
            }
        }

        while (!q.isEmpty()) {
            int sz = q.size();
            for (int i = 0; i < sz; i++) {
                int[] cur = q.poll();

                int y = cur[0];
                int x = cur[1];
                board[y][x] = '#';

                for (int[] d : directions) {
                    int newY = y + d[0];
                    int newX = x + d[1];

                    if (inArea(newX, newY, rows, cols) && board[newY][newX] == 'O') {
                        q.offer(new int[]{newY, newX});
                    }
                }
            }
        }

        // 恢复
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (board[i][j] == '#') {
                    board[i][j] = 'O';
                } else if (board[i][j] == 'O') {
                    board[i][j] = 'X';
                }
            }
        }

    }

    private boolean inArea(int x, int y, int rows, int cols) {
        return x >= 0 && x < cols && y >= 0 && y < rows;
    }
}

# 179. Largest Number(最大数)

class Solution {
    public String largestNumber(int[] nums) {
        List<String> list = new ArrayList<>();
        for (int n : nums) {
            list.add(String.valueOf(n));
        }

        Collections.sort(list, new MyCompare());

        StringBuilder sb = new StringBuilder();
        for (String s : list) {
            sb.append(s);
        }
        // 处理前置0,保留一位
        int len = sb.length();
        int k = 0;
        while (k < len-1 && sb.charAt(k) == '0') k++;
        return sb.substring(k);
    }
    
    class MyCompare implements Comparator<String> {
        public int compare(String s1, String s2) {
            String r1 = s1+s2;
            String r2 = s2+s1;
            return r2.compareTo(r1);
        }
    }
}

# 783. Minimum Distance Between BST Nodes(二叉搜索树节点最小距离)

// 中序遍历,校验前后两个节点的差值,记录最小值
class Solution {
    int min = Integer.MAX_VALUE;
    int pre = Integer.MAX_VALUE;

    public int minDiffInBST(TreeNode root) {
        recursive(root);
        return min;
    }

    private void recursive(TreeNode n) {
        if (n == null) return;

        recursive(n.left);

        min = Math.min(min, Math.abs(pre - n.val));
        pre = n.val;

        recursive(n.right);
    }
}

# 530. Minimum Absolute Difference in BST(二叉搜索树的最小绝对差)

class Solution {
    int min = Integer.MAX_VALUE;
    int pre = Integer.MAX_VALUE;
    
    public int getMinimumDifference(TreeNode root) {
        recursive(root);
        return min;
    }
    
    private void recursive(TreeNode n) {
        if (n == null) return;

        recursive(n.left);

        min = Math.min(min, Math.abs(pre - n.val));
        pre = n.val;

        recursive(n.right);
    }
}

# 236. Lowest Common Ancestor of a Binary Tree(二叉树的最近公共祖先)

// f(l) = true 表示其子节点存在等于p或者q的
// f(left) && f(right)
// (cur==p || cur==q) && (f(left) || f(right))
class Solution {
    TreeNode father = null;

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        f(root, p, q);
        return father;
    }

    private boolean f(TreeNode n, TreeNode p, TreeNode q) {
        if (father != null) return false;

        if (n == null) return false;

        boolean l = f(n.left, p, q);
        boolean r = f(n.right, p, q);

        if ((l && r) || ((n.val == p.val || n.val == q.val) && (l || r))) {
            father = n;
        }

        if (father == null && (n.val == p.val || n.val == q.val)) {
            // System.out.println("p:"+p.val+"q:"+q.val+"n:"+n.val);
            return true;
        }
        if (father == null && (l || r)) {
            // System.out.println("l:"+l+"r:"+r);
            return true;
        }
        return false;
    }
}

# 315. Count of Smaller Numbers After Self(计算右侧小于当前元素的个数)

// 时间:O(n^2),会超时
class Solution {
    public List<Integer> countSmaller(int[] nums) {
        int n = nums.length;
        Integer[] res = new Integer[n];
        int[] sort = new int[n];
//        Arrays.fill(sort, Integer.MAX_VALUE);
        // 倒序循环
        // 从数组的最后开始选一个数,插入排序到sort数组中
        // 每次排序的下标即为结果,存入到res中
        sort[0] = nums[n-1];
        res[n-1] = 0;
        for (int i = n-2; i >= 0; i--) {
            int val = nums[i];
            int j = n-2-i;
//            System.out.println("j:"+j+";val:"+val);
//            System.out.println("sort:"+Arrays.toString(sort));
//            System.out.println("res:"+Arrays.toString(res));
            for (; j >= 0; j--) {
                // 此处需要大于等于,因为相同的数,不计入结果中,所以要将相同数放入到最小的位置
                if (sort[j] >= val) {
                    sort[j+1] = sort[j];
                } else {
                    break;
                }
            }
            sort[j+1] = val;
            res[i] = j+1;
        }
        return Arrays.asList(res);
    }
}
  • 归并排序计算逆序对
class Solution {

    private int[] cnt;
    private int[] indexes;

    public List<Integer> countSmaller(int[] nums) {
        int n = nums.length;
        cnt = new int[n];
        indexes = new int[n];
        for (int i = 0; i < n; i++) {
            indexes[i] = i;
        }
        int[] temp = new int[n];

        sort(nums, 0, n-1, temp);

        List<Integer> res = new ArrayList<>(n);
        for (int i = 0; i < cnt.length; i++) {
            res.add(i, cnt[i]);
        }

        return res;
    }

    private void sort(int[] nums, int left, int right, int[] temp) {
        if (left < right) {
            int mid = left + (right - left) / 2;
            sort(nums, left, mid, temp);// 左边归并排序,使得左子序列有序
            sort(nums, mid+1, right, temp);// 右边归并排序,使得右子序列有序
            merge(nums, left, mid, right, temp);// 将两个有序子数组合并操作
        }
    }

    private void merge(int[] nums, int left, int mid, int right, int[] temp) {
        int i = left;//左序列指针
        int j = mid+1;//右序列指针
        int t = left;//临时数组指针

        // mid是左序列的最大边界,right是右序列的最大边界
        while (i <= mid && j <= right) {
            if (nums[indexes[i]] <= nums[indexes[j]]) {
                temp[t++] = indexes[j++];
            } else {
                cnt[indexes[i]] += right - j + 1;
                temp[t++] = indexes[i++];
                // left > right时,right将被赋值到结果集中,同时代表left和left之后的所有元素都大于right
                // 因此,right的位置也移动了
            }
        }
        // 将左边剩余元素填充进temp中
        while (i <= mid) {
            temp[t++] = indexes[i++];
        }
        // 将右序列剩余元素填充进temp中
        while (j <= right) {
            temp[t++] = indexes[j++];
        }

        t = left;
        // 将temp中的元素全部拷贝到原数组中
        while (t <= right) {
            indexes[t] = temp[t];
            t++;
        }
    }
}

# 剑指 Offer 51. 数组中的逆序对

class Solution {
    private int cnt = 0;
    public int reversePairs(int[] nums) {
        int n = nums.length;
        int[] temp = new int[n];
        sort(nums, 0, n-1, temp);
        // System.out.println("nums:"+Arrays.toString(nums));
        return cnt;
    }

    private void sort(int[] nums, int left, int right, int[] temp) {
        if (left < right) {
            int mid = left + (right - left) / 2;
            sort(nums, left, mid, temp);// 左边归并排序,使得左子序列有序
            sort(nums, mid+1, right, temp);// 右边归并排序,使得右子序列有序
            merge(nums, left, mid, right, temp);// 将两个有序子数组合并操作
        }
    }

    private void merge(int[] nums, int left, int mid, int right, int[] temp) {
        int i = left;//左序列指针
        int j = mid+1;//右序列指针
        int t = 0;//临时数组指针

        // mid是左序列的最大边界,right是右序列的最大边界
        while (i <= mid && j <= right) {
            if (nums[i] <= nums[j]) {
                temp[t++] = nums[i++];
            } else {
                cnt += mid-i+1;
                temp[t++] = nums[j++];
            }
        }
        // 将左边剩余元素填充进temp中
        while (i <= mid) {
            temp[t++] = nums[i++];
        }
        // 将右序列剩余元素填充进temp中
        while (j <= right) {
            temp[t++] = nums[j++];
        }

        t = 0;
        // 将temp中的元素全部拷贝到原数组中
        while (left <= right) {
            nums[left++] = temp[t++];
        }
    }
}

同上面一样使用归并排序解题,但写法不一样

class Solution {
    int cnt = 0;
    public int reversePairs(int[] nums) {
        if (nums.length == 0) return 0;
        int[] sorted = mergeSort(nums);
        return cnt;
    }

    private int[] mergeSort(int[] nums) {
        int n = nums.length;
        if (n == 1) {
            return nums;
        }
        int mid = n / 2;
        int[] left = Arrays.copyOfRange(nums, 0, mid);
        int[] right = Arrays.copyOfRange(nums, mid, n);

        left = mergeSort(left);
        right = mergeSort(right);

        int[] merged = merge(left, right);

        return merged;
    }

    private int[] merge(int[] left, int[] right) {
        if (left == null) left = new int[0];
        if (right == null) right = new int[0];

        int[] ans = new int[left.length+right.length];

        int a = 0, b = 0, i = 0;
        while (a < left.length && b < right.length) {
            if (left[a] <= right[b]) {
                ans[i] = left[a];
                a++;
            } else {
                // 关键步骤
                cnt += (left.length-a);

                ans[i] = right[b];
                b++;
            }
            i++;
        }

        if (a < left.length) {
            for (int k = a; k < left.length; k++) {
                ans[i++] = left[k];
            }
        } else {
            for (int k = b; k < right.length; k++) {
                ans[i++] = right[k];
            }
        }
        return ans;
    }
}

# 213. 打家劫舍 II

在198题(打家劫舍)中,与此题唯一不同的是,此题的前后房屋是相连的,因此在选第一个节点时,就无法选最后一个节点。如果只有三个节点,只能选一个节点。
思路:既然选第一个就无法选最后一个,选最后一个就无法选第一个,就出现两种场景:

  1. 从前开始递推,再用原有递推方程,推到倒数第二个(去掉倒数第一个)
  2. 从后开始递推,推到倒数第二个(去掉第一个) 两个结果比较最大值,即答案。

迭代:

class Solution {
    public int rob(int[] nums) {
        // 边界场景
        int n = nums.length;
        if (n < 1) {
            return 0;
        } else if (n < 2) {
            return nums[0];
        } else if (n < 3) {
            return Math.max(nums[0], nums[1]);
        } else if (n < 4) {
            return Math.max(Math.max(nums[0], nums[1]), nums[2]);
        }

        // 初始化
        int[] dp = new int[n];
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);
        // 迭代
        for (int i = 2; i < n; i++) {
            dp[i] = Math.max(dp[i-1], dp[i-2]+nums[i]);
        }
        int front = dp[n-2];

        // 倒序迭代
        dp[n-1] = nums[n-1];
        dp[n-2] = Math.max(nums[n-1], nums[n-2]);
        for (int i = n-3; i >= 0; i--) {
            dp[i] = Math.max(dp[i+1], dp[i+2]+nums[i]);
        }
        int back = dp[1];

        return Math.max(front, back);
    }
}

重刷的时候,代码简洁了不少

class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if (n == 1) return nums[0];
        int a0 = 0, b0 = 0;
        int a1 = 0, b1 = 0;
        for (int i = 0; i < n; i++) {
            if (i < n-1) {
                int c = Math.max(b0, a0+nums[i]);
                a0 = b0;
                b0 = c;
            }

            if (i > 0) {
                int c = Math.max(b1, a1+nums[i]);
                a1 = b1;
                b1 = c;
            }
        }
        return Math.max(b0, b1);
    }
}

# 152. 乘积最大子数组

// 如果存在偶数个负号,则最大乘积就是所有数的和;如果是奇数个负号,最大乘积就是排除掉一个负号数后的最大乘积。结论仍然无法支撑递推。
// 换种思路,按照常规递推整数数组方式,,dp[i]代表以i为子数组终点的最大乘积,此时存在两种场景:
// 1.若i为正数,要保证乘积最大,则i-1必须也为最大。
// 2.若i为负数,要保证最大乘积,则i-1必须最小。
// 因此需要定义两个缓存值:i-1乘积的最大值和最小值
// 动归方程:定义两个状态数组max,min;第一个nums[i]为正数,第二个为负数
// max[i] = MAX{max[i-1]*nums[i], min[i-1]*nums[i], nums[i]}
// min[i] = MIN{min[i-1]*nums[i], max[i-1]*nums[i], nums[i]}
// 实时刷新最大的max
class Solution {
    public int maxProduct(int[] nums) {
        int n = nums.length;
        int res = 0;
        int[] max = new int[n];
        int[] min = new int[n];
        max[0] = nums[0];
        min[0] = nums[0];
        res = nums[0];
        for (int i = 1; i < n; i++) {
            if (nums[i] > 0) {
                max[i] = Math.max(max[i-1]*nums[i], nums[i]);
                min[i] = Math.min(min[i-1]*nums[i], nums[i]);
            } else if (nums[i] < 0) {
                max[i] = Math.max(min[i-1]*nums[i], nums[i]);
                min[i] = Math.min(max[i-1]*nums[i], nums[i]);
            } else {
                min[i] = nums[i];
                max[i] = nums[i];
            }
            res = Math.max(res, max[i]);
        }
        return res;
    }
}

动态规划,空间优化

class Solution {
    public int maxProduct(int[] nums) {
        int n = nums.length;
        int pmax = nums[0], pmin = nums[0], res = nums[0];
        for (int i = 1; i < n; i++) {
            int cmax = nums[i]*pmax;
            int cmin = nums[i]*pmin;
            pmax = Math.max(nums[i], Math.max(cmax, cmin));
            pmin = Math.min(nums[i], Math.min(cmax, cmin));
            res = Math.max(res, Math.max(pmax, pmin));
        }
        return res;
    }
}

# 309. 最佳买卖股票时机含冷冻期

// 动态规划
// 买入可以记作负收益-p[i],卖出可以记作正收益+p[i]
class Solution {
    public int maxProfit(int[] p) {
        int n = p.length;
        int[][] dp = new int[n][3];
        // init
        // dp[i][0] = Max(dp[i-1][0], dp[i-1][2]-p[i]) 代表当前持有股票,因此有可能由两个状态演化来:
        // 1.前一天已经持有  2.前一天不持有(此天必须不能是卖出的),i当天买入
        dp[0][0] = -p[0];
        // dp[i][1] = dp[i-1][0]+p[i] 代表当前不持有股票,当前卖出。因此前一天一定持有。
        dp[0][1] = 0;
        // dp[i][2] = Max(dp[i][1], dp[i][2])  代表当前不持有股票,当天无卖出。当前可能为冷冻期。因此可能由两个状态演化来:
        // 1.前一天卖出  2.前一天不支持有并且不处于冷冻期
        dp[0][2] = 0;

        for (int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2]-p[i]);
            dp[i][1] = dp[i-1][0] + p[i];
            dp[i][2] = Math.max(dp[i-1][1], dp[i-1][2]);
        }
        return Math.max(dp[n-1][1], dp[n-1][2]);
    }
}

动态规划,空间优化

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int p1 = -prices[0], p2 = 0, p3 = 0;
        for (int i = 1; i < n; i++) {
            int c1 = Math.max(p1, p3-prices[i]);
            int c2 = Math.max(p1+prices[i-1], p1+prices[i]);
            int c3 = Math.max(p2, p3);
            p1 = c1;
            p2 = c2;
            p3 = c3;
        }
        return Math.max(p1, Math.max(p2, p3));
    }
}

重刷,状态分析不出来,对于动规,或者说对于如何穷尽状态掌握不好

class Solution {
    public int maxProfit(int[] prices) {
        // p1持有 p2冷冻 p3非冷冻
        int p1 = -prices[0], p2 = 0, p3 = 0;
        for (int i = 1; i < prices.length; i++) {
            int c1 = Math.max(p1, p3-prices[i]);
            int c2 = p1+prices[i];
            int c3 = Math.max(p3, p2);
            p1 = c1;
            p2 = c2;
            p3 = c3;
        }
        return Math.max(p2, p3);
    }
}

# 1827. 最少操作使数组递增(第 50 场双周赛)

第一题

class Solution {
    public int minOperations(int[] nums) {
        int n = nums.length;
        int cnt = 0;
        for (int i = 1; i < n; i++) {
            while (nums[i] <= nums[i-1]) {
                nums[i]++;
                cnt++;
            }
        }
        return cnt;
    }
}

# 1828. 统计一个圆中点的数目(第 50 场双周赛)

第二题

class Solution {
    public int[] countPoints(int[][] p, int[][] q) {
        int[] res = new int[q.length];
        for (int i = 0; i < q.length; i++) {
            int cnt = 0;
            for (int j = 0; j < p.length; j++) {
                int x = p[j][0];
                int y = p[j][1];
                
                if (Math.sqrt(Math.pow(x-q[i][0], 2) + Math.pow(y-q[i][1], 2)) <= q[i][2]) {
                    cnt++;
                }
            }
            res[i] = cnt;
        }
        return res;
    }
}

# 1832. 判断句子是否为全字母句(第 237 场周赛)

第一题

class Solution {
    public boolean checkIfPangram(String s) {
        int[] cnt = new int[26];
        for (int i=0; i < s.length(); i++){
            cnt[s.charAt(i) - 'a']++;
        }
        
        for (int i = 0; i < 26; i++){
            if (cnt[i] == 0) {
                return false;
            }
        }
        return true;
    }
}

# 1833. 雪糕的最大数量(第 237 场周赛)

第二题

class Solution {
    public int maxIceCream(int[] c, int n) {
        Arrays.sort(c);
        int cost = 0;
        int cnt = 0;
        for (int i = 0; i < c.length; i++) {
            cost += c[i];
            if (cost <= n) {
                cnt++;
            } else {
                // 存在整型溢出问题,要提前break
                break;
            }
        }
        return cnt;
    }
}

暴力回溯解,超时

class Solution {
    int res = 0;
    public int maxIceCream(int[] c, int n) {
        // 不需要排序,无论如何都会遍历所有结果
        // Arrays.sort(c);
        int cost = 0;
        int cnt = 0;
        recur(cost, c, n, 0, 0);
        return res;
    }
    
    private void recur(int cost, int[] c, int n, int i, int cnt) {
        if (cost > n || i >= c.length) {
            if (cost > n) {
                cnt--;
            }
            res = Math.max(res, cnt);
            return;
        }
        recur(cost+c[i], c, n, i+1, cnt+1);
        recur(cost, c, n, i+1, cnt);
    }
}

排序+贪心,go解法:

import "sort"
func maxIceCream(costs []int, cs int) int {
    sort.Ints(costs)
    res := 0
    for _, c := range costs {
        cs -= c
        if cs < 0 {
            return res
        } else {
            res++
        }
    }
    return res
}

计数+贪心,go解法:

import "fmt"
func maxIceCream(costs []int, coins int) int {
    var m [100001]int
    for _, v := range costs {
        m[v]++
    }
    // fmt.Println(m)
    cnt := 0
    for i, num := range m {
        if i == 0 || num == 0 {
            continue
        }
        if coins >= i {
            c := min(num, coins/i)
            cnt += c
            coins -= c*i
        } else {
            return cnt;
        }
        // fmt.Println(strconv.Itoa(coins)+","+strconv.Itoa(cnt)+","+strconv.Itoa(i))
    }
    return cnt
}

func min(a, b int) int {
    if a > b {
        return b
    }
    return a
}

# 220. Contains Duplicate III(存在重复元素 III)

利用滑动窗口和有序集合,存在数字相加或相减,需要避免整型溢出。

class Solution {
    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        int n = nums.length;
        TreeSet<Long> set = new TreeSet<>();
        for (int i = 0; i < n; i++) {
            Long ceil = set.ceiling((long)nums[i]-(long)t);
            if (ceil != null && ceil <= (long)nums[i]+(long)t) {
                return true;
            }
            set.add((long) nums[i]);
            if (i >= k) {
                set.remove((long) nums[i-k]);
            }
        }
        return false;
    }
}

还有更优的桶分组解法

// todo

# 139. 单词拆分

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        int n = s.length();
        boolean[] dp = new boolean[n+1];
        Map<String, Boolean> map = new HashMap<>();
        // init
        for (String w : wordDict) {
            map.put(w, true);
        }
        dp[0] = true;

        for (int i = 1; i <= n; i++) {
            for (int j = i-1; j >= 0; j--) {
                dp[i] = dp[j] && map.getOrDefault(s.substring(j, i), false);
                if (dp[i]) break;
            }
        }
        return dp[n];
    }
}

动态规划:

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        Set<String> set = new HashSet<>(wordDict);
        int n = s.length();
        boolean[] dp = new boolean[n];
        for (int i = 0; i < n; i++) {
            if (set.contains(s.substring(0, i+1))) {
                dp[i] = true;
                continue;
            }
            for (int j = i-1; j >= 0; j--) {
                if (dp[j] && set.contains(s.substring(j+1, i+1))) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[n-1];
    }
}

重刷

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        Set<String> set = new HashSet<>(wordDict);
        int n = s.length();
        boolean[] dp = new boolean[n+1];
        dp[0] = true;
        for (int j = 1; j <= n; j++) {
            for (int i = j; i >= 1; i--) {
                if (dp[i-1] && set.contains(s.substring(i-1, j))) {
                    dp[j] = true;
                    break;
                }
            }
        }
        return dp[n];
    }
}

# 51. N-Queens(N 皇后)

回溯剪枝:

class Solution {
    List<List<String>> res = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
        recursive(n, 0, new ArrayList<>(), -2);
        return res;
    }

    private void recursive(int n, int depth, List<String> board, int curri) {
        // 递归深度(列)超过n
        if (depth >= n) {
            res.add(new ArrayList(board));
            return;
        }

        // 横行遍历
        for (int i = 0; i < n; i++) {
            // 跳过无法使用的格子
            if (!isValid(n, board, depth, i)) {
                continue;
            }

            // 选择i为Q的位置,生成一行的字符串
            String row = "";
            for (int j = 0; j < n; j++) {
                if (j == i) {
                    row = row + "Q";
                } else {
                    row = row + ".";
                }
            }

            board.add(row);

            recursive(n, depth+1, board, i);
            
            // 回退
            board.remove(board.size()-1);
        }
    }

    private boolean isValid(int rn, List<String> board, int row, int col) {
        // cn col长度,rn row长度
        int cn = board.size();

        // 检查列是否有皇后互相冲突
        for (int i = 0; i < cn; i++) {
            if (board.get(i).charAt(col) == 'Q')
                return false;
        }
        // 检查右上方是否有皇后互相冲突
        for (int i = row - 1, j = col + 1;
             i >= 0 && j < rn; i--, j++) {
            if (board.get(i).charAt(j) == 'Q')
                return false;
        }
        // 检查左上方是否有皇后互相冲突
        for (int i = row - 1, j = col - 1;
             i >= 0 && j >= 0; i--, j--) {
            if (board.get(i).charAt(j) == 'Q')
                return false;
        }
        return true;
    }
}

# 52. N-Queens II(N皇后 II)

回溯(将N皇后题中保存结果改为计数即可):

class Solution {
    int cnt = 0;
    public int totalNQueens(int n) {
        recursive(n, 0, new ArrayList<>(), -2);
        return cnt;
    }

    private void recursive(int n, int depth, List<String> board, int curri) {
        // 递归深度(列)超过n
        if (depth >= n) {
            cnt++;
            return;
        }

        // 横行遍历
        for (int i = 0; i < n; i++) {
            // 跳过无法使用的格子
            if (!isValid(n, board, depth, i)) {
                continue;
            }

            // 选择i为Q的位置,生成一行的字符串
            String row = "";
            for (int j = 0; j < n; j++) {
                if (j == i) {
                    row = row + "Q";
                } else {
                    row = row + ".";
                }
            }

            board.add(row);

            recursive(n, depth+1, board, i);

            // 回退
            board.remove(board.size()-1);
        }
    }

    private boolean isValid(int rn, List<String> board, int row, int col) {
        // cn col长度,rn row长度
        int cn = board.size();

        // 检查列是否有皇后互相冲突
        for (int i = 0; i < cn; i++) {
            if (board.get(i).charAt(col) == 'Q')
                return false;
        }
        // 检查右上方是否有皇后互相冲突
        for (int i = row - 1, j = col + 1;
             i >= 0 && j < rn; i--, j++) {
            if (board.get(i).charAt(j) == 'Q')
                return false;
        }
        // 检查左上方是否有皇后互相冲突
        for (int i = row - 1, j = col - 1;
             i >= 0 && j >= 0; i--, j--) {
            if (board.get(i).charAt(j) == 'Q')
                return false;
        }
        return true;
    }

}

# 27. Remove Element(移除元素)

双指针,将重复的元素交换到不重复位置即可:

class Solution {
    public int removeElement(int[] nums, int val) {
        int n = nums.length;
        int l = 0, r = 0;

        while (r < n) {
            // l++: l==r; vr!=val;
            if (nums[r] != val) {
                swap(nums, l, r);
                l++;
            }
            r++;
        }
        return l;
    }

    private void swap(int[] n, int a, int b) {
        int tmp = n[a];
        n[a] = n[b];
        n[b] = tmp;
    }
}

# 698. Partition to K Equal Sum Subsets(划分为k个相等的子集)

回溯解法,会超时:

class Solution {
    boolean res = false;
    public boolean canPartitionKSubsets(int[] nums, int k) {
        int[] bucket = new int[k];
        int[] uu = new int[nums.length];
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            uu[i] = nums[nums.length-i-1];
            sum += nums[nums.length-i-1];
        }

        if (sum % k != 0) return false;

        int target = sum / k;

        return backtrack(uu, bucket, 0, target);
    }

    private boolean backtrack(int[] nums, int[] bucket, int index, int target) {
        // 结束了
        if (index >= nums.length) {
            // check
            for (int n : bucket) {
                if (n != target) {
                    return false;
                }
            }
            return true;
        }

        // 遍历桶,每次将以数字装入桶中
        for (int i = 0; i < bucket.length; i++) {

            if (bucket[i] + nums[index] > target) {
                continue;
            }

            bucket[i] += nums[index];

            if (backtrack(nums, bucket, index+1, target)) {
                return true;
            }

            // 回退
            bucket[i] -= nums[index];
        }
        return false;
    }
    
}

# 111. Minimum Depth of Binary Tree(二叉树的最小深度)

// 直接套bfs框架
class Solution {
    public int minDepth(TreeNode root) {
        return bfs(root);
    }

    public int bfs(TreeNode n) {
        if (n == null) return 0;
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(n);
        int step = 1;

        while (!q.isEmpty()) {
            int sz = q.size();
            for (int i = 0; i < sz; i++) {
                TreeNode cur = q.poll();
                if (cur.left == null && cur.right == null) {
                    return step;
                }

                if (cur.left != null)
                    q.offer(cur.left);
                if (cur.right != null)
                    q.offer(cur.right);
            }
            step++;
        }
        return -1;
    }
}

# 773. Sliding Puzzle(滑动谜题)

class Solution {
    // 定义二维对应一维下标的映射
    int[][] mapping = new int[][]{
        new int[]{1, 3},
        new int[]{0, 2, 4},
        new int[]{1, 5},
        new int[]{0, 4},
        new int[]{1, 3, 5},
        new int[]{2, 4}
    };

    public int slidingPuzzle(int[][] input) {
        // 将board转为一维数组
        int[] board = new int[input.length*input[0].length];
        String bb = "";
        for (int i = 0; i < input.length; i++) {
            for (int j = 0; j < input[0].length; j++) {
                board[(i*3)+j] = input[i][j];
                bb = bb + input[i][j];
            }
        }
        // 定义target
        String target = "123450";

        return bfs(bb, target);
    }

    public int bfs(String board, String target) {
        Queue<String> q = new LinkedList<>();
        Set<String> visited = new HashSet<>();
        q.offer(board);
        visited.add(board);
        int step = 0;

        while (!q.isEmpty()) {
            int sz = q.size();
            for (int i = 0; i < sz; i++) {
                String cur = q.poll();
                // 判断board是否到终点
                if (cur.equals(target)) {
                    return step;
                }

                // 找到0的位置,从mapping中获取移动的方向,根据下标交换元素
                for (int j = 0; j < mapping.length; j++) {
                    if (cur.charAt(j) == '0') {
                        for (int m = 0; m < mapping[j].length; m++) {
                            String tmp = swap(cur, mapping[j][m], j);
                            if (!visited.contains(tmp)) {
                                q.offer(tmp);
                                visited.add(tmp);
                            }
                        }
                    }
                }
            }
            step++;
        }

        return -1;
    }

    private String swap(String s, int a, int b) {
        StringBuilder res = new StringBuilder(s);
        char tmp = res.charAt(a);
        return res.replace(a, a+1, String.valueOf(res.charAt(b))).replace(b, b+1, String.valueOf(tmp)).toString();
    }
}

时隔两个月后再次做,这次开始写复杂了,没有定义位置映射,但逻辑一致,后来补上了位置映射:

class Solution {
    // 定义二维对应一维下标的映射
    int[][] mapping = new int[][]{
        new int[]{1, 3},
        new int[]{0, 2, 4},
        new int[]{1, 5},
        new int[]{0, 4},
        new int[]{1, 3, 5},
        new int[]{2, 4}
    };
    
    public int slidingPuzzle(int[][] board) {
        String origin = "";
        int index = -1;
        for (int[] b : board) {
            for (int n : b) {
                origin = origin + n;
                if (n == 0) {
                    index = origin.length()-1;
                }
            }
        }
        return bfs(origin, "123450", index);
    }
    
    private int bfs(String origin, String target, int index) {
        Queue<Node> q = new LinkedList<>();
        Set<String> visited = new HashSet<>();
        
        int mod = 3;
        int l = 0, r = 5;
        int lm = 2, rm = 3;
        
        visited.add(origin);
        q.offer(new Node(origin, index));
        int cnt = 0;
        
        while (!q.isEmpty()) {
            int sz = q.size();
            
            for (int i = 0; i < sz; i++) {
                Node cur = q.poll();
                String s = cur.s;
                int k = cur.index;
                // System.out.println("s:"+s+"; k:"+k);
                // end
                if (s.equals(target)) {
                    return cnt;
                }
                // move
                for (int j : mapping[k]) {
                    String tmp = swap(s, k, j);
                    if (!visited.contains(tmp)) {
                        q.offer(new Node(tmp, j));
                        visited.add(tmp);
                    }
                }
            }
            cnt++;
        }
        return -1;
    }
    
    private String swap(String s, int i, int j) {
        char[] cs = s.toCharArray();
        char tmp = cs[i];
        cs[i] = cs[j];
        cs[j] = tmp;
        return new String(cs);
    }
    
    class Node {
        String s;
        int index;
        public Node(String s, int index) {
            this.s = s;
            this.index = index;
        }
    }
}

# 77. Combinations(组合)

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        Set<Integer> visited = new HashSet<>();
        backtrack(n, k, new ArrayList<>(), visited, 1);
        return res;
    }

    private void backtrack(int n, int k, List<Integer> record, Set<Integer> visited, int start) {
        if (record.size() >= k) {
            res.add(new ArrayList(record));
            return;
        }

        for (int i = start; i <= n; i++) {
            if (visited.contains(i)) {
                continue;
            }

            record.add(i);
            visited.add(i);

            backtrack(n, k, record, visited, i+1);
            
            record.remove(record.size()-1);
            visited.remove(i);
        }
    }
}

# 226. Invert Binary Tree(翻转二叉树)

直接递归遍历二叉树:

class Solution {
    public TreeNode invertTree(TreeNode root) {
        recursive(root);
        return root;
    }

    private void recursive(TreeNode n) {
        if (n == null) {
            return;
        }

        recursive(n.left);
        recursive(n.right);

        // reverse
        TreeNode tmp = n.left;
        n.left = n.right;
        n.right = tmp;
    }
}

# 114. Flatten Binary Tree to Linked List(二叉树展开为链表)

递归,利用stack存储right,左右置换。但空间复杂度不符合要求,此解为O(n)。

class Solution {
    public void flatten(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        recursive(stack, root);
    }

    private void recursive(Stack<TreeNode> stack, TreeNode node) {
        if (node == null) {
            return;
        }

        if (node.right != null) {
            stack.push(node.right);
            node.right = null;
        }

        if (node.left != null) {
            node.right = node.left;
            node.left = null;
        }

        if (node.right == null) {
            if (!stack.isEmpty()) {
                node.right = stack.pop();
            }
        }
        recursive(stack, node.right);
    }

}

# 724. Find Pivot Index(寻找数组的中心下标)

class Solution {
    public int pivotIndex(int[] nums) {
        int n = nums.length;

        int[] pre = new int[n+1];
        // init
        for (int i = 0; i < n; i++) {
            pre[i+1] = pre[i] + nums[i];
        }
        for (int i = 1; i < pre.length; i++) {
            if (pre[n]-pre[i] == pre[i-1]) {
                return i-1;
            }
        }
        return -1;
    }
}

# 523. Continuous Subarray Sum(连续的子数组和)

public class Solution {

    public boolean checkSubarraySum(int[] nums, int k) {
        int n = nums.length;
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, -1);
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += nums[i];
            if (k != 0) {
                sum = sum % k;
            }
            if (map.containsKey(sum)) {
                if (i - map.get(sum) > 1) {
                    return true;
                }
            } else {
                map.put(sum, i);
            }
        }

        return false;
    }
}

前缀和+哈希表:

class Solution {
    public boolean checkSubarraySum(int[] nums, int k) {
        int n = nums.length;
        int[] pre = new int[n+1];
        for (int i = 1; i <= n; i++) {
            pre[i] = pre[i-1] + nums[i-1];
        }
        Set<Integer> set = new HashSet<>();
        for (int i = 2; i <= n; i++) {
            set.add(pre[i-2]%k);
            if (set.contains(pre[i]%k)) {
                return true;
            }
        }
        return false;
    }
}

# 剑指 Offer 42. 连续子数组的最大和

同53题。

// 动归方程:dp[i]表示以nums[i]结尾的数,最大的子数组合。
// if dp[i-1] > 0, dp[i] = dp[i-1]+nums[i]
// if dp[i-1] <= 0, dp[i] = nums[i]
// 由于只依赖前一个最大子数组的合,所以可以进一步压缩空间,只用pre变量保存即可。
class Solution {
    public int maxSubArray(int[] nums) {
        int n = nums.length;
        // init
        int pre = nums[0];
        int max = nums[0];

        for (int i = 1; i < n; i++) {
            int tmp;
            if (pre > 0) {
                tmp = pre + nums[i];
            } else {
                tmp = nums[i];
            }
            max = Math.max(max, tmp);
            pre = tmp;
        }
        return max;
    }
}

重刷时,代码写得更简洁了

class Solution {
    public int maxSubArray(int[] nums) {
        int pre = 0, n = nums.length, max = Integer.MIN_VALUE;
        for (int i = 0; i < n; i++) {
            if (pre >= 0) {
                pre += nums[i];
            } else {
                pre = nums[i];
            }
            max = Math.max(max, pre);
        }
        return max;
    }
}

# 560. Subarray Sum Equals K(和为K的子数组)

class Solution {
    public int subarraySum(int[] nums, int k) {
        // presum[0] = 0;
        int[] presum = new int[nums.length+1];
        for (int i = 1; i < presum.length; i++) {
            presum[i] = presum[i-1] + nums[i-1];
        }
        // System.out.println("presum:"+ Arrays.toString(presum));
        int cnt = 0;
        HashMap<Integer, Integer> map = new HashMap<>();
        // 如果presum[i] - k==0,自然cnt要+1
        map.put(0, 1);
        for (int i = 1; i < presum.length; i++) {
            // System.out.println("presum[i] - k: "+(presum[i] - k));
            if (map.containsKey(presum[i] - k)) {
                cnt += map.get(presum[i] - k);
            }
            map.put(presum[i], map.getOrDefault(presum[i], 0) + 1);
        }

        return cnt;
    }
}

优化后(presum不需要使用数组存储):

class Solution {
    public int subarraySum(int[] nums, int k) {
        int presum = 0;
        int cnt = 0;
        HashMap<Integer, Integer> map = new HashMap<>();
        // 如果presum-k==0,自然cnt要+1
        map.put(0, 1);
        for (int i = 0; i < nums.length; i++) {
            presum += nums[i];
            if (map.containsKey(presum - k)) {
                cnt += map.get(presum - k);
            }
            map.put(presum, map.getOrDefault(presum, 0) + 1);
        }

        return cnt;
    }
}

go解法:

func subarraySum(nums []int, k int) int {
    n := len(nums)
    pre := make([]int, n)
    pre[0] = nums[0]
    
    for i := 1; i < n; i++ {
        pre[i] = pre[i-1] + nums[i]
    }
    
    var m = make(map[int]int)
    m[0] = 1
    cnt := 0
    for i := 0; i < n; i++ {
        cnt += m[pre[i]-k]
        m[pre[i]]++
    }
    return cnt
}

# 930. 和相同的二元子数组

class Solution {
    public int numSubarraysWithSum(int[] A, int S) {
        int[] presum = new int[A.length+1];
        for (int i = 1; i < presum.length; i++) {
            presum[i] = presum[i-1] + A[i-1];
        }

        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        int cnt = 0;
        for (int i = 1; i < presum.length; i++) {
            if (map.containsKey(presum[i]-S)) {
                cnt += map.get(presum[i]-S);
            }
            map.put(presum[i], map.getOrDefault(presum[i], 0)+1);
        }

        return cnt;
    }
}

时隔三个月的前缀和写法:

class Solution {
    public int numSubarraysWithSum(int[] nums, int goal) {
        int presum = 0;
        int cnt = 0;
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        for (int i = 0; i < nums.length; i++) {
            presum += nums[i];
            cnt += map.getOrDefault(presum-goal, 0);
            map.put(presum, map.getOrDefault(presum, 0)+1);
        }
        return cnt;
    }
}

双指针/滑动窗口:

class Solution {
    public int numSubarraysWithSum(int[] nums, int goal) {
        int l1 = 0, l2 = 0, r = 0;
        int n = nums.length;
        int cnt = 0;
        int s1 = 0, s2 = 0;
        while (r < n) {
            s1 += nums[r];
            s2 += nums[r];
            while (l1 <= r && s1 > goal) {
                s1 -= nums[l1];
                l1++;
            }

            while (l2 <= r && s2 >= goal) {
                s2 -= nums[l2];
                l2++;
            }
            r++;
            cnt += l2 - l1;
        }
        return cnt;
    }
}

# 1248. Count Number of Nice Subarrays(统计「优美子数组」)

前缀和:

class Solution {
    public int numberOfSubarrays(int[] nums, int k) {
        // pre[i]记录前面有多少个奇数
        int[] pre = new int[nums.length+1];
        for (int i = 1; i < pre.length; i++) {
            if (nums[i-1] % 2 != 0) {
                // 奇数
                pre[i] = pre[i-1] + 1;
            } else {
                pre[i] = pre[i-1];
            }
        }
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        int cnt = 0;
        for (int i = 1; i < pre.length; i++) {
            if (map.containsKey(pre[i]-k)) {
                cnt += map.get(pre[i]-k);
            }
            map.put(pre[i], map.getOrDefault(pre[i], 0)+1);
        }

        return cnt;
    }
}

相同逻辑的go实现:

func numberOfSubarrays(nums []int, k int) (res int) {
    n := len(nums)
    pre := make([]int, n+1)
    
    for i := 1; i <= n; i++ {
        odd := 0
        if nums[i-1] % 2 != 0 {
            odd = 1
        }
        pre[i] = pre[i-1] + odd
    }
    
    m := map[int]int{0:1}
    for _, val := range pre[1:] {
        res += m[val-k]
        m[val] = m[val] + 1
    }
    return
}

# 974. Subarray Sums Divisible by K(和可被 K 整除的子数组)

class Solution {
    public int subarraysDivByK(int[] A, int k) {

        int[] presum = new int[A.length+1];
        for (int i = 1; i < presum.length; i++) {
            presum[i] = presum[i-1] + A[i-1];
        }

        int cnt = 0;
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        for (int i = 1; i < presum.length; i++) {
            // 取余数,presum[i]%k会存在负数场景,因此通过+k再取余k,得到正数
            int remainder = (presum[i] % k + k) % k;
            if (map.containsKey(remainder)) {
                cnt += map.get(remainder);
            }
            map.put(remainder, map.getOrDefault(remainder, 0)+1);
        }

        return cnt;
    }
}

# 654. Maximum Binary Tree(最大二叉树)

class Solution {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        return recursive(nums, 0, nums.length-1);
    }

    private TreeNode recursive(int[] nums, int left, int right) {
        if (left > right || left < 0 || right > nums.length-1) {
            return null;
        }

        // find index of max value
        int index = -1;
        int max = Integer.MIN_VALUE;
        for (int i = left; i <= right; i++) {
            if (nums[i] > max) {
                max = nums[i];
                index = i;
            }
        }

        TreeNode root = new TreeNode(nums[index]);
        root.left = recursive(nums, left, index-1);
        root.right = recursive(nums,  index+1, right);

        return root;
    }
}

# 106. Construct Binary Tree from Inorder and Postorder Traversal(从中序与后序遍历序列构造二叉树)

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        return recursive(postorder, 0, postorder.length-1, inorder, 0, inorder.length-1);
    }

    private TreeNode recursive(int[] post, int pl, int pr, int[] in, int il, int ir) {
        if (pl > pr || il > ir || pl < 0 || pr >= post.length || il < 0 || ir >= in.length) {
            return null;
        }

        int root = post[pr];
        TreeNode node = new TreeNode(root);
        // 在中序遍历数组中找到root节点
        int index = -1;
        for (int i = il; i <= ir; i++) {
            if (in[i] == root) {
                index = i;
                break;
            }
        }
        int lsize = index - il;
        node.left = recursive(post, pl, pl+lsize-1, in, il, index-1);
        node.right = recursive(post, pl+lsize, pr-1, in, index+1, ir);

        return node;
    }
}

# 652. Find Duplicate Subtrees(寻找重复的子树)

// 后序遍历,获取子树的序列化字串,存入到map中,用来判断是否之前已存在重复
class Solution {
    public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
        HashMap<String, Integer> map = new HashMap<>();
        List<TreeNode> res = new ArrayList<>();
        recursive(root, res, map);
        return res;
    }

    private String recursive(TreeNode node, List<TreeNode> res, HashMap<String, Integer> map) {
        if (node == null) {
            return "#";
        }

        String left = recursive(node.left, res, map);
        String right = recursive(node.right, res, map);

        String serialize = left +","+ right +","+ node.val;
        if (map.getOrDefault(serialize, 0) == 1) {
            res.add(node);
            map.put(serialize, map.get(serialize)+1);
        } else {
            map.put(serialize, map.getOrDefault(serialize, 0)+1);
        }

        return serialize;
    }

}

# 897. Increasing Order Search Tree(递增顺序搜索树)

中序遍历,将结果记录在linkedList中:

class Solution {
    public TreeNode increasingBST(TreeNode root) {
        LinkedList<TreeNode> q = new LinkedList<>();

        recursive(root, q);
        // 清空最后节点的左右分支,防止TreeNode成环。
        q.peekLast().left = null;
        q.peekLast().right = null;

        return q.poll();
    }

    private TreeNode recursive(TreeNode node, LinkedList<TreeNode> q) {
        if (node == null) return null;

        recursive(node.left, q);

        if (!q.isEmpty()) {
            TreeNode cur = q.peekLast();
            cur.right = node;
            cur.left = null;
        }
        q.addLast(node);

        recursive(node.right, q);
        return node;
    }
}

# 5740. 所有元音按顺序排布的最长子字符串(第 238 场周赛)

第三题:

class Solution {
    public int longestBeautifulSubstring(String word) {
        // 记录种类数,种类不到5个,则不记录为·美丽字串长度·
        int type = 0;
        // 当前字符大于上一个字符,type++
        // 当前字符等于上一个字符,right++
        // 当前字符小于上一个字符,right++,type清零
        // 当type为0时,必须是a开头,
        int left = 0, right = 0;
        char pre = 'A';
        int max = 0;
        while (right < word.length()) {
            char w = word.charAt(right);
            // System.out.println("w:"+w+"; type:"+type);
            if (type == 0) {
                if (w != 'a') {
                    left++;
                } else {
                    type++;
                }
                right++;
            } else {
                if (w > pre) {
                    type++;
                    right++;
                } else if (w < pre) {
                    // System.out.println("max:"+max+"; right :"+(right-1)+"; left:"+left);
                    if (type == 5) {
                        
                        max = Math.max(max, (right-1)-left+1);
                    }
                    type = 0;
                    left = right;
                    // System.out.println("nums[left]:"+word.charAt(left));
                } else {
                    right++;
                }
                
            }
            pre = w;
        }
        if (type == 5) {
            // System.out.println("max:"+max+"; right :"+(right-1)+"; left:"+left);
            max = Math.max(max, (right-1)-left+1);
        }
        return max;
    }
}

# 1838. 最高频元素的频数(第 238 场周赛)

第二题:

class Solution {
    public int maxFrequency(int[] nums, int k) {
        // 排序
        Arrays.sort(nums);
        // 先求出前缀和
        int[] presum = new int[nums.length+1];
        for (int i = 1; i < presum.length; i++) {
            presum[i] = nums[i-1] + presum[i-1];
        }
        // 滑动窗口
        // 1.窗口内容和 > 窗口最大值*数量,left++
        // 2.小于,right++;left==right,right++
        // while condition:right < nums.length
        int max = 1;
        int left = 1, right = 1;
        while (right < presum.length) {
            if (left == right) {
                right++;
                continue;
            }
            int sum = presum[right]-presum[left-1];
            int cnt = right-left+1;
            if (nums[right-1]*cnt - sum > k) {
                left++;
            } else {
                right++;
                max = Math.max(max, cnt);
            }
        }
        
        return max;
    }
}

排序+滑动窗口:

class Solution {
    public int maxFrequency(int[] nums, int k) {
        Arrays.sort(nums);

        int n = nums.length;
        int l = 0;
        int max = 1;
        long sum = 0;
        for (int r = 1; r < n; r++) {
            sum += (nums[r]-nums[r-1]) * (r-l);
            while (sum > k) {
                sum -= (nums[r]-nums[l]);
                l++;
            }

            max = Math.max(max, r-l+1);
        }

        return max;
    }
}

# 1837. K 进制表示下的各位数字总和(第 238 场周赛)

第一题:

class Solution {
    public int sumBase(int n, int k) {
        int res = 0;
        while (n >= k) {
			int r = n % k;
            n = n / k;
            res += r;
        }
        res += n;
        
        return res;
    }
}

# 538. Convert BST to Greater Tree(把二叉搜索树转换为累加树)

同一题:1038. 把二叉搜索树转换为累加树

// 修改二叉树遍历顺序,从右子树-》根节点-》左子树,累加计数,并返回给上层继续累加即可。
class Solution {
    public TreeNode convertBST(TreeNode root) {
        recursive(root, 0);
        return root;
    }

    private int recursive(TreeNode node, int pre) {
        if (node == null) return pre;

        // right
        pre = recursive(node.right, pre);
        // mid
        node.val += pre;
        pre = node.val;
        // left
        pre = recursive(node.left, pre);

        return pre;
    }
}

# 897. 递增顺序搜索树

使用queue实现:

class Solution {
    public TreeNode increasingBST(TreeNode root) {
        LinkedList<TreeNode> q = new LinkedList<>();

        recursive(root, q);

        q.peekLast().left = null;
        q.peekLast().right = null;

        return q.poll();
    }

    private TreeNode recursive(TreeNode node, LinkedList<TreeNode> q) {
        if (node == null) return null;

        recursive(node.left, q);

        if (!q.isEmpty()) {
            TreeNode cur = q.peekLast();
            cur.right = node;
            cur.left = null;
        }
        q.addLast(node);
        recursive(node.right, q);

        return node;
    }
}

不借助数据结构,原地实现(最优解):

class Solution {
    TreeNode p;

    public TreeNode increasingBST(TreeNode root) {
        TreeNode dummy = new TreeNode(-1);
        p = dummy;
        recursive(root);
        return dummy.right;
    }

    // 中序遍历,递归到第一个节点时,一定是在中间执行中序逻辑。
    private void recursive(TreeNode node) {
        if (node == null) return;

        recursive(node.left);

        p.right = node;
        node.left = null;
        p = p.right;

        recursive(node.right);
    }
}

# 1011. 在 D 天内送达包裹的能力

class Solution {
    public int shipWithinDays(int[] ws, int d) {
        int max = 0, sum = 0;
        for (int w : ws) {
            max = Math.max(max, w);
            sum += w;
        }
        int l = max, r = sum;
        while (l < r) {
            int mid = l + r >> 1;
            if (check(ws, mid, d)) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        return r;
    }

    // 贪心 ws 重量数组  t 运载能力 d 天数
    boolean check(int[] ws, int t, int d) {
        // wi 重量数组下标
        int wi = 0;
        for (int i = 0; i < d; i++) {
            for (int sum = t; wi < ws.length && sum - ws[wi] >= 0; ) {
                sum -= ws[wi++];
            }
        }
        // d 天走完,是否走完ws重量数组
        return wi == ws.length;
    }

    // 宫水三叶题解
    boolean check1(int[] ws, int t, int d) {
        int n = ws.length;
        int cnt = 1;
        for (int i = 1, sum = ws[0]; i < n; sum = 0, cnt++) {
            while (i < n && sum + ws[i] <= t) {
                sum += ws[i];
                i++;
            }
        }
        return cnt - 1 <= d;
    }
}

# 1373. 二叉搜索子树的最大键值和

class Solution {
    private int max = 0;
    // 看子树是否为bst;
    // 后序遍历记录子树节点最大和以及节点
    public int maxSumBST(TreeNode root) {
        func(root);
        return max;
    }

    // 返回结果 res[0] 是否为bst子树 res[1] 子树最大值 res[2] 子树最小值 res[3] 子树所有节点和
    private int[] func(TreeNode node) {
        if (node == null) return new int[]{1, Integer.MIN_VALUE, Integer.MAX_VALUE, 0};

        int[] l = func(node.left);
        int[] r = func(node.right);

        // 如果左右子树皆为bst,根据左右节点大小,判断当前是否为bst
        // 当前节点为bst子树,计算总和,与max比较再更新。
        // 叶子节点为bst子树,返回true
        int[] res = new int[4];
        if (node.left == null && node.right == null) {
            max = Math.max(max, node.val);
            return new int[]{1, node.val, node.val, node.val};
        } else if (l[0] == 1 && r[0] == 1 && l[1] < node.val && node.val < r[2]) {
            int tmax = l[3]+r[3]+node.val;
            max = Math.max(max, tmax);
            return new int[]{1, Math.max(r[1], node.val), Math.min(l[2], node.val), tmax};
        }
        return new int[]{0, 0, 0, 0};
    }
}

# 96. 不同的二叉搜索树

class Solution {
    int[][] memo;
    public int numTrees(int n) {
        memo = new int[n+1][n+1];
        return count(1, n);
    }

    private int count(int low, int high) {
        if (low > high) return 1;
        // 查备忘录
        if (memo[low][high] != 0) {
            return memo[low][high];
        }
        int res = 0;
        for (int i = low; i <= high; i++) {
            int left = count(low, i-1);
            int right = count(i+1, high);
            res += left * right;
        }
        // 将结果存入备忘录
        memo[low][high] = res;

        return res;
    }
}

动态规划

class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n+1];
        dp[1] = 1;
        dp[0] = 1;
        for (int i = 2; i <= n; i++) {
            for (int k = 1; k <= i; k++) {
                dp[i] += (dp[k-1]*dp[i-k]);
            }
        }
        return dp[n];
    }
}

# 95. 不同的二叉搜索树 II

class Solution {
    public List<TreeNode> generateTrees(int n) {
        if (n == 0) return new LinkedList<>();
        return gen(1, n);
    }

    private List<TreeNode> gen(int low, int high) {
        List<TreeNode> res = new LinkedList<>();
        if (low > high) {
            res.add(null);
            return res;
        }

        for (int mid = low; mid <= high; mid++) {
            List<TreeNode> left = gen(low, mid-1);
            List<TreeNode> right = gen(mid+1, high);
            for (TreeNode l : left) {
                for (TreeNode r : right) {
                    TreeNode root = new TreeNode(mid);
                    root.left = l;
                    root.right = r;
                    res.add(root);
                }
            }
        }

        return res;
    }
}

# 938. 二叉搜索树的范围和

class Solution {
    int sum = -1;
    boolean end = false;
    public int rangeSumBST(TreeNode root, int low, int high) {
        f(root, low, high);
        return sum;
    }

    private void f(TreeNode node, int low, int high) {
        if (end || node == null) return;

        f(node.left, low, high);

        if (sum == -1 && node.val == low) {
            sum = node.val;
        } else if (sum != -1 && !end) {
            sum += node.val;
            if (node.val == high) {
                end = true;
            }
        }

        f(node.right, low, high);
    }
}

# 209. 长度最小的子数组

朴素的nlogn做法,先求前缀和,再以每个数为left或right做二分:

class Solution {
    int N = Integer.MAX_VALUE;
    public int minSubArrayLen(int target, int[] nums) {
        int n = nums.length;
        int[] pre = new int[n+1];
        for (int i = 1; i <= n; i++) {
            pre[i] = pre[i-1] + nums[i-1];
        }

        int min = N;
        for (int i = 1; i <= n; i++) {
            min = Math.min(min, binary(pre, i, target));
        }
        return min == N ? 0 : min;
    }

    public int binary(int[] pre, int end, int t) {
        int l = 0, r = end;
        while (l < r) {
            int mid = l + ((r-l+1)>>1);
            if (pre[end]-pre[mid] >= t) {
                l = mid;
            } else {
                r = mid-1;
            }
        }

        return pre[end]-pre[r] >= t ? end-r : N;
    }
}

最优解,滑动窗口:

// 滑动窗口主要关注:
// 1.什么时候移动left:当满足sum>=target时
// 2.什么时候移动right:
// 3.什么时候计算最小长度
class Solution {
    int N = Integer.MAX_VALUE;
    public int minSubArrayLen(int target, int[] nums) {
        int n = nums.length;
        int sum = 0, l = 0, r = 0;
        int min = N;
        while (r < n) {
            sum += nums[r++];
            while (sum >= target) {
                min = Math.min(min, r-l);
                sum -= nums[l++];
            }
        }
        return min == N ? 0 : min;
    }
}

# 222. 完全二叉树的节点个数

class Solution {
    public int countNodes(TreeNode root) {
        int hl = 0, hr = 0;
        TreeNode l = root, r = root;

        while (l != null) {
            l = l.left;
            hl++;
        }

        while (r != null) {
            r = r.right;
            hr++;
        }

        if (hl == hr) {
            return (int)Math.pow(2, hl) - 1;
        }

        return countNodes(root.left) + countNodes(root.right) + 1;
    }
}

# 35. 搜索插入位置

// 题意:找到第一个大于等于target的位置
// 搜索左侧边界
class Solution {
    public int searchInsert(int[] nums, int target) {
        int l = 0, r = nums.length;
        while (l < r) {
            int mid = l + ((r - l) >> 1);
            if (nums[mid] == target) {
                r = mid;
            } else if (nums[mid] > target) {
                r = mid;
            } else if (nums[mid] < target) {
                l = mid+1;
            }
        }
        // 不能返回r,nums中所有数都小于target时,一直走l=mid+1逻辑,r是不变的。
        return l;
    }
}
func searchInsert(nums []int, target int) int {
    n := len(nums)
    l, r := 0, n
    for l < r {
        mid := l + ((r-l)>>1)
        if target == nums[mid] {
            return mid
        } else if target < nums[mid] {
            r = mid
        } else {
            l = mid+1
        }
    }
    return r
}

# 704. 二分查找

class Solution {
    public int search(int[] nums, int target) {
        // while条件是小于,right可以等于nums.length
        int l = 0, r = nums.length;
        while (l < r) {
            int mid = l + ((r-l) >> 1);
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                r = mid;
            } else if (nums[mid] < target) {
                l = mid+1;
            }
        }
        return -1;
    }
}
class Solution {
    public int search(int[] nums, int target) {
        int left = 0, right = nums.length-1;
        while (left < right) {
            int mid = left + ((right-left)>>1);
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        // 如果right==target,mid会一直右移,直到等于right,但while是小于,mid计算落点是靠左的,所以会漏掉right,需要最后进行判断。
        return nums[right] == target ? right : -1;
    }
}
class Solution {
    public int search(int[] nums, int target) {
        int left = 0, right = nums.length-1;
        // while条件是小于等于时,right和left的移位不能直接等于mid,否则会出现死循环。
        // 并且right起点可以从nums.length-1开始,最终也不会遗漏每一个可能的下标。
        while (left <= right) {
            int mid = left + ((right-left)>>1);
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid -1;
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }
}

# 718. 最长重复子数组

动态规划:

class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int m = nums2.length;
        int[][] dp = new int[n+1][m+1];
        int res = 0;

        for (int i = n-1; i >= 0; i--) {
            for (int j = m-1; j >= 0; j--) {
                dp[i][j] = nums1[i] == nums2[j] ? dp[i+1][j+1] + 1 : 0;
                res = Math.max(res, dp[i][j]);
            }
        }

        return res;
    }
}

# 633. 平方数之和

brute force:(超时)

class Solution {
    public boolean judgeSquareSum(int c) {
        if (c == 0) return true;
        // 1-c
        for (int i = 1; i*i <= c; i++) {
            int a = i*i;
            int b = c - a;
            if (Math.sqrt((double)b) % 1 == 0) {
                return true;
            }
        }
        return false;
    }
}

二分法:

class Solution {
    public boolean judgeSquareSum(int c) {
        int a = 0, b = (int) Math.sqrt(c);

        while (a <= b) {
            int sum = a*a + b*b;
            if (sum == c) {
                return true;
            } else if (sum > c) {
                b--;
            } else if (sum < c) {
                a++;
            }
        }
        return false;
    }
}

# 378. 有序矩阵中第 K 小的元素

brute force:

class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        int[] res = new int[matrix.length*matrix[0].length];
        int i = 0;
        for (int[] rows : matrix) {
            for (int n : rows) {
                res[i++] = n;
            }
        }
        Arrays.sort(res);
        return res[k-1];
    }
}

归并算法: 时间竟然比暴力法还慢,可能是数据量的问题?

class Solution {
    public int kthSmallest(int[][] ma, int k) {
        PriorityQueue<int[]> pq = new PriorityQueue<>(new Comparator<int[]>() {
            public int compare(int[] a, int[] b) {
                return a[0] - b[0];
            }
        });

        // 把每排第一个放入queue,放入对象为三元素数组,包含信息:value、row坐标、colum坐标,以便从queue中取出时求下一个。
        for (int i = 0; i < ma.length; i++) {
            pq.offer(new int[]{ma[i][0], i, 0});
        }

        // 初始化时放入了第一个,后序只要放入k-1个,queue中即有k个元素了。
        for (int i = 0; i < k-1; i++) {
            int[] po = pq.poll();
            if (po[2]+1 < ma[0].length) {
                pq.offer(new int[]{ma[po[1]][po[2]+1], po[1], po[2]+1});
            }
        }

        return pq.poll()[0];
    }
}

# 403. 青蛙过河

动态规划:未优化版本

class Solution {
    public boolean canCross(int[] stones) {
        int n = stones.length;
        boolean[][] dp = new boolean[n][n];
        // init
        dp[0][0] = true;
        
        for (int i = 1; i < n; i++) {
            
            for (int j = i-1; j >= 0; j--) {
                int k = stones[i] - stones[j];
                // why?
                if (k > j+1) {
                    break;
                }
                dp[i][k] = dp[j][k] || dp[j][k-1] || dp[j][k+1];
            }
        }
        
        for (boolean b : dp[n-1]) {
            if (b) return true;
        }
        return false;
    }
}

# 137. 只出现一次的数字 II

class Solution {
    public int singleNumber(int[] nums) {
        Map<Integer, Integer> map = new HashMap<>();

        for (int n : nums) {
            Integer t = map.get(n);
            if (t == null) t = 0;
            map.put(n, t+1);
        }

        int res = -1;
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if (entry.getValue()!=3) {
                res = entry.getKey();
                break;
            }
        }
        return res;
    }
}

# 690. 员工的重要性

// bfs
class Solution {
    public int getImportance(List<Employee> employees, int id) {
        // list找到对应的id,存入queue中,之后循环即可,题目没有环出现的可能
        int res = 0;
        Queue<Integer> q = new LinkedList<>();
        q.offer(id);
        
        while (!q.isEmpty()) {
            int up = q.poll();
            for (Employee emp : employees) {
                if (emp.id == up) {
                    res += emp.importance;
                    for (int sub : emp.subordinates) {
                        q.offer(sub);
                    }
                    break;
                }
            }          
        }
        return res;
    }
}

使用hash做提前优化: 代码可以继续优化,map可以直接用Employee做value,由于是存储的引用,并不会多占用过多内存,时间上更优(复杂度上没区别)。

class Solution {
    public int getImportance(List<Employee> emps, int id) {
        Map<Integer, Employee> map = new HashMap<>();
        int res = 0;
        Queue<Integer> q = new LinkedList<>();
        q.offer(id);
        for (Employee emp : emps) {
            map.put(emp.id, emp);
        }
        
        while (!q.isEmpty()) {
            int up = q.poll();
            Employee emp = map.get(up);
            res += emp.importance;
            for (int sub : emp.subordinates) {
                q.offer(sub);
            }     
        }
        return res;
    }
}

使用dfs效率更佳:

class Solution {
    public int getImportance(List<Employee> emps, int id) {
        Map<Integer, Employee> map = new HashMap<>();
        for (Employee emp : emps) {
            map.put(emp.id, emp);
        }
        return dfs(map, id);
    }
    
    private int dfs(Map<Integer, Employee> map, int id) {
        Employee emp = map.get(id);
        int res = emp.importance;
        for (int sub : emp.subordinates) {
            res += dfs(map, sub);
        }
        return res;
    }
}

# 剑指 Offer 13. 机器人的运动范围

dfs,向右边和下边搜索,利用二维数组去重

class Solution {
    int cnt = 0;
    int y;
    int x;
    boolean[][] valid;
    public int movingCount(int m, int n, int k) {
        y = m;
        x = n;
        valid = new boolean[m][n];
        dfs(0, 0, k);
        return cnt;
    }

    private void dfs(int m, int n, int k) {
        // check 边界\去重、必须小于等于k
        if (m < 0 || n < 0 || m >= y || n >= x) {
            return;
        }
        if (invalid(m, n, k)) {
            return;
        }
        if (valid[m][n]) {
            return;
        }
        cnt++;
        valid[m][n] = true;
        // 右、下
        dfs(m, n+1, k);
        dfs(m+1, n, k);
    }

    private boolean invalid(int m, int n, int k) {
        int sum = 0;
        while (m != 0) {
            sum += m % 10;
            m = m / 10;
        }

        while (n != 0) {
            sum += n % 10;
            n = n / 10;
        }
        return sum > k;
    }
}

# 554. 砖墙

前缀和:

class Solution {
    public int leastBricks(List<List<Integer>> wall) {
        // <sum, cnt>
        HashMap<Integer, Integer> map = new HashMap<>();
        int max = 0;
        for (List<Integer> w : wall) {
            int sum = 0;
            // 不要加入最后一个元素计算前缀和,因为边界通路不计算在内
            for (int i = 0; i < w.size()-1; i++) {
                sum += w.get(i);
                int cnt = map.getOrDefault(sum, 0)+1;
                map.put(sum, cnt);
                // System.out.println("sum:"+sum+"; cnt:"+cnt);
                max = Math.max(max, cnt);
            }
        }
        return wall.size() - max;
    }
}

# 1847. 最近的房间(第 51 场双周赛)

周赛第四题 treeMap解,但时间复杂度接近暴力,无法通过:

class Solution {
    public int[] closestRoom(int[][] rooms, int[][] q) {
        TreeMap<Integer, List<int[]>> map = new TreeMap<Integer, List<int[]>>(
            new Comparator<Integer>() {
                public int compare(Integer obj1, Integer obj2) {
                    return obj1.compareTo(obj2);
                }
            }
        );
        
        for (int[] r : rooms) {
            List<int[]> list = map.get(r[1]);
            if (list == null) {
                list = new ArrayList<>();
            }
            list.add(r);
            map.put(r[1], list);
        }
        
        int[] res = new int[q.length];
        for (int i = 0; i < q.length; i++) {
            Map<Integer, List<int[]>> smap = map.tailMap(q[i][1]);
            
            int min = Integer.MAX_VALUE;
            int index = Integer.MAX_VALUE;
            
            for (Map.Entry<Integer, List<int[]>> entry : smap.entrySet()) {
                Integer k = entry.getKey();
                List<int[]> vs = entry.getValue();
                for (int[] v : vs) {
                    if (Math.abs(q[i][0]-v[0]) < min) {
                        index = v[0];
                        min = Math.abs(q[i][0]-v[0]);
                    } else if (Math.abs(q[i][0]-v[0]) == min) {
                        index = Math.min(index, v[0]);
                        min = Math.abs(q[i][0]-v[0]);
                    }
                }
                
            }
            res[i] = index == Integer.MAX_VALUE ? -1 : index;
        }
                    
        return res;    

    }
}

其他大佬的解答:

class Solution {

	public int[] closestRoom(int[][] rooms, int[][] queries) {
		Integer[] index = new Integer[queries.length];
		for (int i = 0; i < index.length; i++) {
			index[i] = i;
		}
		// rooms 按照size从大到小排序
		Arrays.sort(rooms, (a, b) -> b[1] - a[1]);
		// 按照queries size从大到小排序
		Arrays.sort(index, (a, b) -> queries[b][1] - queries[a][1]);
		TreeSet<Integer> set = new TreeSet<>();
		int[] result = new int[queries.length];
		for (int i = 0, j = 0; i < index.length; i++) {
			// 所有size大于query的roomId放入treeSet中
			while (j < rooms.length && rooms[j][1] >= queries[index[i]][1]) {
				set.add(rooms[j++][0]);
			}
			// 找到queries[i] 对应的最近的小值和大值
			Integer floor = set.floor(queries[index[i]][0]), ceiling = set.ceiling(queries[index[i]][0]);
			// 没有则-1
			if (floor == null && ceiling == null) {
				result[index[i]] = -1;
			} else {
				// 绝对值判断大小
				if (queries[index[i]][0] - (floor == null ? -999999999 : floor) 
					<= (ceiling == null ? 999999999 : ceiling) - queries[index[i]][0]) {
					result[index[i]] = floor;
				} else {
					result[index[i]] = ceiling;
				}
			}
		}
		return result;
	}
}

# 1846. 减小和重新排列数组后的最大元素(第 51 场双周赛)

周赛第三题

class Solution {
    public int maximumElementAfterDecrementingAndRearranging(int[] arr) {
        Arrays.sort(arr);
        if (arr[0] != 1) {
            arr[0] = 1;
        }
        int max = 0;
        for (int i = 1; i < arr.length; i++) {
            int temp = Math.abs(arr[i]-arr[i-1]);
            if (temp > 1) {
                arr[i] = arr[i] - temp + 1;
            }
        }
        return arr[arr.length-1];
    }
}

每日一题时再做,写法清晰很多了:

class Solution {
    public int maximumElementAfterDecrementingAndRearranging(int[] arr) {
        Arrays.sort(arr);
        int val = 1;
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > val) {
                val++;
            }
        }
        return val;
    }
}

# 1845. 座位预约管理系统(第 51 场双周赛)

周赛第二题

class SeatManager {
    PriorityQueue<Integer> pq;

    public SeatManager(int n) {
        pq = new PriorityQueue<>(new Comparator<Integer>() {
            public int compare(Integer a, Integer b) {
                return a-b;
            }
        });
        
        for (int i = 1; i <= n; i++) {
            pq.offer(i);
        }
    }
    
    public int reserve() {
        return pq.poll();
    }
    
    public void unreserve(int s) {
        pq.offer(s);
    }
}

# 1844. 将所有数字用字符替换(第 51 场双周赛)

周赛第一题

class Solution {
    public String replaceDigits(String s) {
        char[] cs = s.toCharArray();
        // System.out.println(Arrays.toString(cs));
        for (int i = 0; i < cs.length-1; i+=2) {
            cs[i+1] = shift(cs[i], cs[i+1]);
        }
        // System.out.println(Arrays.toString(cs));
        return new String(cs);
    }
    
    private char shift(char x, char i) {
        // System.out.println(x);
        // System.out.println((int)i);
        return (char)(x+(i-48));
    }
}

# 1849. 将字符串拆分为递减的连续值(第 239 场周赛)

第二题

import java.math.BigInteger;
class Solution {
    private String s;
    public boolean splitString(String ss) {
        s = ss;
        for (int i = 0; i < s.length()-1; i++) {
            String pre = s.substring(0, i+1);
            int start = i+1;
            
            // 存在子问题,使用递归
            if (func(pre, start)) {
                return true;
            }
        }
        
        return false;
    }
    
    private boolean func(String pre, int start) {
        if (start >= s.length()) {
            return true;
        }
        
        BigInteger p = new BigInteger(pre);
        for (int i = start+1; i <= s.length(); i++) {
            // System.out.println(p+"; "+s.substring(start, i));
            if (p.subtract(BigInteger.ONE).equals(new BigInteger(s.substring(start, i))) && func(s.substring(start, i), i)) {
                return true;
            }
        }
        
        return false;
    }
}

# 1848. 到目标元素的最小距离(第 239 场周赛)

第一题

class Solution {
    public int getMinDistance(int[] nums, int target, int start) {
        int l = start, r = start;
        while (l >= 0 || r < nums.length) {
            if (l >= 0 && nums[l] == target) {
                return start - l;
            } else if (r < nums.length && nums[r] == target) {
                return r - start;
            } 
            l--;
            r++;
        }
        return -1;
    }
}

# 416. 分割等和子集

动态规划:

class Solution {
    public boolean canPartition(int[] nums) {
        // sum = sum/2
        // nums[i]<=j dp[i][j] = dp[i-1][j] | dp[i-1][j-nums[i]]
        // nums[i]>j = dp[i-1][j];
        int n = nums.length;
        int sum = 0;
        int max = Integer.MIN_VALUE;
        for (int i : nums) {
            sum += i;
            max = Math.max(max, i);
        }
        if (sum % 2 != 0) return false; 
        sum /= 2;
        if (max > sum) return false;
        boolean[][] dp = new boolean[n][sum+1];
        
        for (int i = 0; i < n; i++) {
            dp[i][0] = true;
        }
        for (int j = 1; j < sum; j++) {
            if (nums[0] == j) {
                dp[0][j] = true;
            }
        }
        
        // System.out.println("sum:"+sum+";");
        // System.out.println("array:"+Arrays.deepToString(dp));
        
        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= sum; j++) {
                if (nums[i] <= j) {
                    dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]];
                } else if (nums[i] > j) {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        // System.out.println("array:"+Arrays.deepToString(dp));
        return dp[n-1][sum];
    }
}

# 516. 最长回文子序列

动态规划:

class Solution {
    public int longestPalindromeSubseq(String s) {
        // dp[i][j] 
        int n = s.length();
        int[][] dp = new int[n][n];
        
        for (int i = n-1; i >= 0; i--) {
            
            for (int j = i; j < n; j++) {
                if (i == j) {
                    dp[i][j] = 1;
                } else if (s.charAt(i) == s.charAt(j)) {
                    dp[i][j] = dp[i+1][j-1] + 2;
                } else if (s.charAt(i) != s.charAt(j)) {
                    dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
                }
            }
        }
        
        return dp[0][n-1];
    }
}

# 377. 组合总和 Ⅳ

暴力解:递归

class Solution {
    public int combinationSum4(int[] nums, int target) {
        if (target == 0) {
            return 1;
        } else if (target < 0) {
            return 0;
        }
        
        int res = 0;
        for (int n : nums) {
            res += combinationSum4(nums, target-n);
        }
        
        return res;
    }
}

记忆化递归(备忘录):时间超过100%

class Solution {
    private int[] dp;
    public int combinationSum4(int[] nums, int target) {
        dp = new int[target+1];
        Arrays.fill(dp, -1);
        return func(nums, target);
    }
    
    
    public int func(int[] nums, int target) {
        if (target == 0) {
            return 1;
        } else if (target < 0) {
            return 0;
        }
        
        if (dp[target] != -1) return dp[target];
        
        int res = 0;
        for (int n : nums) {
            int next = target-n;
            if (next < 0) continue;
            res += (dp[next] = func(nums, next));
        }
        return res;
    }
}

动态规划:状态压缩

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target+1];
        // 题目是需要返回组合数,target为0时,是因为将target从大到小减小递归,最终到0
        // 因此需要计数+1
        dp[0] = 1;
        for (int i = 1; i <= target; i++) {
            for (int num : nums) {
                if (i - num >= 0) {
                    dp[i] += dp[i-num];
                }
            }
        }
        return dp[target];
    }
}

# 208. 实现 Trie (前缀树)

class Trie {
    boolean end;
    Trie[] next;

    /** Initialize your data structure here. */
    public Trie() {
        next = new Trie[26];
        end = false;
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        Trie cur = this;
        Trie[] list = cur.next;
        for (char c : word.toCharArray()) {
            if (list[c-'a'] == null) {
                list[c-'a'] = new Trie();
            }
            cur = list[c-'a'];
            list = cur.next;
        }
        cur.end = true;
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        Trie cur = this;
        Trie[] list = cur.next;
        for (char c : word.toCharArray()) {
            if (list[c-'a'] == null) {
                return false;
            }
            cur = list[c-'a'];
            list = cur.next;
        }
        return cur.end;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        Trie cur = this;
        Trie[] list = cur.next;
        for (char c : prefix.toCharArray()) {
            if (list[c-'a'] == null) {
                return false;
            }
            cur = list[c-'a'];
            list = cur.next;
        }
        return true;
    }
}

两种模板trie树实现方式: 数组方式:

class Trie {
    int N = 100009;
    int[][] trie;
    int[] count;
    int index;

    /** Initialize your data structure here. */
    public Trie() {
        trie = new int[N][26];
        count = new int[N];
        index = 0;
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        int pos = 0;
        for (int i = 0; i < word.length(); i++) {
            int c = (int) (word.charAt(i)-'a');
            if (trie[pos][c] == 0) {
                trie[pos][c] = ++index;                
            }
            pos = trie[pos][c];
        }
        count[pos]++;
        // System.out.println(Arrays.deepToString(trie));
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        int pos = 0;
        for (int i = 0; i < word.length(); i++) {
            int c = (int) (word.charAt(i)-'a');
            if (trie[pos][c] == 0) {
                return false;
            }
            pos = trie[pos][c];
        }
        return count[pos] != 0;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        int pos = 0;
        for (int i = 0; i < prefix.length(); i++) {
            int c = (int) (prefix.charAt(i)-'a');
            if (trie[pos][c] == 0) {
                // System.out.println("pos:"+pos+"; c:"+(char)(c+'a'));
                return false;
            }
            pos = trie[pos][c];
        }
        return true;
    }
}

TrieNode方式:

class Trie {
    class TrieNode {
        boolean end;
        TrieNode[] ns = new TrieNode[26];
    }

    TrieNode root;

    /** Initialize your data structure here. */
    public Trie() {
        root = new TrieNode();
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        TrieNode p = root;
        for (int i = 0; i < word.length(); i++) {
            int c = (int) (word.charAt(i)-'a');
            if (p.ns[c] == null) {
                p.ns[c] = new TrieNode();
            }
            p = p.ns[c];
        }
        p.end = true;
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        TrieNode p = root;
        for (int i = 0; i < word.length(); i++) {
            int c = (int) (word.charAt(i)-'a');
            if (p.ns[c] == null) {
                return false;
            }
            p = p.ns[c];
        }
        return p.end;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        TrieNode p = root;
        for (int i = 0; i < prefix.length(); i++) {
            int c = (int) (prefix.charAt(i)-'a');
            if (p.ns[c] == null) {
                return false;
            }
            p = p.ns[c];
        }
        return true;
    }
}

# 363. 矩形区域不超过 K 的最大数值和

二维前缀和+二分查找,思路如下图:

1622356121780

class Solution {
    public int maxSumSubmatrix(int[][] matrix, int k) {
        // 前缀和
        int[][] ma = matrix;
        int n = ma.length, m = ma[0].length;
        int[][] pre = new int[n+1][m+1];
        for (int r = 1; r <= n; r++) {
            for (int c = 1; c <= m; c++) {
                pre[r][c] = pre[r][c-1] + pre[r-1][c] - pre[r-1][c-1] + ma[r-1][c-1];
            }
        }
        
        // 枚举 top bot
        int res = Integer.MIN_VALUE;
        for (int top = 1; top <= n; top++) {
            for (int bot = top; bot <= n; bot++) {
                TreeSet<Integer> ts = new TreeSet<>();
                ts.add(0);
                for (int c = 1; c <= m; c++) {
                    int right = pre[bot][c] - pre[top-1][c];
                    Integer left = ts.ceiling(right - k);
                    if (left != null) {
                        res = Math.max(res, right - left);
                    }
                    ts.add(right);
                }
            }
        }
        
        return res;
    }
}

# 368. 最大整除子集

整除的传递性+动态规划:

class Solution {
    public List<Integer> largestDivisibleSubset(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        int[] dp = new int[n];
        dp[0] = 1;
        int max = 1;
        for (int i = 1; i < n; i++) {
            int div = nums[i];
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (div % nums[j] == 0) {
                    dp[i] = Math.max(dp[i], dp[j]+1);
                }
            }
            max = Math.max(max, dp[i]);
        }
        // System.out.println(Arrays.toString(dp));
        // System.out.println(max);
        
        List<Integer> res = new ArrayList<>();
        int pre = 0;
        for (int i = n-1; i >= 0 && max != 0; i--) {
            if (pre == 0 && dp[i] == max) {
                pre = nums[i];
                max--;
                res.add(nums[i]);
            } else if (dp[i] == max && pre != 0 && pre % nums[i] == 0) {
                max--;
                pre = nums[i];
                res.add(nums[i]);
            }
        }
        return res;
    }
}

go解法:

import "sort"
func largestDivisibleSubset(nums []int) []int {
    n := len(nums)
    sort.Ints(nums)
    dp := make([]int, n)
    
    dp[0] = 1
    max := 1
    for i := 1; i < n; i++ {
        div := nums[i]
        dp[i] = 1
        for j := 0; j < i; j++ {
            if div % nums[j] == 0 {
                dp[i] = fMax(dp[i], dp[j]+1)
            }
        }
        max = fMax(max, dp[i])
    }
    var res []int
    pre := 0
    for i := n-1; i >= 0 && max != 0; i-- {
        if dp[i] == max && (pre % nums[i] == 0 || pre == 0) {
            pre = nums[i]
            max--;
            res = append(res, nums[i])
        }
    }
    return res
}

func fMax(a, b int) int {
    if a > b {
        return a
    }
    return b
}

动态规划,写法更好理解,算法逻辑与前面一致

class Solution {
    public List<Integer> largestDivisibleSubset(int[] nums) {
        int n = nums.length;
        Arrays.sort(nums);
        int[] dp = new int[n];
        dp[0] = 1;
        int maxSize = 1, maxIndex = 0, maxValue = nums[0];
        for (int i = 1; i < n; i++) {
            dp[i] = 1;
            for (int j = i-1; j >= 0; j--) {
                if (nums[i] % nums[j] == 0) {
                    dp[i] = Math.max(dp[i], dp[j]+1);
                }
            }
            if (dp[i] > maxSize) {
                maxSize = dp[i];
                maxIndex = i;
                maxValue = nums[i];
            }
        }

        List<Integer> res = new ArrayList<>();
        for (int i = maxIndex; i >= 0 && maxSize > 0; i--) {
            if (maxSize == dp[i] && maxValue % nums[i] == 0) {
                res.add(nums[i]);
                maxValue = nums[i];
                maxSize--;
            }
        }
        return res;
    }
}

# 91. 解码方法

动态规划:

class Solution {
    public int numDecodings(String s) {
        int n = s.length();
        if (s.charAt(0) == '0') return 0;
        int[] dp = new int[n+1];
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            int di = 0;
            if (s.charAt(i-1) != '0') {
                di = dp[i-1];
            }
            int di2 = 0;
            int s1 = (int) (s.charAt(i-1)-'0');
            int s2 = (int) (s.charAt(i-2)-'0');
            if ((s2 == 2 && s1 <= 6) || s2 == 1) {
                di2 = dp[i-2];
            }
            dp[i] = di + di2;
        }
        return dp[n];
    }
}

动态规划,空间优化,更优的代码:

class Solution {
    public int numDecodings(String s) {
        int p1 = 1, p2 = 0;
        if (s.charAt(0) == '0') return 0;
        for (int i = 0; i < s.length(); i++) {
            int cur = 0;
            if (i > 0 && Integer.valueOf(s.substring(i-1, i+1)) <= 26 && s.charAt(i-1) != '0') {
                cur += p2;
            }
            if (s.charAt(i) != '0') {
                cur += p1;
            }
            p2 = p1;
            p1 = cur;
        }
        return p1;
    }
}

# 781. 森林中的兔子

class Solution {
    public int numRabbits(int[] answers) {
        Map<Integer, Integer> map = new HashMap<>();
        int n = answers.length;
        for (int i = 0; i < n; i++) {
            int cur = answers[i]+1;
            map.put(cur, map.getOrDefault(cur, 0)+1);
        }
        int res = 0;
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            int quantity = entry.getKey();
            int rabbits = entry.getValue();
            int multiple = rabbits / quantity;
            int remainder = rabbits % quantity;
            res += (multiple * quantity) + (remainder > 0 ? quantity : 0);
        }
        return res;
    }
}

# 面试题 17.21. 直方图的水量

单调栈:

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        int res = 0;
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < n; i++) {
            while (!stack.empty() && height[i] > height[stack.peek()]) {
                int cur = stack.pop();

                if (stack.empty()) continue;

                int left = stack.peek(), right = i;
                res += (Math.min(height[left], height[right]) - height[cur]) * (right - left -1);
            }
            stack.push(i);
        }
        return res;
    }
}

# 1006. 笨阶乘

双栈做法,但是代码写的太繁琐:

class Solution {
    public int clumsy(int n) {
        char[] ch = new char[]{'*', '/', '+', '-'};
        Stack<Integer> sn = new Stack<>();
        Stack<Character> sc = new Stack<>();
        sc.push('+');
        for (int cur = n, ci = 0; cur > 0; cur--) {
            char symbol = sc.peek();
            if (symbol == '*' || symbol == '/') {
                sc.pop();
                int num = sn.pop();
                int res = 0;
                if (symbol == '*') res = num * cur;
                if (symbol == '/') res = num / cur;
                sn.push(res);
            } else {
                sn.push(cur);
            }
            sc.push(ch[ci%4]);
            ci++;
        }
        
        sc.pop();
        int res = 0;
        while (!sn.empty()) {
            int cur = sn.pop();
            char symbol = sc.pop();
            if (symbol == '-') cur *= -1;
            res += cur;
        }
        return res;
    }
}

优化后的代码:

class Solution {
    public int clumsy(int n) {
        char[] ch = new char[]{'*', '/', '+', '-'};
        Stack<Integer> sn = new Stack<>();
        sn.push(n);
        int chi = 0;
        for (int cur = n-1; cur > 0; cur--) {
            if (chi % 4 == 0) {
                sn.push(sn.pop() * cur);
            } else if (chi % 4 == 1) {
                sn.push(sn.pop() / cur);
            } else if (chi % 4 == 2) {
                sn.push(cur);
            } else if (chi % 4 == 3) {
                sn.push(-cur);
            }
            chi++;
        }
        
        int res = 0;
        while (!sn.empty()) {
            res += sn.pop();
        }
        return res;
    }
}

# ## 87. 扰乱字符串

暴力法,递归,超时:

class Solution {
    public boolean isScramble(String s1, String s2) {
        int n1 = s1.length();
        int n2 = s2.length();
        if (n1 != n2) return false;
        int n = n1;
        if (s1.equals(s2)) return true;

        for (int i = 1; i < n; i++) {
            String s1a = s1.substring(0,i);
            String s1b = s1.substring(i,n);

            String s2a = s2.substring(0,i);
            String s2b = s2.substring(i);
            String s3a = s2.substring(0, n-i);
            String s3b = s2.substring(n-i);
            if ((isScramble(s1a, s2a) && isScramble(s1b, s2b)) || (isScramble(s1a, s3b) && isScramble(s1b, s3a))) {
                return true;
            }
            
        }
        return false;
    }        
}

将递归转化为记忆化递归,可以AC,速度也不慢:

class Solution {
    int[][][] cache;
    int N = -1, Y = 1, EMPTY = 0;
    String s1;
    String s2;
    public boolean isScramble(String _s1, String _s2) {
        s1 = _s1;
        s2 = _s2;
        if (s1.equals(s2)) return true;
        if (!check(s1, s2)) return false;
        int n = s1.length();
        cache = new int[n][n][n+1];
        recursive(0, 0, n);
        return cache[0][0][n] == Y;
    }

    private boolean recursive(int i, int j, int k) {
        if (cache[i][j][k] != EMPTY) return cache[i][j][k] == Y;
        String a = s1.substring(i, i+k);
        String b = s2.substring(j, j+k);
        if (a.equals(b)) {
            cache[i][j][k] = Y;
            return true;
        }
        if (!check(a, b)) {
            cache[i][j][k] = N;
            return false;
        }
        for (int p = 1; p < k; p++) {
            
            if (recursive(i, j, p) && recursive(i+p, j+p, k-p)) {
                cache[i][j][k] = Y;
                return true;
            }

            if (recursive(i, k-p+j, p) && recursive(i+p, j, k-p)) {
                cache[i][j][k] = Y;
                return true;
            }

        }

        cache[i][j][k] = N;
        return false;
    }

    private boolean check(String s1, String s2) {
        if (s1.length() != s2.length()) return false;
        int n = s1.length();
        char[] c1 = s1.toCharArray();
        char[] c2 = s2.toCharArray();

        int[] cnt1 = new int[26];
        int[] cnt2 = new int[26];

        for (int i = 0; i < n; i++) {
            cnt1[c1[i]-'a']++;
            cnt2[c2[i]-'a']++;
        }

        for (int i = 0; i < 26; i++) {
            if (cnt1[i] != cnt2[i]) return false;
        }
        return true;
    }
        
}

区间dp,待补充

修改于: 8/11/2022, 3:17:56 PM