【LeetCode】HOT100

文章目录

HOT 100

⭐⭐3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character, Integer> map = new HashMap<>();
        int ans = 0;
        for (int left = 0, right = 0; right < s.length(); right++) {
            if (map.containsKey(s.charAt(right))) {
                left = Math.max(left, map.get(s.charAt(right)) + 1);
            }
            ans = Math.max(ans, right - left + 1);
            map.put(s.charAt(right), right);
        }
        return ans;
    }
}

优化:用数组代替map,map的作用就是记录当前这个窗口中什么时候出现了这个新的重复的元素,那么每次记录下每个元素的下标就可以了

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int[] last = new int[128];
        for (int i = 0; i < 128; i++) {
            last[i] = -1;
        }
        int ans = 0;
        for (int left = 0, right = 0; right < s.length(); right++) {
            if (last[s.charAt(right)] != -1) {
                left = Math.max(left, last[s.charAt(right)] + 1);
            }
            ans = Math.max(ans, right - left + 1);
            last[s.charAt(right)] = right;   
        }
        return ans;
    }
}
⭐⭐⭐4. 寻找两个正序数组的中位数

给定两个大小分别为 mn 的正序(从小到大)数组 nums1nums2。请你找出并返回这两个正序数组的 中位数

算法的时间复杂度应该为 O(log (m+n))

Solution1:

/*这道题让我们求两个有序数组的中位数,而且限制了时间复杂度为O(log (m+n)),看到这个时间复杂度,自然而然的想到了应该使用二分查找法来求解。那么回顾一下中位数的定义,如果某个有序数组长度是奇数,那么其中位数就是最中间那个,如果是偶数,那么就是最中间两个数字的平均值。这里对于两个有序数组也是一样的,假设两个有序数组的长度分别为m和n,由于两个数组长度之和 m+n 的奇偶不确定,因此需要分情况来讨论,对于奇数的情况,直接找到最中间的数即可,偶数的话需要求最中间两个数的平均值。为了简化代码,不分情况讨论,我们使用一个小trick,我们分别找第 (m+n+1) / 2 个,和 (m+n+2) / 2 个,然后求其平均值即可,这对奇偶数均适用。加入 m+n 为奇数的话,那么其实 (m+n+1) / 2 和 (m+n+2) / 2 的值相等,相当于两个相同的数字相加再除以2,还是其本身。

这里我们需要定义一个函数来在两个有序数组中找到第K个元素,下面重点来看如何实现找到第K个元素。首先,为了避免产生新的数组从而增加时间复杂度,我们使用两个变量i和j分别来标记数组nums1和nums2的起始位置。然后来处理一些边界问题,比如当某一个数组的起始位置大于等于其数组长度时,说明其所有数字均已经被淘汰了,相当于一个空数组了,那么实际上就变成了在另一个数组中找数字,直接就可以找出来了。还有就是如果K=1的话,那么我们只要比较nums1和nums2的起始位置i和j上的数字就可以了。难点就在于一般的情况怎么处理?因为我们需要在两个有序数组中找到第K个元素,为了加快搜索的速度,我们要使用二分法,对K二分,意思是我们需要分别在nums1和nums2中查找第K/2个元素,注意这里由于两个数组的长度不定,所以有可能某个数组没有第K/2个数字,所以我们需要先检查一下,数组中到底存不存在第K/2个数字,如果存在就取出来,否则就赋值上一个整型最大值。如果某个数组没有第K/2个数字,那么我们就淘汰另一个数字的前K/2个数字即可。有没有可能两个数组都不存在第K/2个数字呢,这道题里是不可能的,因为我们的K不是任意给的,而是给的m+n的中间值,所以必定至少会有一个数组是存在第K/2个数字的。最后就是二分法的核心啦,比较这两个数组的第K/2小的数字midVal1和midVal2的大小,如果第一个数组的第K/2个数字小的话,那么说明我们要找的数字肯定不在nums1中的前K/2个数字,所以我们可以将其淘汰,将nums1的起始位置向后移动K/2个,并且此时的K也自减去K/2,调用递归。反之,我们淘汰nums2中的前K/2个数字,并将nums2的起始位置向后移动K/2个,并且此时的K也自减去K/2,调用递归即可。
转自评论区:作者 Wait想念
*/
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int len1 = nums1.length, len2 = nums2.length;
        int left = (len1 + len2 + 1) / 2;
        int right = (len1 + len2 + 2) / 2;
        return (findKthSmallest(nums1, 0, nums2, 0, left) + findKthSmallest(nums1, 0, nums2, 0, right)) / 2.0;
    }

    // 从nums1的i下标开始,从nums的下标j开始,找到以这两个下标开始的子数组的第k个数(从小到大)
    private int findKthSmallest(int[] nums1, int i, int[] nums2, int j, int k) {
        if (i > nums1.length - 1) return nums2[j + k - 1];
        if (j > nums2.length - 1) return nums1[i + k - 1];
        if (k == 1) return Math.min(nums1[i], nums2[j]);
        int midVal1 = (i + k / 2 - 1 < nums1.length) ? nums1[i + k / 2 - 1] : Integer.MAX_VALUE;
        int midVal2 = (j + k / 2 - 1 < nums2.length) ? nums2[j + k / 2 - 1] : Integer.MAX_VALUE;
        if (midVal1 < midVal2) {
            // nums1的第k/2个数较小,说明要找的数字肯定不在nums1的前k/2个数中
            // 如果nums1没有k/2个元素,说明nums2的前k/2个数都不是所求,可以割掉
            // 要割nums2就要使得midVal1较大,因此这种情况可以赋予它一个整型最大值
            return findKthSmallest(nums1, i + k / 2, nums2, j, k - k / 2);
        } else {
            return findKthSmallest(nums1, i, nums2, j + k / 2, k - k / 2);
        }
    }
}
9. 回文数

难度简单1894

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false

回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

  • 例如,121 是回文,而 123 不是。
class Solution {
    public boolean isPalindrome(int x) {
        if (x < 0) return false;
        int a = x, b = 0;
        while (x != 0) {
            b = b * 10 + (x % 10);
            x /= 10;
        }
        return a == b;
    }
}
8. 字符串转换整数 (atoi)

请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。

函数 myAtoi(string s) 的算法如下:

  1. 读入字符串并丢弃无用的前导空格
  2. 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
  3. 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
  4. 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
  5. 如果整数数超过 32 位有符号整数范围 [−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1
  6. 返回整数作为最终结果。

注意:

  • 本题中的空白字符只包括空格字符 ' '
  • 除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。
class Solution {
    public int myAtoi(String s) {
        int i = 0, n = s.length();
        while (i < n && s.charAt(i) == ' ') i++;    // 删除前置0
        if (i == n) return 0;
        boolean isNeg = false;  // 判断正负号
        if (s.charAt(i) == '-') {
            isNeg = true;
            i++;
        } else if (s.charAt(i) == '+') {
            i++;
        }
        int res = 0;    // 计数
        while (i < n && s.charAt(i) >= '0' && s.charAt(i) <= '9') {
            int digit = s.charAt(i) - '0';
            if (res > (Integer.MAX_VALUE - digit) / 10) {   // 如果超过了提前返回
                return isNeg ? Integer.MIN_VALUE : Integer.MAX_VALUE;
            }
            res = res * 10 + digit;
            i++;
        }
        return isNeg ? -res : res;
    }
}
10. 正则表达式匹配

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.''*' 的正则表达式匹配。

  • '.' 匹配任意单个字符
  • '*' 匹配零个或多个前面的那一个元素

所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

class Solution {
    public boolean isMatch(String s, String p) {
        int m = s.length(), n = p.length();
        // dp[i][j] 表示s的前i个元素是否可以和p的前j个元素进行匹配
        boolean[][] dp = new boolean[m+1][n+1];
        dp[0][0] = true;
        // first column
        for (int i = 1; i <= m; i++) {
            dp[i][0] = false;
        }
        // first row
        for (int j = 1; j <= n; j++) {
            if (j >= 2 && p.charAt(j-1) == '*') {
                dp[0][j] = dp[0][j-2];
            } else {
                dp[0][j] = false;
            }
        }

        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                if(p.charAt(j-1) != '*'){
                    dp[i][j] = (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '.') && dp[i-1][j-1];
                }
                else{
                    if(s.charAt(i-1) == p.charAt(j-2) || p.charAt(j-2) == '.'){
                        dp[i][j] = dp[i][j-1] || dp[i-1][j];
                        if(j > 1) dp[i][j] = dp[i][j] || dp[i][j-2];
                    }
                    else{
                        dp[i][j] = dp[i][j-2];
                    }
                }
            }
        }
        return dp[m][n];
    }
}
14. 最长公共前缀

难度简单2142

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) return "";
        String prefix = strs[0];
        for (int i = 1; i < strs.length; i++) {
            prefix = longestCommonPrefix(prefix, strs[i]);
            if (prefix == "") return "";
        }
        return prefix;
    }

    private String longestCommonPrefix(String str1, String str2) {
        int minLen = Math.min(str1.length(), str2.length());
        int i = 0;
        while (i < minLen && str1.charAt(i) == str2.charAt(i)) {
            i++;
        }
        return str1.substring(0, i);
    }
}
29. 两数相除

难度中等871

给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。

返回被除数 dividend 除以除数 divisor 得到的商。

整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2

class Solution {
    public int divide(int dividend, int divisor) {
        if (dividend == 0) return 0;
        if (dividend == Integer.MIN_VALUE && divisor == -1) {
            return Integer.MAX_VALUE;
        }
        boolean isNegative = (dividend ^ divisor) < 0;
        int res = 0;
        long a = Math.abs((long) dividend);
        long b = Math.abs((long) divisor);
        // 100 3
        // 100/64<3
        // 100/32>=3
        // 所以可以先得到32个3
        // 将100减去32个3,然后处理剩余的4
        for (int i = 31; i >= 0; i--) {
            if ((a >> i) >= b) {
                res += 1 << i;
                a -= b << i;
            }
        }
        return isNegative ? -res : res;
    }
}
⭐⭐31. 下一个排列

实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列(即,组合出下一个更大的整数)。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须** 原地 **修改,只允许使用额外常数空间。

class Solution {
    public void nextPermutation(int[] nums) {
        int len = nums.length;
        int i = len - 2;
        // 1238 5 764
        // 1238 6 754
        // 764已经最大了,只能继续看左边的5此时从7下降到5说明肯定可以变大
        // 但是不能变的太大,只能找到后面第一个比5大的数字也就是6,将两者交换,得到12386754、
        // 将i右边的数字降序换为升序
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }
        if (i >= 0) {
            int j = len - 1;
            while (j > i && nums[j] <= nums[i]) {
                j--;
            }
            swap(nums, i, j);
        }
        // [i+1, len-1] reverse
        reverse(nums, i + 1, len - 1);
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    private void reverse(int[] nums, int left, int right) {
        while (left < right) {
            swap(nums, left, right);
            left++;
            right--;
        }
    }
}
⭐⭐32. 最长有效括号

给你一个只包含 '('')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

class Solution {
    public int longestValidParentheses(String s) {
        int ans = 0;
        if (s == null || s.length() == 0) return ans;
        // dp[i]表示以i结尾的最长括号子串的长度
        // 每次判断 )
        //两种情况 
        // yyyyxxxx()
        // yyyy(xxxx),注意(前面的内容也要加入
        int[] dp = new int[s.length()];
        for (int i = 1; i < s.length(); i++) {
            if (s.charAt(i) == ')') {
                if (s.charAt(i - 1) == '(') {
                    dp[i] = (i - 2 >= 0 ? dp[i - 2] : 0) + 2;
                } else if(i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
                    dp[i] = dp[i - 1] + 2 + (i - dp[i - 1] >= 2 ? dp[i - dp[i - 1] - 2] : 0);
                }
                ans = Math.max(ans, dp[i]);
            }
        }
        return ans;
    }
}

