剑指offer专项突破快刷(一)

268 阅读10分钟

整数|位运算

微信图片_20230601161216.jpg

剑指 Offer II 001. 整数除法

class Solution {
    public int divide(int a, int b) {
        if (a == Integer.MIN_VALUE) {
            if (b == -1) {
                return Integer.MAX_VALUE;
            }
            if (b == 1) {
                return Integer.MIN_VALUE;
            }
        }
        if (b == Integer.MIN_VALUE) {
            if (a == Integer.MIN_VALUE) {
                return 1;
            }
            return 0;
        }
        if (a == 0) {
            return 0;
        }
        boolean reverse = false;
        if (a > 0) {
            a = -a;
            reverse = !reverse;
        }
        if (b > 0) {
            b = -b;
            reverse = !reverse;
        }
        int left = 1;
        int right = Integer.MAX_VALUE;
        int ans = 0;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            boolean check = quickAdd(b, mid, a);
            if (check) {
                ans = mid;
                if (mid == Integer.MAX_VALUE) {
                    return ans;
                }
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return reverse ? -ans : ans;
    }

    // 快速乘
    public boolean quickAdd(int y, int z, int x) {
        int result = 0;
        int add = y;
        while (z != 0) {
            // 说明是奇数
            if ((z & 1) != 0) {
                if (result < x - add) {
                    return false;
                }
                result += add;
            }
            if (z != 1) {
                if (add < x - add) {
                    return false;
                }
                add += add;
            }
            z >>= 1;
        }
        return true;
    }
}

剑指 Offer II 002. 二进制加法

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    public String addBinary(String a, String b) {
        char[] chars1 = a.toCharArray();
        char[] chars2 = b.toCharArray();

        int idx1 = chars1.length - 1;
        int idx2 = chars2.length - 1;
        int mod = 0;

        StringBuilder sb = new StringBuilder();
        while (idx1 >= 0 || idx2 >= 0 || mod != 0) {
            int v1 = idx1 >= 0 ? chars1[idx1] - '0' : 0;
            int v2 = idx2 >= 0 ? chars2[idx2] - '0' : 0;

            int val = (v1 + v2 + mod) % 2;
            sb.append(val);

            mod = (v1 + v2 + mod) / 2;
            idx1--;
            idx2--;
        }
        return sb.reverse().toString();
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 003. 前 n 个数字二进制中 1 的个数

// 时间复杂度:O(nlogn),不符合题目要求
class Solution {
    public int[] countBits(int n) {
        int[] ans = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            ans[i] = count(i);
        }
        return ans;
    }

    public int count(int n) {
        int res = 0;
        while (n != 0) {
            n = n & (n - 1);
            res++;
        }
        return res;
    }
}
//leetcode submit region end(Prohibit modification and deletion)
// 时间复杂度:O(n)
class Solution {
    public int[] countBits(int n) {
        // bits表示i的比特位为1的数量
        int[] bits = new int[n + 1];
        int highBit = 0;
        for (int i = 1; i <= n; i++) {
            // 找2的指数幂的最高位
            if ((i & (i - 1)) == 0) {
                highBit = i;
            }
            bits[i] = bits[i - highBit] + 1;
        }
        return bits;
    }
}

剑指 Offer II 004. 只出现一次的数字

如果是正好出现两次可以用异或。

题目要求不借用外部空间,那么不能排序也不能用hashmap,因为排序需要logn的空间和nlogn的时间复杂度。

只能用位计数,从0到31位上看每一位的1的个数,如果是重复的数,肯定是3

class Solution {
    public int singleNumber(int[] nums) {
        int ans = 0;
        for (int i = 0; i < 32; ++i) {
            int total = 0;
            for (int num : nums) {
                // 求得每一位的1的总数
                total += ((num >> i) & 1);
            }
            if (total % 3 != 0) {
                // 不是3的倍数那说明这一位肯定是我们需要的结果
                ans |= (1 << i);
            }
        }
        return ans;
    }
}

剑指 Offer II 005. 单词长度的最大乘积

意思是找到两个长度最大的不相交单词。

class Solution {
    public int maxProduct(String[] words) {
        int length = words.length;
        int[] masks = new int[length];
        for (int i = 0; i < length; i++) {
            String word = words[i];
            int wordLength = word.length();
            for (int j = 0; j < wordLength; j++) {
                masks[i] |= 1 << (word.charAt(j) - 'a');
            }
        }
        int maxProd = 0;
        for (int i = 0; i < length; i++) {
            for (int j = i + 1; j < length; j++) {
                if ((masks[i] & masks[j]) == 0) {
                    maxProd = Math.max(maxProd, words[i].length() * words[j].length());
                }
            }
        }
        return maxProd;
    }
}

剑指 Offer II 006. 排序数组中两个数字之和

有很多解法,用双指针比较经典。

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0;
        int right = numbers.length - 1;
        while (left < right) {
            if (numbers[left] + numbers[right] < target) {
                left++;
            } else if (numbers[left] + numbers[right] > target) {
                right--;
            } else {
                return new int[]{left, right};
            }
        }
        return null;
    }
}

