LeetCode 678. 有效的括号字符串(贪心,动规) / 4. 寻找两个正序数组的中位数(二分,关键题) / 447. 回旋镖的数量

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 678. 有效的括号字符串(贪心,动规) / 4. 寻找两个正序数组的中位数(二分,关键题) / 447. 回旋镖的数量相关的知识,希望对你有一定的参考价值。

678. 有效的括号字符串

2021.9.12 每日一题

题目描述

给定一个只包含三种字符的字符串:( ,) 和 *,写一个函数来检验这个字符串是否为有效字符串。有效字符串具有如下规则:

任何左括号 ( 必须有相应的右括号 )。
任何右括号 ) 必须有相应的左括号 ( 。
左括号 ( 必须在对应的右括号之前 )。
* 可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符串。
一个空字符串也被视为有效字符串。

示例 1:

输入: “()”
输出: True

示例 2:

输入: “(*)”
输出: True

示例 3:

输入: “(*))”
输出: True

注意:

字符串大小将在 [1,100] 范围内。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-parenthesis-string
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

我刚开始是想用数字记录左右字符和特殊字符,然后判断的,然后从左往右写的时候,发现,从左到右判断右括号是否多余很好判断,而左括号不太容易判断;然后就想到了反过来不就能判断左括号了吗,然后就返回来写了,就过了

class Solution {
    public boolean checkValidString(String s) {
        //看到的第一反应当然还是栈了,但是后来感觉计数就行
        //写代码的时候,发现正反向各遍历一次好像是可以的

        int l = s.length();
        int count = 0;
        int left = 0;
        int right = 0;
        for(int i = 0; i < l; i++){
            char c = s.charAt(i);
            if(c == '('){
                left++;
            }else if(c == ')'){
                left--;
            }else{
                count++;
            }
            //如果右括号多了,那么就需要用特殊符号补充
            if(left < 0){
                count--;
                left++;
                //如果没有特殊符号了,就不行了
                if(count < 0){
                    //System.out.println("1 " + i);
                    return false;
                }
            }
        }
        //反过来
        count = 0;
        for(int i = l - 1; i >= 0; i--){
            char c = s.charAt(i);
            if(c == ')'){
                right++;
            }else if(c == '('){
                right--;
            }else{
                count++;
            }
            //如果左括号多了,那么就需要用特殊符号补充
            if(right < 0){
                count--;
                right++;
                //如果没有特殊符号了,就不行了
                if(count < 0){
                    //System.out.println("2 " + i);
                    return false;
                }
            }
        }
        return true;
    }
}

看了官解的贪心,直呼秒啊
还是上面从左到右遍历的思路,但是现在遇到星号,因为可能是左括号,也可能是右括号,所以维护一个最大值和最小值,表示未匹配的左括号的最大值和最小值
如果遍历过程中遇到左括号,那么max和min都加1
如果遍历过程中遇到右括号,那么max和min都减1
如果遍历过程中遇到星号,那么max加1,就是将星号变成左括号,min减1,将星号变成右括号
如果min为0了,就不减了,意思是在这个过程中未匹配的左括号数量不能少于0
如果max小于0了,那么说明右括号多了,所以返回false
遍历到最后,如果min大于0,说明左括号多了,false,如果等于0,说明都可以被匹配,true

挺难理解的,不如前后双向遍历

class Solution {
    public boolean checkValidString(String s) {
        //官解贪心
        int l = s.length();
        int count = 0;
        int left = 0;
        int max = 0;
        int min = 0;
        for(int i = 0; i < l; i++){
            char c = s.charAt(i);
            if(c == '('){
                min++;
                max++;
            }else if(c == ')'){
                min--;
                max--;
            }else{
                max++;
                min--;
            }
            //如果右括号多了,那么就需要用特殊符号补充
            if(max < 0){
                return false;
            }
            if(min < 0)
                min = 0;
        }
        return min == 0;
    }
}

官解给的动态规划也非常值得研究,又是一个不普通的动态规划