✅♦♦♦旋转排序数组♦♦♦
33. 搜索旋转排序数组(left)

整数数组 nums 按升序排列,数组中的值 互不相同

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2]

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1

class Solution {
    public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) return -1;
        int len = nums.length, left = 0, right = len - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            if (nums[mid] < nums[left]) {
                if (nums[mid] < target && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            } else {
                if (nums[left] <= target && target < nums[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }
        }
        return nums[left] == target ? left : -1;
    }
}
⭐⭐81. 搜索旋转排序数组 II(left)

已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。(可能有重复的值)

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4]

给你 旋转后 的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false

/*
该题与33. 搜索旋转排序数组的区别在于,这题的数组中可能会出现重复元素。
二分查找的本质就是在循环的每一步中考虑排除掉哪些元素,本题在用二分查找时,只有在nums[mid]严格大于或小于左边界时才能判断它左边或右边是升序的,这时可以再根据nums[mid], target与左右边界的大小关系排除掉一半的元素;
当nums[mid]等于左边界时,无法判断是mid的左边还是右边是升序数组,而只能肯定左边界不等于target(因为nums[mid] != target),所以只能排除掉这一个元素,让左边界加一。
*/
class Solution {
    public boolean search(int[] nums, int target) {
        int len = nums.length, left = 0, right = len - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return true;
            if (nums[mid] == nums[left]) {
                left++;
            } else if (nums[mid] > nums[left]) {
                if (nums[left] <= target && target < nums[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            } else {
                if (nums[mid] < target && target <= nums[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        return nums[left] == target;
    }
}
⭐⭐面试题 10.03. 搜索旋转数组(right)

搜索旋转数组。给定一个排序后的数组,包含n个整数,但这个数组已被旋转过很多次了,次数不详。请编写代码找出数组中的某个元素,假设数组元素原先是按升序排列的。若有多个相同元素,返回索引值最小的一个。

class Solution {
    public int search(int[] arr, int target) {
        if (arr[0] == target) return 0;
        int left = 0, right = arr.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] == target) {
                while (mid > 0 && arr[mid - 1] == arr[mid]) mid--;
                return mid;
            }
            if (arr[mid] == arr[right]) {
                right--;
            } else if (arr[mid] < arr[right]) {
                if (arr[mid] < target && target <= arr[right]) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            } else {
                if (arr[left] <= target && target < arr[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }
        }
        return arr[left] == target ? left : -1;
    }
}
⭐⭐⭐153. 寻找旋转排序数组中的最小值(right)

已知一个长度为 n 的数组,预先按照升序排列,经由 1n旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素

class Solution {
    public int findMin(int[] nums) {
        int left = 0, right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            // 最后一段是 [l, l+1],此时mid等于l,nums[mid]==nums[l]无法判断较小的在左边还是右边也就是无法进一步缩小区间,如果和右端点比的话,mid等于l+1,nums[l]和nums[l+1]可以判断谁大谁小,也就可以进一步缩小区间
            // 同理,要是要搜索旋转数组的最大值,那么就要和左端点比了
            if (nums[mid] <= nums[right]) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
       return nums[left];
    }
}
⭐⭐⭐154. 寻找旋转排序数组中的最小值 II(right)

已知一个长度为 n 的数组,预先按照升序排列,经由 1n旋转 后,得到输入数组。给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须尽可能减少整个过程的操作步骤。

class Solution {
    public int findMin(int[] nums) {
        int len = nums.length, left = 0, right = len - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            // 对于有重复元素的情况,如果等于的话,只能把当前元素排除
            if (nums[mid] == nums[right]) {
                right--;
            } else if (nums[mid] < nums[right]) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return nums[left];
    }
}

26. 删除有序数组中的重复项

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致

class Solution {
    public int removeDuplicates(int[] nums) {
        int slow = 0;
        for (int fast = 1; fast < nums.length; fast++) {
            if (nums[fast] != nums[slow]) {
                nums[++slow] = nums[fast];
            }
        }
        return slow + 1;
    }
}
80. 删除有序数组中的重复项 II

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

class Solution {
    public int removeDuplicates(int[] nums) {
        int slow = 1;
        for (int fast = 2; fast < nums.length; fast++) {
            if (nums[fast] != nums[slow - 1]) {
                nums[++slow] = nums[fast];
            }
        }
        return slow + 1;
    }
}
⭕️⭐⭐⭐42. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

Solution1:

/*
对于每个下标为i的地方,我们看这个地方能不能放得下雨水,就是说它左边有没有比它高的,右边有没有比它高的。
对于能放下多少,只要看左边最高的有多高,右边最高的有多高,这两个一左一右最高的形成两个围墙把它围起来,这个地方放的水量就是
如果min(left_max, right_max) - height[i] > 0,则为min(left_max, right_max) - height[i],否则为0
时间复杂度:O(n^2)
空间复杂度:O(1)
*/
class Solution {
    public int trap(int[] height) {
        int ans = 0;
        int n = height.length;
        for (int i = 1; i < n - 1; i++) {
            int left_max = 0, right_max = 0;
            for (int l = i - 1; l >= 0; l--) {
                left_max = Math.max(left_max, height[l]);
            }
            for (int r = i + 1; r < n; r++) {
                right_max = Math.max(right_max, height[r]);
            }
            ans += (Math.max(Math.min(left_max, right_max) - height[i], 0));
        }
        return ans;
    }
}
执行用时:263 ms, 在所有 Java 提交中击败了5.05%的用户
内存消耗:38.1 MB, 在所有 Java 提交中击败了65.83%的用户

Solution2:

/* 
空间换时间
预先存好每个元素i的左边最大和右边最大,分别用left_max[i]和right_max[i]来记录,最后要求的时候直接用存好的值
时间复杂度:O(n)
空间复杂度:O(n)
*/ 
class Solution {
    public int trap(int[] height) {
        int ans = 0;
        int n = height.length;
        int[] left_max = new int[n], right_max = new int[n];
        // update left max value
        for (int i = 1; i < n; i++) {
            left_max[i] = Math.max(left_max[i - 1], height[i - 1]);
        }
        // update right max value
        for (int i = n - 2; i >= 0; i--) {
            right_max[i] = Math.max(right_max[i + 1], height[i + 1]);
        }
        for (int i = 1; i < n - 1; i++) {
            ans += (Math.max(Math.min(left_max[i], right_max[i]) - height[i], 0));
        }
        return ans;
    }
}
执行用时:1 ms, 在所有 Java 提交中击败了82.68%的用户
内存消耗:38.1 MB, 在所有 Java 提交中击败了63.43%的用户

Solution3:

/*
因为对于每个元素我们要找到它的左边最大和右边最大然后取这两个值得较小值,然后减去当前高度得到存水容量
因此每次我们不关心两个值分别是多少,我们实际上只关心两个值较小的那个是多少
我们用left_max记录当前left元素左边的最大值,right_max记录当前right元素右边的最大值
当left_max < right_max时,此时对于left而言,left左侧的最大值已经得到,现在要看left右侧的最大值
	如果在右边有比right_max小的元素,那么此时left右边最大的至少还是right_max > left_max;
	如果在右边有比right_max大的元素xxx,那么就是说left_max < right_max < xxx
此时left两边的最大值的较小值就是left_max;
同理可以得到right两边的最大值的较小值就是right_max
时间复杂度:O(n)
空间复杂度:O(1)
*/
class Solution {
    public int trap(int[] height) {
        int ans = 0;
        int n = height.length;
        // left_max记录当前left元素左边的最大值,right_max记录当前right元素右边的最大值
        // left_max: 0, 1, 2的左边最大值
        // right_max: n-1, n-2, n-3的右边最大值
        int left_max = 0, right_max = 0;
        int left = 0, right = n - 1;
        while (left < right) {
            left_max = Math.max(left_max, height[left]);    // 更新当前left左边最大值
            right_max = Math.max(right_max, height[right]); // 更新当前right右边最大值
            if (left_max < right_max) {
                // 由于left_max < right_max,所以两个挡板中left_max必然是较低的
                ans += left_max - height[left];
                left++;
            } else {
                ans += right_max - height[right];
                right--;
            }
        }
        return ans;
    }
}
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:37.9 MB, 在所有 Java 提交中击败了90.72%的用户
50. Pow(x, n)

实现 pow(x, n) ,即计算 xn 次幂函数(即,xn )。

class Solution {
    public double myPow(double x, int n) {
        if (n == 1) return x;
        if (n == 0) return 1;
        if (n == -1) return 1 / x;
        double half = myPow(x, n / 2);
        double mod = myPow(x, n % 2);
        return half * half * mod;
    }
}

class Solution {
    public double myPow(double x, int n) {
        if (n == 0) return 1.0;
        long b = n;
        if (b < 0) {
            x = 1 / x;
            b = -b;
        }
        double res = 1.0;
        while (b > 0) {
            if ((b & 1) != 0) res *= x;
            x *= x;
            b /= 2;
        }
        return res;
    }
}
56. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

/*
先根据第一个元素其次第二个元素升序排序
维护一个不重叠区间[start, end]
对于当前的i,如果能加入上一个不重叠区间,则更新不重叠区间的两端,也就是更新end
对于当前的i,如果不能加入上一个不重叠区间,则把上一个区间加入结果集,并更新当前区间[start, end]
对于最后一个单独的不重叠区间,要在遍历完之后单独加入结果集
*/
class Solution {
    public int[][] merge(int[][] intervals) {
        List<int[]> res = new ArrayList<>();
        Arrays.sort(intervals, (o1, o2) -> (o1[0] == o2[0]) ? o1[1] - o2[1] : o1[0] - o2[0]);
        int start = intervals[0][0], end = intervals[0][1];
        for (int i = 1; i < intervals.length; i++) {
            if (intervals[i][0] <= end) {
                end = Math.max(end, intervals[i][1]);
            } else {
                // 对于当前的i,如果不能加入上一个不重叠区间,则把上一个区间加入结果集,并更新当前区间
                res.add(new int[]{start, end});
                start = intervals[i][0];
                end = intervals[i][1];
            }
        }
        res.add(new int[]{start, end});
        return res.toArray(new int[0][0]);
    }
}
⭕️57. 插入区间

给你一个 无重叠的 *,*按照区间起始端点排序的区间列表。

在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。

class Solution {
    public int[][] insert(int[][] intervals, int[] newInterval) {
        List<int[]> res = new ArrayList<>();
        int i = 0;
        // [] [] [] [----] 原始区间的右端点一直在当前区间左端点左边,不用怀疑,直接丢进去
        while (i < intervals.length && intervals[i][1] < newInterval[0]) {
            res.add(intervals[i]);
            i++;
        }
        // 一直合并到最后一个左端点大于end
        while (i < intervals.length && intervals[i][0] <= newInterval[1]) {
            newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
            newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
            i++;
        }
        res.add(newInterval);
        // [----] [] [] [] 原始区间的左端点一直在当前区间的右端点右边,不用怀疑,直接丢进去
        while (i < intervals.length) {
            res.add(intervals[i]);
            i++;
        }
        return res.toArray(new int[0][]);
    }
}
71. 简化路径

给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/' 开头),请你将其转化为更加简洁的规范路径。

在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。任意多个连续的斜杠(即,'//')都被视为单个斜杠 '/' 。 对于此问题,任何其他格式的点(例如,'...')均被视为文件/目录名称。

请注意,返回的 规范路径 必须遵循下述格式:

  • 始终以斜杠 '/' 开头。
  • 两个目录名之间必须只有一个斜杠 '/'
  • 最后一个目录名(如果存在)不能'/' 结尾。
  • 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 '.''..')。

返回简化后得到的 规范路径

class Solution {
    public String simplifyPath(String path) {
        // split完之后会出现四种元素
        // 1. 空 2. "." 3. ".." 4. abc
        Deque<String> doubleQueue = new LinkedList<>();	// 注意双向链表
        String[] strs = path.split("/");
        for (String str : strs) {
            if ("".equals(str) || ".".equals(str)) continue;	// 空 和 . 直接跳过
            if ("..".equals(str)) {
                if (!doubleQueue.isEmpty()) doubleQueue.pollLast();
            } else {
                doubleQueue.offerLast(str);
            }
        }
        StringBuilder res = new StringBuilder();
        if (doubleQueue.isEmpty()) {
            res.append("/");
        } else {
            while (!doubleQueue.isEmpty()) {
                res.append("/");
                res.append(doubleQueue.pollFirst());
            }
        }
        
        return res.toString();
    }
}
⭕️⭐75. 颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,**原地**对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 012 分别表示红色、白色和蓝色。

class Solution {
    // 两次遍历,第一次把0排好,第二次把1排好
    public void sortColors(int[] nums) {
        // ptr先全部指向0,然后指向1
        // 第一轮先判断是否是0,是的话放到ptr上
        // 第二轮判断是否是1,是的话放到prt上
        int ptr = 0, n = nums.length, i = 0;
        while (i < n) {
            if (nums[i] == 0) {
                swap(nums, i, ptr++);
            }
            i++;
        }
        i = ptr;
        while (i < n) {
            if (nums[i] == 1) {
                swap(nums, i, ptr++);
            }
            i++;
        }
    }

    // 一次遍历,设置两个指针,left存0,right存2
    public void sortColors_1(int[] nums) {
        int len = nums.length;
        // i用来遍历,left存0,right存2
        int i = 0, left = 0, right = len - 1;
        while (i < len) {
            if (nums[i] == 0 && i > left) {
                swap(nums, i, left++);
            } else if (nums[i] == 2 && i < right) {
                swap(nums, i, right--);
            } else {
                i++;
            }
        }
    }

    private void swap(int[] nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}
✅⭐79. 单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

class Solution {
    boolean flag = false;

    public boolean exist(char[][] board, String word) {
        if (board == null || board.length == 0) return false;
        int m = board.length, n = board[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                boolean[][] vis = new boolean[m][n];
                dfs(word, 0, board, i, j, vis);
                if (flag) return true;
            }
        }
        return false;
    }

    private void dfs(String word, int index, char[][] board, int i, int j, boolean[][] vis) {
        if (index == word.length()) {
            flag = true;
            return;
        }
        if ((i < 0 || i >= board.length) || (j < 0 || j >= board[0].length) || vis[i][j] || word.charAt(index) != board[i][j]) {
            return;
        }
        vis[i][j] = true;
        dfs(word, index + 1, board, i + 1, j, vis);
        dfs(word, index + 1, board, i, j + 1, vis);
        dfs(word, index + 1, board, i - 1, j, vis);
        dfs(word, index + 1, board, i, j - 1, vis);
        vis[i][j] = false;
    }
}
⭕️⭐84. 柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

暴力法,每次查看以当前高度所能产生的最宽的矩形,即分别向左向右找第一个小于当前元素的下标l,r

class Solution {
    public int largestRectangleArea(int[] heights) {
        // for each column, find the first column lower than it on its two sides
        // left i right
        // heights[i] * ((right - 1) -  (left + 1) + 1);
        int n = heights.length, res = 0;
        for (int i = 0; i < n; i++) {
            int left = i, right = i;
            while (left >= 0 && heights[left] >= heights[i]) {
                left--;
            }
            while (right < n && heights[right] >= heights[i]) {
                right++;
            }
            res = Math.max(res, heights[i] * ((right - 1) -  (left + 1) + 1));
        }
        return res;
    }
}

对于向左向右找第一个小于当前元素的下标l,r,可以用单调递增栈来优化寻找

单调栈
单调栈分为单调递增栈和单调递减栈

11. 单调递增栈即栈内元素保持单调递增的栈
12. 同理单调递减栈即栈内元素保持单调递减的栈

操作规则(下面都以单调递增栈为例)

21. 如果新的元素比栈顶元素大,就入栈
22. 如果新的元素较小,那就一直把栈内元素弹出来,直到栈顶比新元素小

加入这样一个规则之后,会有什么效果

31. 栈内的元素是递增的
32. 当元素出栈时,说明这个新元素是出栈元素向后找第一个比其小的元素
新的栈顶元素 < 出栈元素 < 当前元素

举个例子,配合下图,现在索引在 6 ,栈里是 1 5 6 。
接下来新元素是 2 ,那么 6 需要出栈。
当 6 出栈时,右边 2 代表是 6 右边第一个比 6 小的元素。

当元素出栈后,说明新栈顶元素是出栈元素向前找第一个比其小的元素
当 6 出栈时,5 成为新的栈顶,那么 5 就是 6 左边第一个比 6 小的元素。
作者:ikaruga
链接:https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/84-by-ikaruga/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

图片.png

class Solution {
    public int largestRectangleArea(int[] heights) {
        int res = 0;
        Deque<Integer> stack = new ArrayDeque<>();
        int[] new_heights = new int[heights.length + 2];
        System.arraycopy(heights, 0, new_heights, 1, heights.length);
        for (int i = 0; i < new_heights.length; i++) {
            while (!stack.isEmpty() && new_heights[stack.peek()] > new_heights[i]) {
                int cur = stack.pop();
                int l = stack.peek();
                int r = i;
                // l是cur左边第一个比他小的,r是cur右边第一个比他小的
                // (r - 1) - (l + 1) + 1 = r - l - 1
                res = Math.max(res, (r - l - 1) * new_heights[cur]);
            }
            stack.push(i);
        }
        return res;
    }
}
⭕️⭐85. 最大矩形

给定一个仅包含 01 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。

img

/*
每次将其看作是一串柱形,然后判断一串柱形所能形成的而最大面积
*/
class Solution {
    public int maximalRectangle(char[][] matrix) {
        if (matrix == null || matrix.length == 0) return 0;
        int res = 0;
        int m = matrix.length, n = matrix[0].length;
        int[] heights = new int[n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == '1') {
                    heights[j] += 1;
                } else {
                    heights[j] = 0;
                }
            }
            res = Math.max(res, largestRectangleArea(heights));
        }
        return res;
    }

    private int largestRectangleArea(int[] heights) {
        int[] new_heights = new int[heights.length + 2];
        int res = 0;
        System.arraycopy(heights, 0, new_heights, 1, heights.length);
        Deque<Integer> stack = new ArrayDeque<>();
        for (int i = 0; i < new_heights.length; i++) {
            while (!stack.isEmpty() && new_heights[stack.peek()] > new_heights[i]) {
                int cur = stack.pop();
                int l = stack.peek();
                int r = i;
                res = Math.max(res, new_heights[cur] * ((r - 1) - (l + 1) + 1));
            }
            stack.push(i);
        }
        return res;
    }
}
120. 三角形最小路径和

给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 ii + 1

// 自顶向下 dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j])
class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        // dp[i][j]表示走到[i, j]处的最小路径和
        // 对于最后一行判断最小的路径和
        int m = triangle.size();
        int[][] dp = new int[m][m];
        dp[0][0] = triangle.get(0).get(0);
        for (int i = 1; i < m; i++) {
            dp[i][0] = dp[i - 1][0] + triangle.get(i).get(0);
            dp[i][i] = dp[i - 1][i - 1] + triangle.get(i).get(i);
        }
        for (int i = 2; i < m; i++) {
            for (int j = 1; j <= i - 1; j++) {
                dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle.get(i).get(j);
            }
        }
        int res = Integer.MAX_VALUE;
        for (int j = 0; j < m; j++) {
            res = Math.min(res, dp[m - 1][j]);
        }
        return res;
    }
}