数组

微信图片_20230512152647.jpg

剑指 Offer II 007. 数组中和为 0 的三个数

注意条件:数组不是有序的,那么不能直接使用双指针,可以考虑排序,因为题目要求的结果并不是索引,所以不用在意相对位置。

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> ans = new ArrayList<>();
        for (int i = 0; i < nums.length - 2; i++) {
            List<List<Integer>> twos = twoSum(nums, i + 1, -nums[i]);
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            if (twos.size() != 0) {
                for (List<Integer> two : twos) {
                    List<Integer> three = new ArrayList<>();
                    three.add(nums[i]);
                    three.add(two.get(0));
                    three.add(two.get(1));
                    ans.add(three);
                }
            }
        }
        return ans;
    }

    public List<List<Integer>> twoSum(int[] nums, int start, int target) {
        int left = start;
        int right = nums.length - 1;
        List<List<Integer>> ans = new ArrayList<>();
        while (left < right) {
            if (nums[left] + nums[right] == target) {
                List<Integer> tmp = new ArrayList<>();
                tmp.add(nums[left]);
                tmp.add(nums[right]);
                ans.add(tmp);
                while (left < nums.length - 1 && nums[left] == nums[left + 1]) {
                    left++;
                }
                while (right > 0 && nums[right] == nums[right - 1]) {
                    right--;
                }
                left++;
                right--;
            } else if (nums[left] + nums[right] < target) {
                left++;
            } else {
                right--;
            }
        }
        return ans;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 008. 和大于等于 target 的最短子数组

求连续子数组的最值,可以考虑用滑动窗口。

class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int n = nums.length;
        int ans = Integer.MAX_VALUE;
        int start = 0, end = 0;
        int sum = 0;
        while (end < n) {
            // 用end加
            sum += nums[end];
            // 用start减
            while (sum >= s) {
                ans = Math.min(ans, end - start + 1);
                sum -= nums[start];
                start++;
            }
            end++;
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }
}

剑指 Offer II 009. 乘积小于 K 的子数组

求连续子数组的个数,还是用滑动窗口,这题比较有技巧,要记住。

class Solution {
    public int numSubarrayProductLessThanK(int[] nums, int k) {
        int n = nums.length, ret = 0;
        int prod = 1, i = 0;
        for (int j = 0; j < n; j++) {
            prod *= nums[j];
            while (i <= j && prod >= k) {
                prod /= nums[i];
                i++;
            }
            ret += j - i + 1;
        }
        return ret;
    }
}

剑指 Offer II 010. 和为 k 的子数组

原本想用滑动窗口,但是发现元素可能为负数。

看了官方题解,还是要用前缀和+map,没啥意思。

public class Solution {
    public int subarraySum(int[] nums, int k) {
        int count = 0, pre = 0;
        HashMap<Integer, Integer> mp = new HashMap<>();
        mp.put(0, 1);
        for (int i = 0; i < nums.length; i++) {
            pre += nums[i];
            if (mp.containsKey(pre - k)) {
                count += mp.get(pre - k);
            }
            mp.put(pre, mp.getOrDefault(pre, 0) + 1);
        }
        return count;
    }
}

剑指 Offer II 012. 左右两边子数组的和相等


//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    public int pivotIndex(int[] nums) {
        // 假设位置为k
        // nums[1] + ... + nums[k-1] = nums[k+1] + ... + nums[len]
        // preSums[k-1] = preSums[len] - preSums[k]
        // 要找到一个k,使得 preSums[k-1]+preSums[k] = total
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (nums[0] == sum) {
            return 0;
        }
        int len = nums.length;
        int[] preSums = new int[len];
        preSums[0] = nums[0];
        for (int i = 1; i < len; i++) {
            preSums[i] = preSums[i - 1] + nums[i];
            if (preSums[i] + preSums[i - 1] == sum) {
                return i;
            }
        }
        return -1;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

代码可以继续优化,不需要preSums数组。

class Solution {
    public int pivotIndex(int[] nums) {
        // 假设位置为k
        // nums[1] + ... + nums[k-1] = nums[k+1] + ... + nums[len]
        // preSums[k-1] = preSums[len] - preSums[k]
        // 要找到一个k,使得 preSums[k-1]+preSums[k] = total
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (nums[0] == sum) {
            return 0;
        }
        int len = nums.length;
        int q = nums[0];
        for (int i = 1; i < len; i++) {
            // r就是pre[i],q就是pre[i-1]
            int r = q + nums[i];
            //preSums[i] = preSums[i - 1] + nums[i];
            if (r + q == sum) {
                return i;
            }
            q = r;
        }
        return -1;
    }
}