class Solution {
    public boolean checkValidString(String s) {
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        //一个字符的情况
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == '*') {
                dp[i][i] = true;
            }
        }
        //两个字符的情况
        for (int i = 1; i < n; i++) {
            char c1 = s.charAt(i - 1), c2 = s.charAt(i);
            dp[i - 1][i] = (c1 == '(' || c1 == '*') && (c2 == ')' || c2 == '*');
        }
        //动规
        for (int i = n - 3; i >= 0; i--) {
            char c1 = s.charAt(i);
            for (int j = i + 2; j < n; j++) {
                char c2 = s.charAt(j);
                //第一种情况,看左右两个边界字符
                if ((c1 == '(' || c1 == '*') && (c2 == ')' || c2 == '*')) {
                    dp[i][j] = dp[i + 1][j - 1];
                }
                //第二种情况,分成两段,如果两段都是合法的,总体也是合法的,这里很关键
                for (int k = i; k < j && !dp[i][j]; k++) {
                    dp[i][j] = dp[i][k] && dp[k + 1][j];
                }
            }
        }
        return dp[0][n - 1];
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/valid-parenthesis-string/solution/you-xiao-de-gua-hao-zi-fu-chuan-by-leetc-osi3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

4. 寻找两个正序数组的中位数

题目描述

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

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

示例 3:

输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000

示例 4:

输入:nums1 = [], nums2 = [1]
输出:1.00000

示例 5:

输入:nums1 = [2], nums2 = []
输出:2.00000

提示:

nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-10^6 <= nums1[i], nums2[i] <= 10^6

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

拖了很久没做,昨天同学笔试遇到了,今天赶紧来补一下

第一种方法,二分,主要就是利用有序,然后每次取中位数的一半,进行排除
这个方法的时间复杂度是log(m + n)

class Solution {
    int l1;
    int l2;
    int[] nums1;
    int[] nums2;
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        //第一个方法,二分查找
        //因为要找到的是中位数,假设是第k个数
        //然后首先在两个数组中分别找第 k / 2 个数,然后比较两个数的大小,比如A[i]<B[j],
        //因为比A[i]小的数最多有(k/2-1) * 2个,所以A[i]不可能是中位数
        //所以排除A[i]及A数组前面的数
        //然后现在找的是第k/2个数
        //然后以此类推,要注意是否越界
        this.nums1 = nums1;
        this.nums2 = nums2;
        l1 = nums1.length;
        l2 = nums2.length;
        int sum = l1 + l2;
        if(sum % 2 == 1){
            int mid = sum / 2;
            double res = getK(mid + 1, 0, 0);
            return res;
        }else{
            int mid1 = sum / 2 - 1;
            int mid2 = sum / 2;
            double res = (getK(mid1 + 1, 0, 0) + getK(mid2 + 1, 0, 0)) / 2.0;
            return res;
        }
    }

    //找第k个数,第一个数组开始下标是left1,第二个是left2
    public int getK(int k, int left1, int left2){
        if(left1 == l1)
            return nums2[left2 + k - 1];
        if(left2 == l2)
            return nums1[left1 + k - 1];
        if(k == 1)
            return nums1[left1] < nums2[left2] ? nums1[left1] : nums2[left2];
        int t = k / 2;
        int newleft1 = Math.min(l1 - 1, left1 + t - 1);
        int newleft2 = Math.min(l2 - 1, left2 + t - 1);
        int v1 = nums1[newleft1];
        int v2 = nums2[newleft2];
        //如果v1<v2,那么排除nums1中的数
        if(v1 <= v2){
            int cut = newleft1 - left1 + 1; //排除了这么多数
            left1 = newleft1 + 1;
            return getK(k - cut, left1, left2);
        }else{
            int cut = newleft2 - left2 + 1; //排除了这么多数
            left2 = newleft2 + 1;
            return getK(k - cut, left1, left2);
        }

    }
}