// 自底向上 dp[i][j] <- min(dp[i + 1][j + 1], dp[i + 1][j])
class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int m = triangle.size();
        int[][] dp = new int[m + 1][m + 1];
        for (int i = m - 1; i >= 0; i--) {
            for (int j = 0; j < triangle.get(i).size(); j++) {
                dp[i][j] = triangle.get(i).get(j) + Math.min(dp[i + 1][j + 1], dp[i + 1][j]);
            }
        }
        return dp[0][0];
    }
}
// 自底向上 空间优化
class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int m = triangle.size();
        int[] dp = new int[m + 1];
        for (int i = m - 1; i >= 0; i--) {
            for (int j = 0; j < triangle.get(i).size(); j++) {
                dp[j] = triangle.get(i).get(j) + Math.min(dp[j + 1], dp[j]);
            }
        }
        return dp[0];
    }
}
// 还可以原地修改,因为用完当前层就不用了,所以可以原地修改数组
⭕️⭐128. 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

// 不要两边遍历,对于每个数字只让他往右边遍历,这样的话对于 1 2 3,遍历完 1 2 3之后,到了2,就不用重新得到1 2 3一次了
class Solution {
   public int longestConsecutive(int[] nums) {
       if (nums == null || nums.length == 0) return 0;
       Set<Integer> set = new HashSet<>();
       for (int num : nums) {
           set.add(num);
       }
       int res = 1;
       for (int num : nums) {
           if (!set.contains(num - 1)) {
               int cur = 1;
               int right = num + 1;
               while (set.contains(right++)) {
                   cur++;
               }
               res = Math.max(res, cur);
           }
       }
       return res;
   }
}
⭕️139. 单词拆分

给你一个字符串 s 和一个字符串列表 wordDict 作为字典,判定 s 是否可以由空格拆分为一个或多个在字典中出现的单词。

**说明:**拆分时可以重复使用字典中的单词。

class Solution {
   public boolean wordBreak(String s, List<String> wordDict) {
       int n = s.length();
       boolean[] dp = new boolean[n + 1];
       /*
       dp[i] 表示 [0, i-1] 中的元素是否可以拆分到 wordDict 中
       dp[j] 表示 [0, j - 1], 则要求 dp[j] && [j, i - 1]可以找到
        */
       dp[0] = true;
       for (int i = 1; i <= n; i++) {
           for (int j = 0; j < i; j++) {
               if (dp[j] && wordDict.contains(s.substring(j, i))) {
                   dp[i] = true;
                   break;
               }
           }
       }
       return dp[n];
   }
}
⭕️⭐⭐⭐146. LRU 缓存机制

实现 LRUCache 类:

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?

class LRUCache {
    class Node {
        int key, val;
        Node next, prev;

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }

    class DoubleList {
        Node head, tail;
        int size;

        public int getSize() {
            return this.size;
        }

        public DoubleList() {
            head = new Node(0, 0);
            tail = new Node(0, 0);
            head.next = tail;
            tail.prev = head;
            size = 0;
        }

        public void remove(Node node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
            size--;
        }

        public Node removeLast() {
            Node last = tail.prev;
            remove(last);
            return last;
        }

        public void addFirst(Node node) {
            Node p = head, q = head.next;
            node.prev = p;
            p.next = node;
            node.next = q;
            q.prev = node;
            size++;
        }
    }

    DoubleList doubleList;
    Map<Integer, Node> map;
    int capacity;

    public LRUCache(int capacity) {
        doubleList = new DoubleList();
        map = new HashMap<>();
        this.capacity = capacity;
    }

    public int get(int key) {
        if (!map.containsKey(key)) return -1;
        int val = map.get(key).val;
        put(key, val);
        return val;
    }

    public void put(int key, int value) {
        Node node = new Node(key, value);
        if (map.containsKey(key)) {
            doubleList.remove(map.get(key));
            doubleList.addFirst(node);
            map.put(key, node);
        } else {
            if (doubleList.getSize() == capacity) {
                Node last = doubleList.removeLast();
                map.remove(last.key);
            }
            doubleList.addFirst(node);
            map.put(key, node);
        }
    }
}
460. LFU 缓存