剑指 Offer II 013. 二维子矩阵的和

class NumMatrix {
    int[][] preSums;

    public NumMatrix(int[][] matrix) {
        int m = matrix.length;
        int n = matrix[0].length;
        preSums = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                preSums[i][j] = preSums[i - 1][j] + preSums[i][j - 1] - preSums[i - 1][j - 1] + matrix[i - 1][j - 1];
            }
        }
    }

    public int sumRegion(int row1, int col1, int row2, int col2) {
        return preSums[row2+1][col2+1] - preSums[row1][col2+1] - preSums[row2+1][col1] + preSums[row1][col1];
    }
}

字符串

微信图片_20230510111937.jpg

剑指 Offer II 014. 字符串中的变位词

// 暴力法,用字符数组,效率不是很理想
class Solution {
    public boolean checkInclusion(String s1, String s2) {
        // 子排列必须是连续的
        // 那就用一个滑动窗口,在s2里找s1的异位词
        int size = s1.length();
        for (int i = 0; i <= s2.length() - size; i++) {
            if (match(s1, s2.substring(i, i + size))) {
                return true;
            }
        }
        return false;
    }

    public boolean match(String s1, String s2) {
        char[] chars1 = s1.toCharArray();
        char[] chars2 = s2.toCharArray();
        int[] counts = new int[26];
        for (char c1 : chars1) {
            counts[c1 - 'a']++;
        }
        for (char c2 : chars2) {
            counts[c2 - 'a']--;
        }
        for (int count : counts) {
            if (count != 0) {
                return false;
            }
        }
        return true;
    }
}
// 果断参考了labuladong的题解
class Solution {
    public boolean checkInclusion(String t, String s) {
        Map<Character, Integer> need = new HashMap<>();
        Map<Character, Integer> window = new HashMap<>();
        for (char c : t.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }

        int left = 0, right = 0;
        int valid = 0;
        while (right < s.length()) {
            char c = s.charAt(right);
            right++;
            // 进行窗口内数据的一系列更新
            if (need.containsKey(c)) {
                window.put(c, window.getOrDefault(c, 0) + 1);
                if (window.get(c).equals(need.get(c))) {
                    valid++;
                }
            }

            // 判断左侧窗口是否要收缩
            while (right - left >= t.length()) {
                // 在这里判断是否找到了合法的子串
                if (valid == need.size()) {
                    return true;
                }
                char d = s.charAt(left);
                left++;
                // 进行窗口内数据的一系列更新
                if (need.containsKey(d)) {
                    if (window.get(d).equals(need.get(d)))
                        valid--;
                    window.put(d, window.get(d) - 1);
                }
            }
        }
        // 未找到符合条件的子串
        return false;
    }
}

剑指 Offer II 015. 字符串中的所有变位词

这就是上一题的另一个结果集。

import java.util.HashMap;

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        Map<Character, Integer> win = new HashMap<>();
        Map<Character, Integer> need = new HashMap<>();
        int valid = 0;
        for (char c : p.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }
        int left = 0;
        int right = 0;
        List<Integer> ans = new ArrayList<>();
        while (right < s.length()) {
            char cur = s.charAt(right);
            right++;
            if (need.containsKey(cur)) {
                win.put(cur, win.getOrDefault(cur, 0) + 1);
                if (win.get(cur).equals(need.get(cur))) {
                    valid++;
                }
            }
            while (right - left >= p.length()) {
                if (valid == need.size()) {
                    ans.add(left);
                }
                char out = s.charAt(left);
                left++;
                if (need.containsKey(out)) {
                    if (need.get(out).equals(win.get(out))) {
                        valid--;
                    }
                    win.put(out, win.get(out) - 1);
                }
            }
        }
        return ans;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 016. 不含重复字符的最长子字符串

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> win = new HashMap<>();
        int left = 0;
        int right = 0;
        int max = 0;
        while (right < s.length()) {
            char cur = s.charAt(right);
            right++;
            if (!win.containsKey(cur)) {
                // 不存在,记录最大长度,注意这里的right在前面已经加过1了
                max = Math.max(max, right - left);
            } else {
                // 已存在,找到跟cur相等的元素
                char out = s.charAt(left);
                // 过程中遇到的也通通删除
                while (out != cur) {
                    win.remove(out);
                    out = s.charAt(++left);
                }
                // 此时left就是和right相等的,删除left
                left++;
            }
            win.put(cur, 1);
        }
        return max;
    }
}