第二个方法,根据中位数的性质,小于等于它的数(左边)和大于等于它的数(右边)个数相同,且左边小于等于右边
所以对于两个数组,可以先对两个数组进行划分,然后判断划分点的关系
如果满足:
第 1 个数组分割线左边的第 1 个数小于等于第 2 个数组分割线右边的第 1 的数
第 2 个数组分割线左边的第 1 个数小于等于第 1 个数组分割线右边的第 1 的数
就说明,左边是小于右边的,那么如果左边的个数加起来是一半的话,那么中位数就可以很方便的取到(分奇偶情况)
注释非常详尽了
这样,只需要在较短的数组里二分,复杂度是logm

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        //利用中位数的性质来做一下
        int l1 = nums1.length;
        int l2 = nums2.length;
        //保证l1 < l2
        if(l1 > l2){
            int[] temp = nums1;
            nums1 = nums2;
            nums2 = temp;
            int len = l1;
            l1 = l2;
            l2 = len;
        }
        //左边要有half个数字
        int half = (l1 + l2 + 1) / 2;

        //在第一个数组里,找分割线,可以是0或者m,找到了第一个数组的分割线,第二个数组的分割线自然也就确定了
        int left = 0;
        int right = l1;
        while(left < right){
            //第一个数组中分割线的位置,分割线在第i个位置,前面就有i个数
            int i = (right - left + 1) / 2 + left;
            //第二个数组中分割线的位置,第二个数组中应该有half - i个数,所以分割线在half - i
            int j = half - i;
            //然后判断这个分割线是否满足条件
            //如果i-1位置大于j位置,说明数组1的分割线靠右了,所以下次查找分割线向左移
            if(nums1[i - 1] > nums2[j]){
                right = i - 1;;
            }else{
                //否则,满足条件的
                left = i;
            }
        }
        //此时,找到了分割线
        int i = left;
        int j = half - i;
        //特殊情况,如果i是0或者l1,j是0或者l2
        //如果边界在最左边,那么将左边最大值赋值为MIN_VALUE
        int leftmax1 = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
        //如果边界在最右边,那么将右边最小值赋值为MAX_VALUE
        int rightmin1 = i == l1 ? Integer.MAX_VALUE : nums1[i];

        int leftmax2 = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
        int rightmin2 = j == l2 ? Integer.MAX_VALUE : nums2[j];

        int sum = l1 + l2;
        //如果是奇数,那么就是左边最大值
        if(sum % 2 == 1){
            return Math.max(leftmax1, leftmax2);
        }else{
            //如果是偶数,那么就是左边最大值和右边最小值之和 / 2
            return (Math.max(leftmax1, leftmax2) + Math.min(rightmin1, rightmin2)) / 2.0;
        }
    }
}

推广到两个有序数组中找第k个数,也是这种思路
这时候,因为数组长度不一定大于k,所以需要根据长度和k的较小值来进行二分

447. 回旋镖的数量

题目描述

给定平面上 n 对 互不相同 的点 points ,其中 points[i] = [xi, yi] 。回旋镖 是由点 (i, j, k) 表示的元组 ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序)。

返回平面上所有回旋镖的数量。

示例 1:

输入:points = [[0,0],[1,0],[2,0]]
输出:2
解释:两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]

示例 2:

输入:points = [[1,1],[2,2],[3,3]]
输出:2

示例 3:

输入:points = [[1,1]]
输出:0

提示:

n == points.length
1 <= n <= 500
points[i].length == 2
-10^4 <= xi, yi <= 10^4
所有点都 互不相同

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-boomerangs
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

注意距离为勾股定理那个距离,题目没写,写汉明距离错了

class Solution {
    public int numberOfBoomerangs(int[][] points) {
        int l = points.length;
        //到i点,距离为d的点的数目
        Map<Integer, Map<Integer, Integer>> map = new HashMap<>();
        for(int i = 0; i < l; i++){
            Map<Integer, Integer> temp = map.getOrDefault(i, new HashMap<>());
            for(int j = 0; j < l; j++){
                if(i == j)
                    continue;
                int[] a = points[i];
                int[] b = points[j];
                int x = Math.abs(a[0] - b[0]);
                int y = Math.abs(a[1] - b[1]);
                int d = x * x + y * y;
                temp.put(d, temp.getOrDefault(d, 0) + 1);
            }
            map.put(i, temp);
        }
        int res = 0;
        for(int k : map.keySet()){
            Map<Integer, Integer> temp = map.get(k);
            for(int p : temp.keySet()){
                int v = temp.get(p);
                res += v * (v - 1);
            }
        }
        return res;
    }
}

一遍处理

class Solution {
    public 以上是关于LeetCode 678. 有效的括号字符串(贪心,动规) / 4. 寻找两个正序数组的中位数(二分,关键题) / 447. 回旋镖的数量的主要内容,如果未能解决你的问题,请参考以下文章

[M贪心] lc678. 有效的括号字符串(括号问题+思维+模拟+dp)

Leetcode 678.有效的括号字符串

LeetCode 678 有效的括号字符串[栈] HERODING的LeetCode之路

Leetcode刷题100天—678. 有效的括号字符串( 字符串)—day36

Leetcode刷题100天—678. 有效的括号字符串( 字符串)—day36

leetcode678