难度困难515

请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。

实现 LFUCache 类:

  • LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象
  • int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1
  • void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。

为了确定最不常使用的键,可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。

当一个键首次插入到缓存中时,它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 getput 操作,使用计数器的值将会递增。

函数 getput 必须以 O(1) 的平均时间复杂度运行。

class LFUCache {

    class Node {
        int key, value;
        int freq = 1;
        Node prev, next;
        
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    class DoubleList {
        Node head, tail;

        public DoubleList() {
            head = new Node(0, 0);
            tail = new Node(0, 0);
            head.next = tail;
            tail.prev = head;
        }

        public void addNode(Node node) {
            Node p = head.next;
            node.prev = head;
            head.next = node;
            node.next = p;
            p.prev = node;
        }

        public void removeNode(Node node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
        }   
    }

    Map<Integer, Node> cache; // 存储缓存的内容
    Map<Integer, DoubleList> freqMap; // 存储每个频次对应的双向链表
    int capacity;   // 缓存最大容量
    int min; // 存储当前最小频次

    public LFUCache(int capacity) {
        this.cache = new HashMap<> (capacity);
        this.freqMap = new HashMap<>();
        this.capacity = capacity;
        this.min = 0;
    }
    
    public int get(int key) {
        if (!cache.containsKey(key)) return -1;
        Node node = cache.get(key);
        freqInc(node);
        return node.value;
    }
    
    public void put(int key, int value) {
        if (capacity == 0) {
            return;
        }
        if (cache.containsKey(key)) {
            Node node = cache.get(key);
            node.value = value;
            freqInc(node);
        } else {
            if (cache.size() == capacity) {
                DoubleList minFreqList = freqMap.get(min);
                Node last = minFreqList.tail.prev;
                minFreqList.removeNode(last);
                cache.remove(last.key);
            }
            Node newNode = new Node(key, value);
            if (!freqMap.containsKey(1)) {
                freqMap.put(1, new DoubleList());
            }
            DoubleList list = freqMap.get(1);
            list.addNode(newNode);
            cache.put(key, newNode);
            min = 1;   
        }
    }

    public void freqInc(Node node) {
        // 从原freq对应的链表里移除, 并更新min
        int freq = node.freq;
        DoubleList list = freqMap.get(freq);
        list.removeNode(node);
        // 一定要注意这一步判断:移除当前元素后是否要更新minFreq
        if (freq == min && list.head.next == list.tail) { 
            min = freq + 1;
        }
        // 加入新freq对应的链表
        node.freq++;
        if (!freqMap.containsKey(freq + 1)) {
            freqMap.put(freq + 1, new DoubleList());
        }
        freqMap.get(freq + 1).addNode(node);
    }
}
151. 翻转字符串里的单词
class Solution {
    public String reverseWords(String s) {
        String temp = cleanExtraSpace(s);
        char[] chars = temp.toCharArray();
        reverse(chars, 0, chars.length - 1);
        reverseWord(chars);
        return new String(chars);
    }

    private String cleanExtraSpace(String s) {
        char[] chars = s.toCharArray();
        // 移除多余的空格
        int left = 0, right = chars.length - 1;
        StringBuilder sb = new StringBuilder();
        while (chars[left] == ' ') left++;
        while (chars[right] == ' ') right--;	// 先去除两端多余的空格
        while (left <= right) {
            if (chars[left] == ' ') {
                sb.append(chars[left]);	// 遇到中间的空格只加入一次
                while (left <= right && chars[left] == ' ') {
                    left++;
                }
            } else {
                sb.append(chars[left]);
                left++;
            }
        }
        return sb.toString();
    }

    private void reverse(char[] chars, int left, int right) {
        while (left < right) {
            char temp = chars[left];
            chars[left] = chars[right];
            chars[right] = temp;
            left++;
            right--;
        }
    }

    private void reverseWord(char[] chars) {
        int left = 0, right = 0, n = chars.length;
        while (right < n) {
            while (left < n && chars[left] == ' ') {
                left++;
            }
            right = left;
            while (right < n && chars[right] != ' ') {
                right++;
            }
            reverse(chars, left, right - 1);
            left = right;
        }
    }
}
165. 比较版本号

给你两个版本号 version1version2 ,请你比较它们。

版本号由一个或多个修订号组成,各修订号由一个 '.' 连接。每个修订号由 多位数字 组成,可能包含 前导零 。每个版本号至少包含一个字符。修订号从左到右编号,下标从 0 开始,最左边的修订号下标为 0 ,下一个修订号下标为 1 ,以此类推。例如,2.5.330.1 都是有效的版本号。

比较版本号时,请按从左到右的顺序依次比较它们的修订号。比较修订号时,只需比较 忽略任何前导零后的整数值 。也就是说,修订号 1 和修订号 001 相等 。如果版本号没有指定某个下标处的修订号,则该修订号视为 0 。例如,版本 1.0 小于版本 1.1 ,因为它们下标为 0 的修订号相同,而下标为 1 的修订号分别为 010 < 1

返回规则如下:

  • 如果 *version1* > *version2* 返回 1
  • 如果 *version1* < *version2* 返回 -1
  • 除此之外返回 0
class Solution {
    public int compareVersion(String version1, String version2) {
        int i = 0, j = 0;
        while (i < version1.length() || j < version2.length()) {
            int a = 0, b = 0;
            while (i < version1.length() && version1.charAt(i) != '.') {
                a = a * 10 + (version1.charAt(i++) - '0');
            }
            while (j < version2.length() && version2.charAt(j) != '.') {
                b = b * 10 + (version2.charAt(j++) - '0');
            }
            if (a > b) return 1;
            else if (a < b) return -1;
            i++;
            j++;
        }
        return 0;
    }
}
167. 两数之和 II - 输入有序数组

难度中等718

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1]numbers[index2] ,则 1 <= index1 < index2 <= numbers.length

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1index2

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int[] res = new int[2];
        if (numbers == null || numbers.length == 0) return res;
        int left = 0, right = numbers.length - 1;
        while (left < right) {
            int sum = numbers[left] + numbers[right];
            if (sum == target) {
                return new int[]{left + 1, right + 1};
            } else if (sum < target) {
                while (left < right && numbers[left] == numbers[left + 1]) left++;
                left++;
            } else {
                while (left < right && numbers[right] == numbers[right - 1]) right--;
                right--;
            }
        }
        return res;
    }
}
199. 二叉树的右视图

难度中等634

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

// 层序遍历,获取每一层的最后一个结点
class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if (root == null) return res;
        Deque<TreeNode> queue = new LinkedList<>();
        TreeNode p = root;
        queue.offer(p);
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                if (i == size - 1) res.add(queue.peek().val);
                p = queue.poll();
                if (p.left != null) queue.offer(p.left);
                if (p.right != null) queue.offer(p.right);
            }
        }
        return res;
    }
}
♦♦♦拓扑排序♦♦♦
✅⭐207. 课程表

你这个学期必须选修 numCourses 门课程,记为 0numCourses - 1

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai必须 先学习课程 bi

  • 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1

请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false

参考:

总结:拓扑排序问题
根据依赖关系,构建邻接表、入度数组。
选取入度为 0 的数据,根据邻接表,减小依赖它的数据指向的入度。
找出入度变为 0 的数据,重复第 2 步。
直至所有数据的入度为 0,得到排序,如果还有数据的入度不为 0,说明图中存在环。

作者:xiao_ben_zhu
链接:https://leetcode-cn.com/problems/course-schedule/solution/bao-mu-shi-ti-jie-shou-ba-shou-da-tong-tuo-bu-pai-/
来源:力扣(LeetCode)

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] indegree = new int[numCourses];
        Map<Integer, List<Integer>> map = new HashMap<>();
        
        for (int[] prerequisite : prerequisites) {
            int head = prerequisite[1], tail = prerequisite[0];
            indegree[tail]++;
            if (!map.containsKey(head)) map.put(head, new ArrayList<>());
            map.get(head).add(tail);
        }
        Deque<Integer> queue = new LinkedList<>();
        for (int i = 0; i < indegree.length; i++) {
            if (indegree[i] == 0) {
                queue.offer(i);
            }
        }
        int cnt = 0;
        while (!queue.isEmpty()) {
            int head = queue.poll();
            cnt++;
            if (map.containsKey(head)) {
                List<Integer> tails = map.get(head);
                for (int tail : tails) {
                    indegree[tail]--;
                    if (indegree[tail] == 0) {
                        queue.offer(tail);
                    }
                }
            }
        }
        return cnt == numCourses;
    }
}

// dfs
class Solution {
    // 对每个结点开始dfs,维护flags数组
    // flags[i] == 0,没有被dfs遍历过
    // flags[i] == -1,被其他结点开始的dfs遍历过
    // flags[i] == 1,被当前结点开始的dfs遍历过
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        Map<Integer, List<Integer>> map = new HashMap<>();
        int[] flags = new int[numCourses];
        for (int[] prerequisite : prerequisites) {
            int head = prerequisite[1], tail = prerequisite[0];
            if (!map.containsKey(head)) map.put(head, new ArrayList<>());
            map.get(head).add(tail);
        }
        for (int i = 0; i < numCourses; i++) {
            if (!dfs(map, i, flags)) return false;
        }
        return true;
    }

    private boolean dfs(Map<Integer, List<Integer>> map, int i, int[] flags) {
        if (flags[i] == -1) return true;
        if (flags[i] == 1)  return false;
        flags[i] = 1;
        if (map.containsKey(i)) {
            List<Integer> tails = map.get(i);
            for (int tail : tails) {
                if (!dfs(map, tail, flags)) return false;
            }
        }
        flags[i] = -1;
        return true;
    }
}

// 如果不知道课程的号码
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        Map<Integer, Integer> indegrees = new HashMap<>();	// 记录入度
        Map<Integer, List<Integer>> map = new HashMap<>();	// 记录图
        Set<Integer> courses = new HashSet<>();	// 记录课程号

        for (int[] prerequisite : prerequisites) {
            int head = prerequisite[1], tail = prerequisite[0];
            indegrees.put(tail, indegrees.getOrDefault(tail, 0) + 1);
            indegrees.put(head, indegrees.getOrDefault(head, 0));	// 注意也要将head加入防止找不到head
            courses.add(head);
            courses.add(tail);
            if (!map.containsKey(head)) map.put(head, new ArrayList<>());
            map.get(head).add(tail);
        }
        Deque<Integer> queue = new LinkedList<>();
        for (int tail : indegrees.keySet()) {
            if (indegrees.get(tail) == 0) {
                queue.offer(tail);
            }
        }
        int cnt = 0;
        while (!queue.isEmpty()) {
            int head = queue.poll();
            cnt++;
            if (map.containsKey(head)) {
                List<Integer> tails = map.get(head);
                for (int tail : tails) {
                    indegrees.put(tail, indegrees.get(tail) - 1);
                    if (indegrees.get(tail) == 0) {
                        queue.offer(tail);
                    }
                }
            }
        }
        return cnt == courses.size();
    }
}
✅⭐210. 课程表 II

现在你总共有 numCourses 门课需要选,记为 0numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai必须 先选修 bi

  • 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1]

返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        int[] res = new int[numCourses];
        int ind = 0;
        int[] indegree = new int[numCourses];   // value -> indegree
        Map<Integer, List<Integer>> map = new HashMap<>();   // value -> tails from value
        for (int[] prerequisite : prerequisites) {
            int head = prerequisite[1], tail = prerequisite[0];
            indegree[tail]++;
            if (!map.containsKey(head)) map.put(head, new ArrayList<>());
            map.get(head).add(tail);
        }
        Deque<Integer> queue = new LinkedList<>();
        for (int i = 0; i < indegree.length; i++) {
            if (indegree[i] == 0) {
                queue.offer(i);
            }
        }
        while(!queue.isEmpty()) {
            int head = queue.poll();
            res[ind++] = head;
            if (map.containsKey(head)) {
                List<Integer> tails = map.get(head);
                for (int tail : tails) {
                    indegree[tail]--;
                    if (indegree[tail] == 0) {
                        queue.offer(tail);
                    }
                }
            }
        }
        return ind == numCourses ? res : new int[0];
    }
}
⭕️⭐⭐⭐215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

  1. 快速排序