剑指 Offer II 017. 含有所有字符的最短字符串

和上面两题差别不大,区别在于,该题可以不用连续序列,所以判断出的条件有所不同。


//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    public String minWindow(String s, String t) {
        Map<Character, Integer> win = new HashMap<>();
        Map<Character, Integer> need = new HashMap<>();
        for (char c : t.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }
        int left = 0;
        int right = 0;
        int min = Integer.MAX_VALUE;
        int minLeft = 0;
        int valid = 0;
        while (right < s.length()) {
            char cur = s.charAt(right);
            right++;
            if (need.containsKey(cur)) {
                win.put(cur, win.getOrDefault(cur, 0) + 1);
                if (need.get(cur).equals(win.get(cur))) {
                    valid++;
                }
            }

            while (valid == need.size()) {
                if (right - left < min) {
                    minLeft = left;
                    min = right - left;
                }
                char out = s.charAt(left);
                left++;
                if(need.containsKey(out)){
                    if (win.get(out).equals(need.get(out))) {
                        valid--;
                    }
                    win.put(out, win.get(out) - 1);
                }
            }
        }
        return min == Integer.MAX_VALUE ? "" : s.substring(minLeft, minLeft + min);
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 018. 有效的回文

class Solution {
    public boolean isPalindrome(String s) {
        // 先把所有字符转化成小写,并过滤掉空格和标点这类字符
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (Character.isLetterOrDigit(c)) {
                sb.append(Character.toLowerCase(c));
            }
        }