class Solution {
    public int findKthLargest(int[] nums, int k) {
        return quickSelect(nums, 0, nums.length - 1, k - 1);
    }

    private int quickSelect(int[] nums, int left, int right, int index) {
        int mid = partition(nums, left, right);
        if (mid == index) {
            return nums[mid];
        } else if (mid < index) {
            return quickSelect(nums, mid + 1, right, index);
        } else {
            return quickSelect(nums, left, mid - 1, index);
        }
    }

    private int partition(int[] nums, int left, int right) {
        /*
        为了防止pivot最大导致快排效果裂开,将基准随机交换一个元素
        每次让left元素跟后面随机一个元素交换,然后再置为pivot
        */
        int random_index = (int) (left + Math.random() * (right - left + 1));
        int temp = nums[left];
        nums[left] = nums[random_index];
        nums[random_index] = temp;
        // 加了上面的交换之后速度 9ms -> 1ms
        int pivot = nums[left];
        while (left < right) {
            // 从右到左找到第一个比基准大的
            while (left < right && nums[right] <= pivot) {
                right--;
            }
            // 放入left
            nums[left] = nums[right];
            // 从左到右找到第一个比基准小的
            while (left < right && nums[left] >= pivot) {
                left++;
            }
            // 放入right
            nums[right] = nums[left];
        }
        // 放入基准
        nums[left] = pivot;
        return left;
    }
}
  1. 优先队列
public class Solution {
    public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        // 使用一个含有 k 个元素的最小堆,PriorityQueue 底层是动态数组,为了防止数组扩容产生消耗,可以先指定数组的长度
        PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);
        for (int i = 0; i < k; i++) {
            minHeap.offer(nums[i]);
        }

        for (int i = k; i < len; i++) {
            int topElement = minHeap.peek();
            // 只要当前遍历的元素比堆顶元素大,堆顶弹出,遍历的元素进去
            if (nums[i] > topElement) {
                minHeap.poll();
                minHeap.offer(nums[i]);
            }
        }
        return minHeap.peek();
    }
}
  1. 堆排序

    顺序排序,建大根堆

    求最大的k个/第k个结点,建小根堆

/*
手动设置一个大小为k的小根堆,最后小根堆的根节点就是第k个最大的节点
*/
class Solution {
    public int findKthLargest(int[] nums, int k) {
        //前K个元素原地建堆
        buildHeap(nums,k);
        //遍历余下的元素
        for(int i = k; i < nums.length; i++){
            //比堆顶小,就跳过
            if(nums[i] < nums[0]) continue;
            //比堆顶大,与堆顶元素交换后重新堆化
            swap(nums,i,0);
            heapify(nums,k,0);
        }
        //K个元素的小堆顶的堆顶就是第K大元素
        return nums[0];
    }

    /**
     建堆函数
     从最后一个非叶子节点开始堆化,其下标为节点数n/2 - 1
     */
    private void buildHeap(int[] nums,int len){
        for(int i = len/2 - 1; i >= 0; i--){
            heapify(nums,len,i);
        }
    }
    /**
     堆化函数。建立小顶堆
     父节点的下标为i,左右孩子的下标分别为2i+1 和 2i+2
     */
    private void heapify(int[] nums,int len, int i){
        while(true){
            //临时变量minPos用于存储最小值的下标。先假设父节点最小
        	int minPos = i;
            //和左节点比较
            if(2 * i + 1 < len && nums[2 * i + 1] < nums[i]){
                minPos = 2 * i + 1;
            }
            //和右节点比较
            if(2 * i + 2 < len && nums[2 * i + 2] < nums[minPos]){
                minPos = 2 * i + 2;
            }
            //如果minpos没有变化,说明父节点已经是最小了,直接跳出
            if(minPos == i) break;

            //否则交换节点
            swap(nums,i,minPos);
            //交换后可能会引起下面大小关系变化,更新父节点,继续堆化
            i = minPos;
        }
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}
225. 用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false
class MyStack {
    private Queue<Integer> a;//输入队列
    private Queue<Integer> b;//输出队列
    
    public MyStack() {
        a = new LinkedList<>();
        b = new LinkedList<>();
    }
    
    public void push(int x) {
        a.offer(x);
        // 将b队列中元素全部转给a队列
        while(!b.isEmpty())
            a.offer(b.poll());
        // 交换a和b,使得a队列没有在push()的时候始终为空队列
        Queue temp = a;
        a = b;
        b = temp;
    }
    
    public int pop() {
        return b.poll();
    }
   
    public int top() {
        return b.peek();
    }
    
    public boolean empty() {
        return b.isEmpty();
    }
}
232. 用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 fals
class MyQueue {
    private Stack<Integer> a;// 输入栈
    private Stack<Integer> b;// 输出栈
    
    public MyQueue() {
        a = new Stack<>();
        b = new Stack<>();
    }
    
    public void push(int x) {
        a.push(x);
    }
    
    public int pop() {
        // 如果b栈为空,则将a栈全部弹出并压入b栈中,然后b.pop()
        if(b.isEmpty()){
            while(!a.isEmpty()){
                b.push(a.pop());
            }
        }
        return b.pop();
    }
    
    public int peek() {
        if(b.isEmpty()){
            while(!a.isEmpty()){
                b.push(a.pop());
            }
        }
        return b.peek();
    }
    
    public boolean empty() {
        return a.isEmpty() && b.isEmpty();
    }
}
✅⭐221. 最大正方形

在一个由 '0''1' 组成的二维矩阵内,找到只包含 '1' 的最大正方形,并返回其面积。

class Solution {
    public int maximalSquare(char[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return 0;
        int m = matrix.length, n = matrix[0].length;
        int[][] dp = new int[m][n];
        int max_len = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == '1') {
                    if (i == 0 || j == 0) {
                        dp[i][j] = 1;
                    } else {
                        dp[i][j] = 1 + Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]);
                    }
                    max_len = Math.max(max_len, dp[i][j]);
                }
            }
        }
        return max_len * max_len;
    }
}
347. 前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

class Solution {
	// Collections.sort
    public int[] topKFrequent_1(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        List<Map.Entry<Integer, Integer>> list = new LinkedList<>(map.entrySet());
        Collections.sort(list, (o1, o2) -> (o2.getValue() - o1.getValue()));
        int[] res = new int[k];
        for (int i = 0; i < k; i++) {
            res[i] = list.get(i).getKey();
        }
        return res;
    }

    // priorityQueue
    public int[] topKFrequent_2(int[] nums, int k) {
        int[] res = new int[k];
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        // 根据entry的值构建大小为k的小顶堆,遍历完堆中的就是值最大的k个entry
        Queue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> (o1.getValue() - o2.getValue()));
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            queue.offer(entry);
            if (queue.size() > k) {
                queue.poll();
            }
        }
        int i = 0;
        while (!queue.isEmpty()) {
            res[i++] = queue.poll().getKey();
        }
        return res;
    }

    // 桶排序
    public List<Integer> topKFrequent_3(int[] nums, int k) {
        if (nums == null || nums.length == 0 || k == 0)
            return new ArrayList<>();
        LinkedList<Integer>[] fre = new LinkedList[nums.length + 1];
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        for (Integer key : map.keySet()) {
            int value = map.get(key);
            if (fre[value] == null) {
                fre[value] = new LinkedList<>();
            }
            fre[value].add(key);
        }
        List<Integer> res = new ArrayList<>();
        //倒序遍历频率数组,这样可以获取频率从高到低的数字
        for (int i = fre.length - 1; i >= 0 && res.size() < k; i--) {
            LinkedList<Integer> list = fre[i];
            //如果当前频率没有数字,跳过
            if (list == null) {
                continue;
            }
            //相同频率的数字可能有多个,这个只取k个就行,注意顺序是从左往右取
            while (res.size() < k && !list.isEmpty()) {
                res.add(list.removeFirst());
            }
        }
        return res;
    }
}
414. 第三大的数

给你一个非空数组,返回此数组中 第三大的数 。如果不存在,则返回数组中最大的数。

class Solution {
    public int thirdMax(int[] nums) {
        Integer first = null, second = null, third = null;
        for (Integer cur : nums) {
            if(cur.equals(first) || cur.equals(second) || cur.equals(third)) continue;
            if(first == null || cur > first) {
                third = second;
                second = first;
                first = cur;
            } else if(second == null || cur > second) {
                third = second;
                second = cur;
            } else if(third == null || cur > third) {
                third = cur;
            }
        }
        return third == null ? first : third;
    }
}
⭕️⭐238. 除自身以外数组的乘积

给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

class Solution {
    // 时间复杂度 O(n)
    // 空间复杂度 O(n)
    // 维护leftMul rightMul,然后对应相乘
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] left = new int[n];
        int[] right = new int[n];
        left[0] = 1;
        for (int i = 1; i < n; i++) {
            left[i] = left[i - 1] * nums[i - 1];
        }
        right[n - 1] = 1;
        for (int j = n - 2; j >= 0; j--) {
            right[j] = right[j + 1] * nums[j + 1];
        }
        int[] res = new int[n];
        for (int i = 0; i < n; i++) {
            res[i] = left[i] * right[i];
        }
        return res;
    }

    // 时间复杂度 O(n)
    // 空间复杂度 O(1)
    // 将left存在res中,遍历right的时候直接把相应的right乘以res中的值然后赋值给res
    public int[] productExceptSelf_1(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        int left = 1;
        for (int i = 0; i < n; i++) {
            if (i > 0) {
                left *= nums[i - 1];
            }
            res[i] = left;
        }
        int right = 1;
        for (int j = n - 1; j >= 0; j--) {
            if (j < n - 1) {
                right *= nums[j + 1];
            }
            res[j] *= right;
        }
        return res;
    }
}
⭐⭐239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        // 最大的k个用小根堆,下标0处存的是最小的元素
        // 那么这里要用大根堆,下标0处存的是最大的元素	-> 超时 49 / 61 个通过测试用例
        int[] res = new int[nums.length - k + 1];
        int idx = 0, removeIdx = 0;
        PriorityQueue<Integer> queue = new PriorityQueue<>((o1, o2) -> (nums[o2] - nums[o1]));  
        for (int i = 0; i < nums.length; i++) {
            queue.offer(i);
            if (queue.size() == k) {
                res[idx++] = nums[queue.peek()];
                queue.remove(removeIdx++);
            }
        }
        return res;
    }
}

// 可以用单调递减栈,栈中存放的是根据下标元素降序,每次新元素加入要判断是否要弹出较小的结点
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length < 2) return nums;
        // 双向队列 保存当前窗口最大值的数组位置 保证队列中数组位置的数值按从大到小排序
        Deque<Integer> queue = new LinkedList();
        // 结果数组
        int[] res = new int[nums.length - k + 1];
        // 遍历nums数组
        for (int i = 0; i < nums.length; i++) {
            // 保证从大到小 如果前面数小则需要依次弹出,直至满足要求
            while (!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]) {
                queue.pollLast();
            }
            // 添加当前值对应的数组下标
            queue.offerLast(i);
            // 判断当前队列中队首的最大值是否有效,即该元素是否还在窗口内
            if (i - queue.peekFirst() + 1 > k) {
                queue.pollFirst();
            }
            // 当窗口长度为k时 保存当前窗口中最大值
            if (i >= k - 1) {
                res[i + 1 - k] = nums[queue.peekFirst()];
            }
        }
        return res;
    }
}
279. 完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

给你一个整数 n ,返回和为 n 的完全平方数的 最少数量

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,14916 都是完全平方数,而 311 不是。

  1. dp
/*
dp[i]表示表示数字i所需要的最少的完全平方数
*/
class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 0;
        for (int i = 1; i < n + 1; i++) {
            dp[i] = Integer.MAX_VALUE;
            for (int j = 1; j * j <= i; j++) {
                dp[i] = Math.min(dp[i], 1 + dp[i - j * j]);
            }
        }
        return dp[n];
    }
}
  1. 数学
/*
任何数都可以表示为4个完全平方数的和,即答案为 1 / 2 / 3 / 4
1 <=> a^2 = num;
2 <=> a^2 + b^2 = num;
4 <=> 4a*(8b+7) = num;
3 <=> 剩余情况
*/
class Solution {
    public int numSquares(int n) {
        if (isSquare(n)) return 1;
        if (checkAnswer4(n)) return 4;
        // 判断 i^2 + j^2 = n
        for (int i = 1; i * i <= n; i++) {
            int j = n - i * i;
            if (isSquare(j)) return 2;
        }
        return 3;
    }

    private boolean isSquare(int num) {
        int x = (int) Math.sqrt(num);
        return x * x == num;
    }

    // (4a)*(8b+7) == num 则是由四个平方数组成
    private boolean checkAnswer4(int num) {
        while (num % 4 == 0) {
            num /= 4;
        }
        return num % 8 == 7;
    }
}
♦♦♦二分♦♦♦
// 查找第一个值等于给定值的元素
private int firstEquals(int[] arr, int target) {
    int l = 0, r = arr.length - 1;
    while (l < r) {
        int mid = l + ((r - l) >> 1);
        if (arr[mid] < target) l = mid + 1;
        else r = mid; // 收缩右边界不影响 first equals
    }
    if (arr[l] == target && (l == 0 || arr[l - 1] < target)) return l;
    return -1;
}
// 查找最后一个值等于给定值的元素
private int lastEquals(int[] arr, int target) {
    int l = 0, r = arr.length - 1;
    while (l < r) {
        int mid = l + ((r - l + 1) >> 1);
        if (arr[mid] > target) r = mid - 1;
        else l = mid; // 收缩左边界不影响 last equals
    }
    if (arr[l] == target && (l == arr.length - 1 || arr[l + 1] > target)) return l;
    return -1;
}
// 查找第一个大于等于给定值的元素
private int firstLargeOrEquals(int[] arr, int target) {
    int l = 0, r = arr.length - 1;
    while (l < r) {
        int mid = l + ((r - l) >> 1);
        if (arr[mid] < target) l = mid + 1;
        else r = mid; // 收缩右边界不影响 first equals
    }
    if (arr[l] >= target && (l == 0 || arr[l - 1] < target)) return l; // >=
    return -1;
}
// 查找最后一个小于等于给定值的元素
private int lastLessOrEquals(int[] arr, int target) {
    int l = 0, r = arr.length - 1;
    while (l < r) {
        int mid = l + ((r - l + 1) >> 1);
        if (arr[mid] > target) r = mid - 1;
        else l = mid; // 收缩左边界不影响 last equals
    }
    if (arr[l] <= target && (l == arr.length - 1 || arr[l + 1] > target)) return l; // <=
    return -1;
}
♦♦♦重复数相关♦♦♦
剑指 Offer 03. 数组中重复的数字

找出数组中重复的数字。在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

class Solution {
    public int findRepeatNumber(int[] nums) {
        // 限定了数字的范围,因此可以原地哈希,不用额外开哈希表
        for (int i = 0; i < nums.length; i++) {
            while (nums[i] != i) {
                if (nums[i] == nums[nums[i]]) {
                    return nums[i];
                }
                swap(nums, i, nums[i]);
            }
        }
        return -1;
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}
287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

class Solution {
    public int findDuplicate(int[] nums) {
        // 范围都在 [1, n],就算都不同,那么就会出现有两个不同的下标,他们位置上的元素相同,建立从下标到数字的映射,将问题转化为链表有环的问题
        int slow = 0, fast = 0;
        while (true) {
            slow = nums[slow];
            fast = nums[nums[fast]];
            if (slow == fast) {
                slow = 0;
                while (slow != fast) {
                    slow = nums[slow];
                    fast = nums[fast];
                }
                return slow;
            }
        }
    }
}
136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

class Solution {
    public int singleNumber(int[] nums) {
        // a ^ 0 = a, a ^ a = 0
        int res = 0;
        for (int num : nums) {
            res ^= num;
        }
        return res;
    }
}
⭕️137. 只出现一次的数字 II

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。

class Solution {
    public int singleNumber(int[] nums) {
        // 每一位上相加,如果该位上是3的倍数,则结果该位为0,否则为1
        int[] digits = new int[32];
        for (int num : nums) {
            for (int i = 0; i < 32; i++) {
                digits[i] += (num & 1);
                num >>= 1;
            }
        }
        int res = 0;
        for (int i = 31; i >= 0; i--) {
            res <<= 1;
            if (digits[i] % 3 != 0) {
                res = (res | 1);	// 将该位数字置为1
            }
        }
        return res;
    }
}
⭕️260. 只出现一次的数字 III

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

class Solution {
   public int[] singleNumber(int[] nums) {
       // a ^ b = x
       // a和b至少有一位是不一样的,找出这个位置,然后对于数组中的每个数,根据这一位是否为1分为两组,两组分别异或,得到两组最后结果
       int x = 0;
       for (int num : nums) {
           x ^= num;
       }
       int idx = 0;
       while ((x & 1) == 0) {
           x >>= 1;
           idx++;
       }
       int a = 0, b = 0;
       for (int num : nums) {
           if (((num >> idx) & 1) == 0) {
               a ^= num;
           } else {
               b ^= num;
           }
       }
       return new int[]{a, b};
   }
}
♦♦♦♦♦♦
⭐⭐295. 数据流的中位数

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

  • void addNum(int num) - 从数据流中添加一个整数到数据结构中。
  • double findMedian() - 返回目前所有元素的中位数。
class MedianFinder {
    Queue<Integer> maxQueue;    // 最大堆,根节点值最大,用来存较小的一半值
    Queue<Integer> minQueue;    // 最小堆,根节点值最小,用来存较大的一半值

    public MedianFinder() {
        maxQueue = new PriorityQueue<>((o1, o2) -> (o2 - o1));
        minQueue = new PriorityQueue<>((o1, o2) -> (o1 - o2));
    }
    
    // 大根堆 小根堆
    // maxQueue minQueue
    // size(maxQueue) = 1 + size(minQueue) 
    // 插入元素,先插入到maxQueue然后弹出一个元素插入到minQueuee
    // 查找元素,就是 maxQueue的根节点
    // size(minQueue) = size(maxQueue)
    // 插入元素,先插入到minQuuee中然后弹出一个元素插入到maxQueue中
    // 查找元素,当前两个根的平均
    public void addNum(int num) {
        if (maxQueue.size() == minQueue.size() + 1) {
            maxQueue.offer(num);
            minQueue.offer(maxQueue.poll());
        } else {
            minQueue.offer(num);
            maxQueue.offer(minQueue.poll());
        }
    }
    
    public double findMedian() {
        if (maxQueue.size() == minQueue.size() + 1) {
            return maxQueue.peek();
        } else {
            return (minQueue.peek() + maxQueue.peek()) / 2.0;
        }
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */
301. 删除无效的括号

给你一个由若干括号和字母组成的字符串 s ,删除最小数量的无效括号,使得输入的字符串有效。

返回所有可能的结果。答案可以按 任意顺序 返回。

class Solution {
    List<String> res = new ArrayList<>();

    public List<String> removeInvalidParentheses(String s) {
        int lremove = 0, rremove = 0;
        for (char c : s.toCharArray()) {
            if (c == '(') {
                lremove++;
            } else {
                if (lremove == 0) {
                    rremove++;
                } else {
                    lremove--;
                }
            }
        }
        helper(s, 0, lremove, rremove);
        return res;
    }

    private void helper(String str, int start, int lremove, int rremove) {
        if (lremove == 0 && rremove == 0 && isValid(str)) {
            res.add(str);
            return;
        }

        for (int i = start; i < str.length(); i++) {
            if (i != start && str.charAt(i) == str.charAt(i - 1)) {
                continue;
            }
            // 如果剩余的字符无法满足去掉的数量要求,直接返回
            if (lremove + rremove > str.length() - i) {
                return;
            }
            // 尝试去掉一个左括号
            if (lremove > 0 && str.charAt(i) == '(') {
                helper(str.substring(0, i) + str.substring(i + 1), i, lremove - 1, rremove);
            }
            // 尝试去掉一个右括号
            if (rremove > 0 && str.charAt(i) == ')') {
                helper(str.substring(0, i) + str.substring(i + 1), i, lremove, rremove - 1);
            }
        }
    }

    private boolean isValid(String s) {
        int cnt = 0;
        char[] chars = s.toCharArray();
        for (char c : chars) {
            if (c == '(') {
                cnt++;
            } else if (c == ')') {
                cnt--;
                if (cnt < 0) return false;
            }
        }
        return cnt == 0;
    }
}
338. 比特位计数

给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。

  1. n & (n - 1) 将最低位的1置为0,直到最终n为0,判断与的次数就是有多少个1
  2. n & (2i)如果结果为2i,说明第i位为1
  3. n & 1断最低位是不是1
class Solution {
    // Brian Kernighan 算法
    public int[] countBits_1(int n) {
        int[] res = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            res[i] = countOnes(i);
        }
        return res;
    }

    private int countOnes(int n) {
        int cnt = 0;
        while (n != 0) {
            n = n & (n - 1);
            cnt++;
        }
        return cnt;
    }

    public int[] countBits_2(int n) {
        int[] res = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            res[i] = res[i & (i - 1)] + 1;
        }
        return res;
    }

    public int[] countBits(int n) {
        int[] res = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            res[i] = res[i >> 1] + (i & 1);
        }
        return res;
    }
}
394. 字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a2[4] 的输入。

class Solution {
    public String decodeString(String s) {
        char[] charArray = s.toCharArray();
        Deque<Character> stack = new LinkedList<>();
        for (char c : charArray) {
            if (c != ']') {
                stack.push(c);
            } else {
                StringBuilder subStr = new StringBuilder(); // 存储数字 * []中的字符串
                // 得到字符串从栈中弹出之后进行头插得到正序的子字符串
                while (!stack.isEmpty() && (stack.peek() >= 'a' && stack.peek() <= 'z')) {
                    subStr.insert(0, stack.pop());
                }
                // 弹出 '['
                stack.pop();
                // 得到从栈中弹出之后进行运算得到的次数
                int exp = 0, cnt = 0;
                while (!stack.isEmpty() && (stack.peek() >= '0' && stack.peek() <= '9')) {
                    cnt = (int) ((stack.pop() - '0') * Math.pow(10, exp++)) + cnt;
                }
                // 将 cnt[subStr]格式的进行转换插入到栈中
                while (cnt > 0) {
                    for (char ch : subStr.toString().toCharArray()) {
                        stack.push(ch);
                    }
                    cnt--;
                }
            }
        }
        // 由于栈是从栈顶遍历到栈底的,因此要进行逆转操作,此处使用头插法
        StringBuilder res = new StringBuilder();
        for (char c : stack) {
            res.insert(0, c);
        }
        return res.toString();
    }