        // 然后对剩下的这些目标字符执行双指针算法,判断回文串
        s = sb.toString();
        // 一左一右两个指针相向而行
        int left = 0, right = s.length() - 1;
        while (left < right) {
            if (s.charAt(left) != s.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

剑指 Offer II 019. 最多删除一个字符得到回文

class Solution {
    public boolean validPalindrome(String s) {
        int left = 0;
        int right = s.length() - 1;
        while (left < right) {
            if (s.charAt(left) == s.charAt(right)) {
                // 正常的比较
                left++;
                right--;
            } else {
                // 不正常的,left或者right进一位,因为只可以删除一个字符,所以这里直接再做两次比较可以
                return validPalindrome(s, left, right - 1) || validPalindrome(s, left + 1, right);
            }
        }
        return true;
    }

    public boolean validPalindrome(String s, int left, int right) {
        int l = left;
        int r = right;
        while (l < r) {
            if (s.charAt(l) != s.charAt(r)) {
                return false;
            }
            l++;
            r--;
        }
        return true;
    }
}

剑指 Offer II 020. 回文子字符串的个数

要用中心点法,枚举中心点,每次都想不到!

class Solution {
    public int countSubstrings(String s) {
        int n = s.length(), ans = 0;
        for (int i = 0; i < 2 * n - 1; ++i) {
            int l = i / 2, r = i / 2 + i % 2;
            while (l >= 0 && r < n && s.charAt(l) == s.charAt(r)) {
                --l;
                ++r;
                ++ans;
            }
        }
        return ans;
    }
}

链表

链表类问题,多想想用快慢指针。

微信图片_20230512152651.jpg

剑指 Offer II 021. 删除链表的倒数第 n 个结点

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode slow = head;
        ListNode fast = head;
        int k = n;
        while (k > 0) {
            fast = fast.next;
            k--;
        }
        // 如果正好走到头了,说明要删除的是第一个
        if (fast == null) {
            return head.next;
        }
        while (fast != null && fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        if (slow != null && slow.next != null) {
            slow.next = slow.next.next;
        }
        return head;
    }
}

剑指 Offer II 022. 链表中环的入口节点

又是典型的快慢指针。


//leetcode submit region begin(Prohibit modification and deletion)

/**
 * Definition for singly-linked list.
 * class ListNode {
 * int val;
 * ListNode next;
 * ListNode(int x) {
 * val = x;
 * next = null;
 * }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        ListNode second = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            // 如果有环,必然相遇
            if (slow == fast) {
                while (second != slow) {
                    second = second.next;
                    slow = slow.next;
                }
                return second;
            }
        }
        //如果无环才会走到这里
        return null;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 023. 两个链表的第一个重合节点

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1 = headA;
        ListNode p2 = headB;
        // 注意条件是p1!=p2,不能用p1!=null
        // 因为如果没有交点的时候,p1和p2都走到最后的null时,此时要退出的,而不是继续给p1指向headB,p2指向headA
        while (p1 != p2) {
            p1 = p1 == null ? headB : p1.next;
            p2 = p2 == null ? headA : p2.next;
        }
        return p1;
    }
}

剑指 Offer II 024. 反转链表

// 迭代写法,现在发现迭代写法很好写
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode p = null, q = head;
        while (q != null) {
            ListNode r = q.next;
            q.next = p;

            p = q;
            q = r;
        }
        return p;
    }
}
// 递归写法,真的是每次都想不到啊!!!!
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode last = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return last;
    }
}

剑指 Offer II 025. 链表中的两数相加

// 先写一个翻转的
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode newL1 = reverse(l1);
        ListNode newL2 = reverse(l2);
        int mod = 0;
        ListNode ans = new ListNode(-1);
        ListNode pre = ans;
        // [7,2,4,3] -> [3,4,2,7]
        // [5,6,4] ->   [4,6,5]
        // 得到结果: [-1,7,0,8,7] -> [7,8,0,7]
        while (newL1 != null || newL2 != null || mod != 0) {
            int v1 = newL1 == null ? 0 : newL1.val;
            int v2 = newL2 == null ? 0 : newL2.val;
            int add = v1 + v2 + mod;
            ans.next = new ListNode(add % 10);
            mod = add / 10;
            
            // 链表类的题目一定不能忘记,让指针动起来!
            ans = ans.next;
            newL1 = newL1 == null ? null : newL1.next;
            newL2 = newL2 == null ? null : newL2.next;
        }
        //reverse [7,0,8,7]
        return reverse(pre.next);
    }

    public ListNode reverse(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode tail = reverse(head.next);
        head.next.next = head;
        head.next = null;
        return tail;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

题目要求不允许翻转,可以用栈来实现先入后出的效果。

剑指 Offer II 026. 重排链表

class Solution {
    public void reorderList(ListNode head) {
        if (head == null) {
            return;
        }
        ListNode mid = middleNode(head);
        ListNode l1 = head;
        ListNode l2 = mid.next;
        mid.next = null;
        l2 = reverseList(l2);
        mergeList(l1, l2);
    }

    public ListNode middleNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
        return prev;
    }

    public void mergeList(ListNode l1, ListNode l2) {
        ListNode l1_tmp;
        ListNode l2_tmp;
        while (l1 != null && l2 != null) {
            l1_tmp = l1.next;
            l2_tmp = l2.next;

            l1.next = l2;
            l1 = l1_tmp;

            l2.next = l1;
            l2 = l2_tmp;
        }
    }
}

剑指 Offer II 027. 回文链表

//leetcode submit region begin(Prohibit modification and deletion)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode() {}
 * ListNode(int val) { this.val = val; }
 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        //找到中点,然后将后面的链表反转
        // 1,2,3 3,2,1
        // 1,2,3 4 3,2,1
        ListNode l1 = head;

        ListNode fast = head;
        ListNode slow = head;
        ListNode pre = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            pre = slow;
            slow = slow.next;
        }
        if (pre != null) {
            pre.next = null;
        }

        ListNode l2 = reverseList(slow);

        while (l1 != null) {
            if (l1.val != l2.val) {
                return false;
            }
            l1 = l1.next;
            l2 = l2.next;
        }
        return true;
    }

    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode last = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return last;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 028. 展平多级双向链表

class Solution {
    public Node flatten(Node head) {
        dfs(head);
        return head;
    }

    public Node dfs(Node node) {
        Node last = null;
        Node cur = node;
        while (cur != null) {
            Node next = cur.next;
            if (cur.child != null) {
                // 如果有child
                Node childLast = dfs(cur.child);

                next = cur.next;
                // 将node和child连接起来
                cur.next = cur.child;
                cur.child.prev = cur;

                // 将childLast和next连接
                if (next != null) {
                    childLast.next = next;
                    next.prev = childLast;
                }

                // 将child置为空
                cur.child = null;
                last = childLast;
            } else {
                // 如果没有child,继续一直往下走
                last = cur;
            }
            // 必须要走起来才能递归
            cur = next;
        }
        return last;
    }
}

剑指 Offer II 029. 排序的循环链表

class Solution {
    public Node insert(Node head, int insertVal) {
        Node node = new Node(insertVal);
        // 链表为空
        if (head == null) {
            head = node;
            head.next = node;
            return head;
        }
        // 链表只有一个节点
        if (head.next == head) {
            head.next = node;
            node.next = head;
            return head;
        }
        Node cur = head, nxt = head.next;
        // 链表不止一个节点,注意是一个循环链表,所以判断退出条件不能用null
        while (nxt != head) {
            // 在递增的区段里找到了合适的位置
            if (cur.val <= insertVal && nxt.val >= insertVal) {
                break;
            }

            // 在由增到减这个位置上可以插入
            if (cur.val > nxt.val) {
                if (insertVal > cur.val || insertVal < nxt.val) {
                    break;
                }
            }

            cur = cur.next;
            nxt = nxt.next;
        }

        cur.next = node;
        node.next = nxt;
        return head;
    }
}

哈希表

微信图片_20230512152644.jpg

剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器

insert和remove都可以直接用hash,都是O(1)

random要实现O(1),只能用数组或者list这个结构

但是arrayList只有查找是O(1),linkedList只有插入和删除是O(1)

可以使用arraylist,删除时将元素和队尾元素交换,插入时,直接插入队尾。

class RandomizedSet {
    List<Integer> nums;
    Map<Integer, Integer> indices;
    Random random;

    public RandomizedSet() {
        nums = new ArrayList<Integer>();
        indices = new HashMap<Integer, Integer>();
        random = new Random();
    }

    public boolean insert(int val) {
        if (indices.containsKey(val)) {
            return false;
        }
        int index = nums.size();
        nums.add(val);
        indices.put(val, index);
        return true;
    }

    public boolean remove(int val) {
        if (!indices.containsKey(val)) {
            return false;
        }
        int index = indices.get(val);
        int last = nums.get(nums.size() - 1);
        nums.set(index, last);
        indices.put(last, index);
        nums.remove(nums.size() - 1);
        indices.remove(val);
        return true;
    }

    public int getRandom() {
        int randomIndex = random.nextInt(nums.size());
        return nums.get(randomIndex);
    }
}

剑指 Offer II 031. 最近最少使用缓存

剑指 Offer II 032. 有效的变位词

class Solution {
    public boolean isAnagram(String s, String t) {
        if (s.length() != t.length()) {
            return false;
        }
        if (s.equals(t)) {
            return false;
        }
        Map<Character, Integer> need = new HashMap<>();
        for (char ss : s.toCharArray()) {
            need.put(ss, need.getOrDefault(ss, 0) + 1);
        }
        int valid = 0;
        Map<Character, Integer> win = new HashMap<>();
        for (char tt : t.toCharArray()) {
            if (!need.containsKey(tt)) {
                return false;
            }
            int target = need.get(tt);
            int current = win.getOrDefault(tt, 0) + 1;
            if (target == current) {
                valid++;
            }
            win.put(tt, current);
        }
        return valid == need.size();
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 033. 变位词组

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        // 问题在于怎么设置key
        Map<String, List<String>> map = new HashMap<>();
        for (String str : strs) {
            // 思路一,对char数组进行排序,排序完之后肯定是相等的
            char[] chars = str.toCharArray();
            Arrays.sort(chars);
            StringBuilder sb = new StringBuilder();
            for(char c : chars){
                sb.append(c);
            }

            List<String> values = map.getOrDefault(sb.toString(), new ArrayList<>());
            values.add(str);
            map.put(sb.toString(), values);
        }
        List<List<String>> ans = new ArrayList<>();
        for (List<String> val : map.values()) {
            ans.add(val);
        }
        return ans;
    }
}
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        // 问题在于怎么设置key
        Map<String, List<String>> map = new HashMap<>();
        for (String str : strs) {
            // 思路二,编码,字符和出现次数
            char[] chars = str.toCharArray();
            int[] counts = new int[26];
            for (char c : chars) {
                counts[c - 'a']++;
            }
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 26; i++) {
                if (counts[i] > 0) {
                    sb.append(i + 'a').append(counts[i]);
                }
            }

            List<String> values = map.getOrDefault(sb.toString(), new ArrayList<>());
            values.add(str);
            map.put(sb.toString(), values);
        }
        List<List<String>> ans = new ArrayList<>();
        for (List<String> val : map.values()) {
            ans.add(val);
        }
        return ans;
    }
}

剑指 Offer II 034. 外星语言是否排序

    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        Map<Character, Integer> index = null;

        public boolean isAlienSorted(String[] words, String order) {
            index = new HashMap<>();
            for (int i = 0; i < order.length(); i++) {
                index.put(order.charAt(i), i);
            }
            for (int i = 0; i < words.length - 1; i++) {
                if (compare(words[i], words[i + 1]) > 0) {
                    return false;
                }
            }
            return true;
        }

        public int compare(String a, String b) {
            int idx = 0;
            while (idx < a.length() && idx < b.length()) {
                if (index.get(a.charAt(idx)) < index.get(b.charAt(idx))) {
                    return -1;
                } else if (index.get(a.charAt(idx)) > index.get(b.charAt(idx))) {
                    return 1;
                } else {
                    idx++;
                }
            }
            if (b.length() == a.length()) {
                return 0;
            } else if (b.length() > a.length()) {
                return -1;
            }
            return 1;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 035. 最小时间差

class Solution {
    public int findMinDifference(List<String> timePoints) {
        if (timePoints.size() > 1440) {
            return 0;
        }
        Collections.sort(timePoints);
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < timePoints.size() - 1; i++) {
            min = Math.min(min, getMinutes(timePoints.get(i + 1)) - getMinutes(timePoints.get(i)));
        }
        min = Math.min(min, getMinutes(timePoints.get(0)) + 1440 - getMinutes(timePoints.get(timePoints.size() - 1)));
        return min;
    }

    public int getMinutes(String str) {
        int hours = 0;
        int minutes = 0;
        if (str.charAt(0) == '0') {
            if (str.charAt(1) != '0') {
                hours = Integer.parseInt(str.substring(1, 2));
            }
        } else {
            hours = Integer.parseInt(str.substring(0, 2));
        }

        if (str.charAt(3) == '0') {
            if (str.charAt(4) != '0') {
                minutes = Integer.parseInt(str.substring(4, 5));
            }
        } else {
            minutes = Integer.parseInt(str.substring(3, 5));
        }
        return hours * 60 + minutes;
    }
}

微信图片_20230516095708.jpg

剑指 Offer II 036. 后缀表达式

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        int ans = 0;
        for (String token : tokens) {
            // 如果是数字直接入栈
            if (isNumberic(token)) {
                stack.push(Integer.parseInt(token));
            } else {
                // 不是数字,说明是表达式,要从栈里取出两个元素
                int v2 = stack.pop();
                int v1 = stack.pop();
                // 计算出结果再放入栈里
                stack.push(caculate(v1, v2, token));
            }
        }
        // 最后栈里应该就剩一个元素
        return stack.pop();
    }

    public boolean isNumberic(String s) {
        return !s.equals("+") && !s.equals("-") && !s.equals("*") && !s.equals("/");
    }

    public int caculate(int v1, int v2, String expr) {
        if (expr.equals("+")) {
            return v1 + v2;
        } else if (expr.equals("-")) {
            return v1 - v2;
        } else if (expr.equals("/")) {
            return v1 / v2;
        }
        return v1 * v2;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 037. 小行星碰撞

class Solution {
    public int[] asteroidCollision(int[] asteroids) {
        Stack<Integer> stack = new Stack<>();
        for (int asteroid : asteroids) {
            if (asteroid < 0) {
                // 此时说明小行星不会撞到任何东西
                if (stack.isEmpty() || stack.peek() < 0) {
                    stack.push(asteroid);
                    continue;
                }
                // 遇到比自己小的,把这些都干掉
                while (!stack.isEmpty() && stack.peek() > 0 && stack.peek() < -asteroid) {
                    stack.pop();
                }
                // 都干完了,只剩自己
                if (stack.isEmpty() || stack.peek() < 0) {
                    stack.push(asteroid);
                } else if (stack.peek() == -asteroid) {
                    // 没有同归于尽,只剩自己
                    stack.pop();
                }
            } else if (asteroid > 0) {
                stack.push(asteroid);
            }
        }
        int[] ans = new int[stack.size()];
        for (int i = stack.size() - 1; i >= 0; i--) {
            ans[i] = stack.pop();
        }
        return ans;
    }
}

剑指 Offer II 038. 每日温度

下一个更大元素问题,用单调栈。

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        Stack<Integer> stack = new Stack<>();

        int len = temperatures.length;
        int[] ans = new int[len];
        for (int i = len - 1; i >= 0; i--) {
            if (stack.isEmpty()) {
                stack.push(i);
                continue;
            }
            while (!stack.isEmpty() && temperatures[stack.peek()] <= temperatures[i]) {
                stack.pop();
            }
            ans[i] = stack.isEmpty() ? 0 : stack.peek() - i;
            stack.push(i);
        }
        return ans;
    }
}

剑指 Offer II 039. 直方图最大矩形面积

遍历每一个宽度,找到左边界和右边界,左边界和右边界都是要大于自己的高度。

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        int[] left = new int[n];
        int[] right = new int[n];

        Deque<Integer> mono_stack = new ArrayDeque<Integer>();
        for (int i = 0; i < n; ++i) {
            while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                mono_stack.pop();
            }
            left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
            mono_stack.push(i);
        }

        mono_stack.clear();
        for (int i = n - 1; i >= 0; --i) {
            while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                mono_stack.pop();
            }
            right[i] = (mono_stack.isEmpty() ? n : mono_stack.peek());
            mono_stack.push(i);
        }

        int ans = 0;
        for (int i = 0; i < n; ++i) {
            ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
        }
        return ans;
    }
}

剑指 Offer II 040. 矩阵中最大的矩形

// 方法一,强行解
class Solution {
    public int maximalRectangle(String[] matrix) {
        int m = matrix.length;
        if (m == 0) {
            return 0;
        }
        int n = matrix[0].length();
        int[][] left = new int[m][n];

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i].charAt(j) == '1') {
                    left[i][j] = (j == 0 ? 0 : left[i][j - 1]) + 1;
                }
            }
        }

        int ret = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i].charAt(j) == '0') {
                    continue;
                }
                int width = left[i][j];
                int area = width;
                for (int k = i - 1; k >= 0; k--) {
                    width = Math.min(width, left[k][j]);
                    area = Math.max(area, (i - k + 1) * width);
                }
                ret = Math.max(ret, area);
            }
        }
        return ret;
    }
}
// 方法二:单调栈的优化
class Solution {
    public int maximalRectangle(String[] matrix) {
        int m = matrix.length;
        if (m == 0) {
            return 0;
        }
        int n = matrix[0].length();
        int[][] left = new int[m][n];

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i].charAt(j) == '1') {
                    left[i][j] = (j == 0 ? 0 : left[i][j - 1]) + 1;
                }
            }
        }

        int ret = 0;
        for (int j = 0; j < n; j++) { // 对于每一列,使用基于柱状图的方法
            int[] up = new int[m];
            int[] down = new int[m];

            Deque<Integer> stack = new ArrayDeque<Integer>();
            for (int i = 0; i < m; i++) {
                while (!stack.isEmpty() && left[stack.peek()][j] >= left[i][j]) {
                    stack.pop();
                }
                up[i] = stack.isEmpty() ? -1 : stack.peek();
                stack.push(i);
            }
            stack.clear();
            for (int i = m - 1; i >= 0; i--) {
                while (!stack.isEmpty() && left[stack.peek()][j] >= left[i][j]) {
                    stack.pop();
                }
                down[i] = stack.isEmpty() ? m : stack.peek();
                stack.push(i);
            }

            for (int i = 0; i < m; i++) {
                int height = down[i] - up[i] - 1;
                int area = height * left[i][j];
                ret = Math.max(ret, area);
            }
        }
        return ret;
    }
}

队列

微信图片_20230504100145.jpg

剑指 Offer II 041. 滑动窗口的平均值

class MovingAverage {

    int size = 0;
    Queue<Integer> queue = new LinkedList<>();
    double total = 0;

    /**
     * Initialize your data structure here.
     */
    public MovingAverage(int size) {
        this.size = size;
        queue = new ArrayDeque<Integer>();
    }

    public double next(int val) {
        if (queue.size() == size) {
            total -= queue.poll();
        }
        total += val;
        queue.offer(val);
        return total / queue.size();
    }
}

剑指 Offer II 042. 最近请求次数

class RecentCounter {
    Queue<Integer> queue;