    public String decodeString_1(String s) {
        char[] charArray = s.toCharArray();
        Deque<String> stackStr = new LinkedList<>();    // 存待处理字符串
        Deque<Integer> stackInt = new LinkedList<>();   // 存待处理系数
        StringBuilder curStr = new StringBuilder();     // 以及处理的结果
        int multi = 0;
        for (char c : charArray) {
            if (c >= '0' && c <= '9') {
                multi = multi * 10 + c - '0';
            } else if (c == '[') {
                stackInt.push(multi);
                stackStr.push(curStr.toString());
                multi = 0;
                curStr = new StringBuilder();
            } else if (c == ']') {
                StringBuilder temp = new StringBuilder(stackStr.pop());
                int cnt = stackInt.pop();
                for (int i = 0; i < cnt; i++) {
                    temp.append(curStr);
                }
                curStr = temp;
            } else {
                curStr.append(c);
            }
        }
        return curStr.toString();
    }
}
—并查集—
990. 等式方程的可满足性

给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:"a==b""a!=b"。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。

只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false

class Solution {
    public boolean equationsPossible(String[] equations) {
        int[] parent = new int[26];
        for (int i = 0; i < 26; i++) {
            parent[i] = i;
        }
        for (String equation : equations) {
            if ("==".equals(equation.substring(1, 3))) {
                int idx1 = equation.charAt(0) - 'a', idx2 = equation.charAt(3) - 'a';
                merge(parent, idx1, idx2);
            }
        }
        for (String equation : equations) {
            if ("!=".equals(equation.substring(1, 3))) {
                int idx1 = equation.charAt(0) - 'a', idx2 = equation.charAt(3) - 'a';
                if (find(parent, idx1) == find(parent, idx2)) return false;
            }
        }
        return true;
    }

    private void merge(int[] parent, int idx1, int idx2) {
        parent[find(parent, idx1)] = find(parent, idx2);
    }

    private int find(int[] parent, int idx) {
        if (parent[idx] == idx) {
            return idx;
        } else {
            return find(parent, parent[idx]);
        }
    }
}
399. 除法求值

给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件,其中 equations[i] = [Ai, Bi]values[i] 共同表示等式 Ai / Bi = values[i] 。每个 AiBi 是一个表示单个变量的字符串。

另有一些以数组 queries 表示的问题,其中 queries[j] = [Cj, Dj] 表示第 j 个问题,请你根据已知条件找出 Cj / Dj = ? 的结果作为答案。

返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -1.0 替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0 替代这个答案。

**注意:**输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果。

class Solution {
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        int n_var = 0;
        Map<String, Integer> variables = new HashMap<>();

        int n = equations.size();
        for (List<String> equation : equations) {
            if (!variables.containsKey(equation.get(0))) {
                variables.put(equation.get(0), n_var++);
            }
            if (!variables.containsKey(equation.get(1))) {
                variables.put(equation.get(1), n_var++);
            }
        }
        int[] f = new int[n_var];
        double[] w = new double[n_var];
        for (int i = 0; i < n_var; i++) {
            f[i] = i;
            w[i] = 1.0;
        }

        for (int i = 0; i < n; i++) {
            int va = variables.get(equations.get(i).get(0)), vb = variables.get(equations.get(i).get(1));
            merge(f, w, va, vb, values[i]);
        }
        int queriesCount = queries.size();
        double[] ret = new double[queriesCount];
        for (int i = 0; i < queriesCount; i++) {
            List<String> query = queries.get(i);
            double result = -1.0;
            if (variables.containsKey(query.get(0)) && variables.containsKey(query.get(1))) {
                int ia = variables.get(query.get(0)), ib = variables.get(query.get(1));
                int fa = find(f, w, ia), fb = find(f, w, ib);
                if (fa == fb) {
                    result = w[ia] / w[ib];
                }
            }
            ret[i] = result;
        }
        return ret;
    }

    private void merge(int[] f, double[] w, int x, int y, double val) {
        int fx = find(f, w, x);
        int fy = find(f, w, y);
        f[fx] = fy;
        w[fx] = val * w[y] / w[x];
    }

    private int find(int[] f, double[] w, int x) {
        if (f[x] != x) {
            int father = find(f, w, f[x]);
            w[x] = w[x] * w[f[x]];
            f[x] = father;
        }
        return f[x];
    }
}
406. 根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        // 按照身高降序 K升序排序
        Arrays.sort(people, (o1, o2) -> o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0]);
        List<int[]> res = new ArrayList<>();
        // K值定义为 排在h前面且身高大于或等于h的人数
        // 因为从身高降序开始插入,此时所有人身高都大于等于h
        // 因此K值即为需要插入的位置
        for (int[] arr : people) {
            res.add(arr[1], arr);
        }
        return res.toArray(new int[res.size()][]);
    }
}
438. 找到字符串中所有字母异位词

给定两个字符串 sp,找到 s 中所有 p异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

class Solution {
    // 每次新建固定大小的窗口
    public List<Integer> findAnagrams_1(String s, String p) {
        List<Integer> res = new ArrayList<>();
        int[] pMap = new int[26];
        for (char c : p.toCharArray()) {
            pMap[c - 'a'] += 1;
        }
        for (int i = 0; i <= s.length() - p.length(); i++) {
            int[] subMap = new int[26];
            String sub = s.substring(i, i + p.length());
            for (char c : sub.toCharArray()) {
                subMap[c - 'a'] += 1;
            }
            if (Arrays.equals(pMap, subMap)) res.add(i);
        }
        return res;
    }

    // 优化1:这个窗口每次都是向后移动一位,所以除了第一位和最后一位,中间的都是不变的
    public List<Integer> findAnagrams_2(String s, String p) {
        List<Integer> res = new ArrayList<>();
        if (s.length() < p.length()) return res;
        int[] pMap = new int[26], subMap = new int[26];
        for (char c : p.toCharArray()) {
            pMap[c - 'a'] += 1;
        }
        for (int i = 0; i < p.length(); i++) {
            subMap[s.charAt(i) - 'a'] += 1;
        }
        if (Arrays.equals(pMap, subMap)) res.add(0);
        // 窗口的起始坐标范围为 [0, s.length() - p.length()],所以判断是否可以向后滑动要求最后一个起始坐标不是最后一个
        for (int i = 0; i <= s.length() - p.length() - 1; i++) {
            // 此时可以向后移动, 区间范围为 [i, i + p.length() - 1]
            subMap[s.charAt(i) - 'a']--;
            subMap[s.charAt(i + p.length()) - 'a']++;
            if (Arrays.equals(pMap, subMap)) res.add(i + 1);
        }
        return res;
    }

    // 上面优化1开了两个数组,其实可以只开一个数组,当遍历完窗口时,将对应的值减少,如果最后都是1,说明其实就是对应相等
    public List<Integer> findAnagrams(String s, String p) {
        int sLen = s.length(), pLen = p.length();
        if (sLen < pLen) {
            return new ArrayList<Integer>();
        }
        List<Integer> ans = new ArrayList<Integer>();
        // count表示当前窗口哪些字母多了或者少了
        int[] count = new int[26];
        for (int i = 0; i < pLen; ++i) {
            count[s.charAt(i) - 'a']++;
            count[p.charAt(i) - 'a']--;
        }
        int differ = 0;
        for (int val : count) {
            if (val != 0) differ++;
        }
        if (differ == 0) ans.add(0);
        for (int i = 0; i <= sLen - pLen - 1; ++i) {
            int old_head = count[s.charAt(i) - 'a'];
            if (old_head == 1) {  // 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同
                differ--;
            } else if (old_head == 0) {  // 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同
                differ++;
            }
            count[s.charAt(i) - 'a']--;
            int new_tail = count[s.charAt(i + pLen) - 'a'];
            if (new_tail == -1) {  // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从不同变得相同
                differ--;
            } else if (new_tail == 0) {  // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从相同变得不同
                differ++;
            }
            count[s.charAt(i + pLen) - 'a']++;
            if (differ == 0) ans.add(i + 1);
        }
        return ans;
    }
}
448. 找到所有数组中消失的数字

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

class Solution {
    public List<Integer> findDisappearedNumbers_1(int[] nums) {
        List<Integer> res = new ArrayList<>();
        Map<Integer, Integer> map = new HashMap<>();
        int n = nums.length;
        for (int num = 1; num <= n; num++) {
            map.put(num, 1);
        }
        for (int num : nums) {
            map.put(num, 0);
        }
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if (entry.getValue() != 0) {
                res.add(entry.getKey());
            }
        }
        return res;
    }

    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            if (nums[Math.abs(nums[i]) - 1] > 0) {
                nums[Math.abs(nums[i]) - 1] = -nums[Math.abs(nums[i]) - 1];
            }
        }
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > 0) {
                res.add(i + 1);
            }
        }
        return res;
    }
}
⭐⭐560. 和为 K 的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回该数组中和为 k 的连续子数组的个数。

// 1. 暴力,每次固定一个开始数字然后往右遍历
// 2. 前缀和
class Solution {
    // 用prefix[i]表示前i个元素的和
    public int subarraySum_1(int[] nums, int k) {
        int len = nums.length;
        int[] prefix = new int[len + 1];
        prefix[0] = 0;
        for (int i = 0; i < len; i++) {
            prefix[i + 1] = prefix[i] + nums[i];
        }
        int cnt = 0;
        for (int i = 0; i < len; i++) {
            for (int j = i; j < len; j++) {
                if (prefix[j + 1] - prefix[i] == k) cnt++;
            }
        }
        return cnt;
    }

    // 用prefix的map来记录前缀和对应其出现的次数
    // map记录前缀和可以用的种数
    public int subarraySum(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        int sum = 0, res = 0;
        map.put(0, 1);
        for(int num : nums) {
            sum += num;
            if (map.containsKey(sum - k)) {
                res += map.get(sum - k);
            }
            map.put(sum, map.getOrDefault(sum, 0) + 1);
        }
        return res;
    }
}
191. 位1的个数

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。

class Solution {
    // 1. 检查第i位时,将n与2^i次方进行与运算,如果与完之后等于2^i,说明该位置是1,如果与完之后等于0,说明该位置是0
    // 1 0 0 1 0
    // 0 0 0 1 0
    public int hammingWeight(int n) {
        int cnt = 0;
        for (int i = 0; i < 32; i++) {
            if ((n & (1 << i)) != 0) cnt++;
        }
        return cnt;
    }

    // 2. 每次检查最低位是不是1,是则计数,每次判断完最低位之后都要右移一位
    public int hammingWeight_1(int n) {
        int cnt = 0;
        for (int i = 0; i < 32; i++) {
            if ((n & 1) == 1) cnt++;
            n = n >> 1;
        }
        return cnt;
    }

    // 3. 通过n & (n-1)来将n最低位的1置为0,直到n等于0为止
    // n:         1 1 0 1 | 1 0 0 0
    // n-1:       1 1 0 1 | 0 1 1 1
    // n & (n-1): 1 1 0 1 | 0 0 0 0
    public int hammingWeight_2(int n) {
        int cnt = 0;
        while (n != 0) {
            n = n & (n - 1);
            cnt++;
        }
        return cnt;
    }
}
547. 省份数量

难度中等757

n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

// dfs
class Solution {
    public int findCircleNum(int[][] isConnected) {
        // 从一个没有标记过的元素开始dfs,dfs的元素标记为已访问
        // dfs结束将结果加1
        int res = 0;
        int n = isConnected.length;
        boolean[] vis = new boolean[n];
        for (int i = 0; i < n; i++) {
            if (!vis[i]) {
                dfs(isConnected, i, vis);
                res++;
            }
        }
        return res;
    }

    private void dfs(int[][] isConnected, int i, boolean[] vis) {
        vis[i] = true;
        for (int j = 0; j < isConnected.length; j++) {
            if (isConnected[i][j] == 1 && !vis[j]) {
                dfs(isConnected, j, vis);
            }
        }
    }
}

// bfs
class Solution {
    public int findCircleNum(int[][] isConnected) {
        int n = isConnected.length;
        boolean[] vis = new boolean[n];
        Deque<Integer> queue = new LinkedList<>();
        int res = 0;
        for (int i = 0; i < n; i++) {
            if (!vis[i]) {
                res++;
                queue.offer(i);
                vis[i] = true;
                while (!queue.isEmpty()) {
                    int v = queue.poll();
                    for (int w = 0; w < n; w++) {
                        if (isConnected[v][w] == 1 && !vis[w]) {
                            queue.offer(w);
                            vis[w] = true;
                        }
                    }
                }
            }
        }
        return res;
    }
}
538. 把二叉搜索树转换为累加树

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

提醒一下,二叉搜索树满足下列约束条件:

  • 节点的左子树仅包含键 小于 节点键的节点。
  • 节点的右子树仅包含键 大于 节点键的节点。
  • 左右子树也必须是二叉搜索树。
class Solution {
    public TreeNode convertBST(TreeNode root) {
        TreeNode p = root;
        Deque<TreeNode> stack = new LinkedList<>();
        int largerSum = 0;
        while (!stack.isEmpty() || p != null) {
            while (p != null) {
                stack.push(p);
                p = p.right;
            }
            p = stack.pop();
            p.val = p.val + largerSum;
            largerSum = p.val;
            p = p.left;
        }
        return root;
    }
}
415. 字符串相加

给定两个字符串形式的非负整数 num1num2 ,计算它们的和并同样以字符串形式返回。

你不能使用任何內建的用于处理大整数的库(比如 BigInteger), 也不能直接将输入的字符串转换为整数形式。

class Solution {
    public String addStrings(String num1, String num2) {
        StringBuilder res = new StringBuilder();
        int i = num1.length() - 1, j = num2.length() - 1, carry = 0;
        while(i >= 0 || j >= 0){
            int n1 = i >= 0 ? num1.charAt(i) - '0' : 0;
            int n2 = j >= 0 ? num2.charAt(j) - '0' : 0;
            int tmp = n1 + n2 + carry;
            carry = tmp / 10;
            res.append(tmp % 10);
            i--; 
            j--;
        }
        if(carry == 1) res.append(1);
        return res.reverse().toString();
    }
}
43. 字符串相乘

给定两个以字符串形式表示的非负整数 num1num2,返回 num1num2 的乘积,它们的乘积也表示为字符串形式。

**注意:**不能使用任何内置的 BigInteger 库或直接将输入转换为整数。

class Solution {
    public String multiply(String num1, String num2) {
        if (num1 == null || num2 == null || "0".equals(num1) || "0".equals(num2) || num1.length() == 0 || num2.length() == 0) return "0";
        String ans = "0";
        for (int i = num2.length() - 1; i >= 0; i--) {
            int y = num2.charAt(i) - '0';
            StringBuilder sb = new StringBuilder();
            for (int j = num2.length() - 1; j > i; j--) {
                sb.append("0");
            }
            int carry = 0;
            for (int j = num1.length() - 1; j >= 0; j--) {
                int x = num1.charAt(j) - '0';
                int mul = x * y + carry;
                int cur = mul % 10;
                carry = mul / 10;
                sb.append(cur);
            }
            if (carry != 0) sb.append(carry);
            ans = add(ans, sb.reverse().toString());
        }
        return ans;
    }

    private String add(String num1, String num2) {
        int i = num1.length() - 1, j = num2.length() - 1, carry = 0;
        StringBuilder sb = new StringBuilder();
        while (i >= 0 || j >= 0 || carry != 0) {
            int n1 = i >= 0 ? num1.charAt(i) - '0': 0;
            int n2 = j >= 0 ? num2.charAt(j) - '0' : 0;
            int sum = n1 + n2 + carry;
            carry = sum / 10;
            sb.append(sum % 10);
            i--;
            j--;
        }
        return sb.reverse().toString();
    }
}
class Solution {
    public String multiply(String num1, String num2) {
        if (num1 == null || num2 == null || "0".equals(num1) || "0".equals(num2) || num1.length() == 0 || num2.length() == 0) return "0";
        // m n 结果最长为 m + n
        // num1[m] * num2[n] 的结果存放在 num1[i] x num2[j] 的结果为 tmp(位数为两位,"0x","xy"的形式),其第一位位于 res[i+j],第二位位于 res[i+j+1]。
        int m = num1.length(), n = num2.length();
        int[] helper = new int[m + n];
        int carry = 0;
        for (int i = m - 1; i >= 0; i--) {
            int x = num1.charAt(i) - '0';
            for (int j = n - 1; j >= 0; j--) {
                int y = num2.charAt(j) - '0';
                int sum = (helper[i + j + 1] + x * y);
                helper[i + j + 1] = sum % 10;
                helper[i + j] += sum / 10;  // 进到上一位可能有很多种可能进上去
            }
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < helper.length; i++) {
            if (i == 0 && helper[i] == 0) continue;
            sb.append(helper[i]);
        }
        return sb.toString();
    }
}
918. 环形子数组的最大和

难度中等338

给定一个长度为 n环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和

class Solution {
    public int maxSubarraySumCircular(int[] nums) {
        // maxDp/minDp表示以i结尾的子数组的最大/最小和
        // 注意要是全为负数,则返回max
        int n = nums.length;
        int[] maxDp = new int[n], minDp = new int[n];
        maxDp[0] = nums[0];
        minDp[0] = nums[0];
        int max = maxDp[0], min = maxDp[0], sum = nums[0];
        for (int i = 1; i < n; i++) {
            maxDp[i] = Math.max(maxDp[i - 1] + nums[i], nums[i]);
            max = Math.max(max, maxDp[i]);
            minDp[i] = Math.min(minDp[i - 1] + nums[i], nums[i]);
            min = Math.min(min, minDp[i]);
            sum += nums[i];
        }
        if (max < 0) return max;
        return Math.max(max, sum - min);
    }
}
1360. 日期之间隔几天

请你编写一个程序来计算两个日期之间隔了多少天。

日期以字符串形式给出,格式为 YYYY-MM-DD

class Solution {
    int[] m_days = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    
    public int daysBetweenDates(String date1, String date2) {
        String[] strs1 = date1.split("-");
        String[] strs2 = date2.split("-");
        int year1 = Integer.parseInt(strs1[0]), month1 = Integer.parseInt(strs1[1]), day1 = Integer.parseInt(strs1[2]);
        int year2 = Integer.parseInt(strs2[0]), month2 = Integer.parseInt(strs2[1]), day2 = Integer.parseInt(strs2[2]);
        return Math.abs(getGap(year2, month2, day2) - getGap(year1, month1, day1));
    }

    private int getGap(int year, int month, int day) {
        int gap = 0;
        for (int y = 1971; y < year; y++) {
            gap += isSpecialYear(y) ? 366 : 365;
        }
        for (int i = 1; i < month; i++) {
            if (i == 2) gap += (28 + (isSpecialYear(year) ? 1 : 0));
            else gap += m_days[i];
        }
        return gap + day;
    }

    private boolean isSpecialYear(int year) {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }
}
⭐⭐1542. 找出最长的超赞子字符串

给你一个字符串 s 。请返回 s 中最长的 超赞子字符串 的长度。

「超赞子字符串」需满足满足下述两个条件:

  • 该字符串是 s 的一个非空子字符串
  • 进行任意次数的字符交换后,该字符串可以变成一个回文字符串

首先,超赞字符串 <=> 字符串中最多只有一个字符的总个数为奇数
我们不需要知道具体出现多少个,可以用1来表示出现了奇数次,用0来表示出现了偶数次
那么对于这个字符串可以将其表示为 status = 00 0000 0010这样
其中1表示该位的数字出现了奇数次,区间为 [i, j] = [0, j] - [0, i-1]
[0, j] 和 [0, i-1] 的状态要么一样,要么只有一位不一样
一样的话,直接获取该状态的下标,两者相减,否则将当前状态加入
不一样的话,对status的每一位先反转即1->0, 0->1,然后找反转后status是否存在过,存在过也可以更新

class Solution {
    public int longestAwesome(String s) {
        // 记录 status 与其对应的下标
        // status 就是记录每个出现的次数是偶数还是奇数
        // 每次加入新的字符,将它参与status计算,判断之前是否出现相同的status
        // 或者只有一位不一样的status
        // 有的话都要参与结果判断
        int res = 0;
        Map<Integer, Integer> map = new HashMap<>(); 
        map.put(0, -1);
        int status = 0;
        for (int i = 0; i < s.length(); i++) {
            int num = s.charAt(i) - '0';
            status ^= (1 << num);
            if (map.containsKey(status)) {
                res = Math.max(res, i - map.get(status));
            } else {
                map.put(status, i);
            }
            for (int j = 0; j <= 9; j++) {
                if (map.containsKey(status ^ (1 << j))) {
                    res = Math.max(res, i - map.get(status ^ (1 << j)));
                }
            }
        }
        return res;
    }
}
1539. 第 k 个缺失的正整数

难度简单75

给你一个 严格升序排列 的正整数数组 arr 和一个整数 k

请你找到这个数组里第 k 个缺失的正整数。

class Solution {
    public int findKthPositive(int[] arr, int k) {
        int n = arr.length, begin = 1, cnt;
        for (int num : arr) {
            if (begin < num) {
                // [begin, num)
                cnt = num - 1 - begin + 1 <= k ? num - begin : k;
                k -= cnt;
                if (k == 0) return begin + cnt - 1;
            }
            begin = num + 1;
        }
        return arr[n - 1] + k;
    }
}
2195. 向数组中追加 K 个整数

给你一个整数数组 nums 和一个整数 k 。请你向 nums 中追加 k 出现在 nums 中的、互不相同 整数,并使结果数组的元素和 最小

返回追加到 nums 中的 k 个整数之和。

class Solution {
    public long minimalKSum(int[] nums, int k) {
        // 3 5 9
        // begin 记录每个区间的首,num 即尾部
        // [begin, num)区间如果合法,则这个区间可用
        // 加完后设置新的区间首为 num+1,这边的话就是6
        // 注意一个情况就是,如果 [begin, num) 元素个数多了,那么只能加上应该有的个数,然后返回
        // 第二,如果nums数组区间内的元素个数不够k,那么要加上从最后一个元素右边的k个元素
        Arrays.sort(nums);
        long sum = 0, begin = 1, cnt;
        int n = nums.length;
        for (int num : nums) {
            if (begin < num) {
                cnt = num - 1 - begin + 1 <= k ? num - begin : k;
                // being + ... + num - 1
                sum += cnt * (begin + begin + cnt - 1) / 2;
                k -= cnt;
                if (k == 0) return sum;
            }
            begin = num + 1;
        }
        sum += (long) (nums[n - 1] + 1 + nums[n - 1] + k) * k / 2;
        return sum;
    }
}
208. 实现 Trie (前缀树)

Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。

请你实现 Trie 类:

  • Trie() 初始化前缀树对象。
  • void insert(String word) 向前缀树中插入字符串 word
  • boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false
  • boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false
class Trie {
    private Trie[] children;
    private boolean isEnd;

    public Trie() {
        children = new Trie[26];
        isEnd = false;
    }
    
    public void insert(String word) {
        Trie root = this;
        for (int i = 0; i < word.length(); i++) {
            int idx = word.charAt(i) - 'a';
            if (root.children[idx] == null) {
                root.children[idx] = new Trie();
            }
            root = root.children[idx];
        }
        root.isEnd = true;
    }
    
    public boolean search(String word) {
        Trie root = searchPrefix(word);
        return root != null && root.isEnd;
    }
    
    public boolean startsWith(String prefix) {
        Trie root = searchPrefix(prefix);
        return root != null;
    }

    private Trie searchPrefix(String prefix) {
        Trie root = this;
        for (int i = 0; i < prefix.length(); i++) {
            int idx = prefix.charAt(i) - 'a';
            if (root.children[idx] == null) {
                return null;
            } else {
                root = root.children[idx];
            }
        }
        return root;
    }
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值