    public RecentCounter() {
        queue = new ArrayDeque<>();
    }

    public int ping(int t) {
        queue.offer(t);
        while (queue.peek() < t - 3000){
            queue.poll();
        }
        return queue.size();
    }
}

剑指 Offer II 043. 往完全二叉树添加节点

class CBTInserter {
    Queue<TreeNode> candidate;
    TreeNode root;

    public CBTInserter(TreeNode root) {
        this.candidate = new ArrayDeque<TreeNode>();
        this.root = root;

        Queue<TreeNode> queue = new ArrayDeque<TreeNode>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
            if (!(node.left != null && node.right != null)) {
                candidate.offer(node);
            }
        }
    }

    public int insert(int val) {
        TreeNode child = new TreeNode(val);
        TreeNode node = candidate.peek();
        int ret = node.val;
        if (node.left == null) {
            node.left = child;
        } else {
            node.right = child;
            candidate.poll();
        }
        candidate.offer(child);
        return ret;
    }

    public TreeNode get_root() {
        return root;
    }
}

剑指 Offer II 044. 二叉树每层的最大值

class Solution {
    public List<Integer> largestValues(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            // 一次for循环就是一层
            int max = Integer.MIN_VALUE;
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                max = Math.max(max, cur.val);
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
            }
            ans.add(max);
        }
        return ans;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer II 045. 二叉树最底层最左边的值

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        Queue<TreeNode> queue = new ArrayDeque<TreeNode>();
        queue.offer(root);
        int ans = root.val;
        while (!queue.isEmpty()) {
            int size = queue.size();
            // 一次for循环就是遍历一层
            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                // 每一层的第一个元素就是最左边的元素
                if (i == 0) {
                    ans = cur.val;
                }

                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
            }
        }
        return ans;
    }
}

剑指 Offer II 046. 二叉树的右侧视图

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }

        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();

            for (int i = 0; i < size; i++) {
                TreeNode cur = queue.poll();
                if (cur.left != null) {
                    queue.offer(cur.left);
                }
                if (cur.right != null) {
                    queue.offer(cur.right);
                }
                if (i == size - 1) {
                    ans.add(cur.val);
                }
            }
        }
        return ans;
    }
}